What are Enums?

4.4

Hi! Uh how to use Enums and what are they?

I think they can be used like:


enum states { JUMP, WALK, RUN, IDLE, HURT, DEATH}

If state == states.JUMP:
If actionjustpressed("Jump"):
Velocity.y = J_Velocity

Yeah, you could do that but can i just do


enum states { JUMP, WALK, RUN, IDLE, HURT, DEATH }

If states.JUMP:
If actionjustpressed("Jump"):
Velocity.y = J_Velocity

Instead of doing: If state == states.JUMP:

I can do: if states.JUMP:

Right?
And another question.. How to switch states with enums? I’m not used to enums so yeah, I’m a newbie:)

Sorry if this didn’t make sense.

As far as I know, enums are just a list of constants. When you check if states.jump:, I think you check if it exists, rather than if the state is states.JUMP. The state variable would be what you want to check against and change depending on what the player is doing (state = self.JUMP, if state == self.IDLE:). See the docs here (scroll down a little).

2 Likes

Enums are effectively int.

So, any enum not explicitely defined as 0 will always return true. The bool type in Godot returns false if 0 (or 0.0 for float) and true if anything but 0.

You can also define your enums like this:

enum Reaction {
	Yell = 10,
	Cower = 20,
	FightBack = 30,
	RunAway = 50,
	FullPanic = 100
	}

So these would all return true.

EDIT:
If there is no explicit assignment of value, the first enum will be 0.
To read more about enums I’d suggest reading the Enum Help page: GDScript reference — Godot Engine (stable) documentation in English

2 Likes

Enums are a way to make a list of options available to a programmer so that those are the only options available, and then give them human-readable names to make reading code containing them easier to read.

Implementation-wise, an Enum is a Dictionary with each name being a String key, and each value is stored as an int.

No. @Opalion explained this pretty well.

enum STATE { JUMP, WALK, RUN, IDLE, HURT, DEATH}

var player_state: STATE

func _process(_delta: float) -> void:
	if Input.is_action_just_pressed("jump") and character.is_on_floor():
		player_state = STATE.JUMP
		character.velocity.y = character.jump_velocity

EDIT:
While @dharhix is correct and you can do that with Enums, I recommend against it. It was really useful 20+ years ago in keeping code small, but assigning int values to ENUMS tends to make your code super confusing because you have to always pop back to the Enum definition to see what the value is and what you meant by putting it there.

The one exception is if you want to use an Enum as a bit mask. Then it would make sense because the individual values of the Enum values don’t matter, just whether they are on or off.

enum MetaMagicProperties { 
	Extend_Spell_Range = 1,
	Maximize_Spell_Damage = 2,
	Silent_Spell = 4,
	Extend_Spell_Damage = 8,
	Cold_Spell = 16
}
1 Like

Thank you for calling me old. :joy:

Although yes, you are correct, there are cases when I find it useful. In the case above, I use them as a range. But, I definitely won’t encourage anyone to use them that way all the time; it will become very confusing. It has its uses though.

2 Likes

I disagree. While it’s usually not necessary to set the integers explicitly, it can help for debugging, if you want to print a state associated with an enum, for example

class_name Main
extends Node2D

enum Colors {
	RED,
	YELLOW,
	GREEN,
	CYAN,
	BLUE,
	MAGENTA
}

var color: Colors = Colors.CYAN

func _ready() -> void:
	print(color)

This will just output 3 and (especially if the enum is bigger than this) it is easier to find what element that stands for if you declare the colors explicitly, like

enum Colors {
	RED = 0,
	YELLOW = 1,
	GREEN = 2,
	CYAN = 3,
	BLUE = 4,
	MAGENTA = 5
}

I also think assigning values explicitly is more intuitive when using them as indices for an array:

enum MeshArrays {
	VERTEX = 0,
	NORMAL = 1,
	COLOR = 2,
	TEX_UV = 3,
	TEX_UV2 = 4,
	INDEX = 5
}

var mesh: Array[Array] = [
	[],
	[],
	[],
	[],
	[],
	[]
]

func foo() -> void:
	print(mesh[MeshArrays.VERTEX])
3 Likes

Right now I’m working on an AI implementation for my units and I use the following. Using enums as range make things far easier to deal with. At least for me. I’m sure everyone would have an opinion on how differently they would do it. Still, it’s valid and it works. That’s what matters most to me.

	## Make a morale check.
	if morale_check_unit():
		# Success
		pass
	else:
		# Failure
		var fail_num = get_fail_number()
		if fail_num <= Reaction.Yell:
			pass
		elif fail_num <= Reaction.Cower:
			pass
		elif fail_num <= Reaction.FightBack:
			pass
		elif fail_num <= Reaction.RunAway:
			pass
		else:
			## Full blown panic
			pass
3 Likes

@yesko and @dharhix you both make good points. I had not considered it from a debug POV. Nor had I considered using them as ints in a comparison.

This comment made me laugh. :slight_smile:


While we’re talking about cool tricks with enums, I like to create a to_string() method. I find it helpful for various reasons, and is what I use for debugging:

enum CHANNEL {
	Master,
	Music,
	SFX,
	UI,
	Ambient,
	Dialogue,
}

## Returns a String for the passed CHANNEL.
func channel_to_string(channel: CHANNEL) -> String:
	return CHANNEL.find_key(channel)

So if I’m having an issue I’ll throw in a print statement like…

print("Channel: %s" % channel.channel_to_string())

The main reason I use it for this particular enum is for this method:

## Returns the bus index for the passed CHANNEL.
func _channel_to_bus_index(channel: CHANNEL) -> int:
	return AudioServer.get_bus_index(Sound.channel_to_string(channel))
3 Likes

Yes, that! I use a version of this as debug measures and for other usages. It can be very helpful when you need something readable.

2 Likes

I’ll add one more trick I use here. Still using enums and it goes like this:

In the AI code I’m using right now, I made a Horde class that is effectively a manager for the units’s behaviors. In there I have an enum defined thus:

## Enum describing events that can be triggered.
enum EventTriggered {
	## Event of value 0.
	IKnewThat = 0,
	## Event adding new unit, value -5.
	AddBrethren = -5,
	## Event removing unit, value 10.
	RemoveBrethren = 10,
	## Event hurting unit, value 3.
	HurtBrethren = 3,
	## Event healing unit, value -3.
	HealBrethren = -3,
	## Event Queen Talking to units, value -50.
	QueenSpeech = -50,
	## Event player in sight, value 50.
	PlayerInSight = 50,
	## Event unit hurting player, value -15.
	PlayerHurt = -15,
	## Event player healing, value 25.
	PlayerHeal = 25,
	## Event when unit is ordered by higher tier, value 20.
	Ordered = 20,
	## Event to force a morale failure, value 10,000.
	ForceMoraleFailure = 10000
}

Why I use enums with defined numbers? Because these are EVENTS used as stressors or calmers. So, they either add to a unit’s stress that could cause a morale failure or calming them thus helping succeed a morale check.

So, the full function, as shown in previous post is this:


## Event triggered by an external script; Horde in this case.
func external_event(event : Horde.EventTriggered):
	if event == Horde.EventTriggered.PlayerInSight:
		if !knows_player:
			knows_player = true
			stressor += event
	else:
		stressor += event

	reevaluate_states()
	## Make a morale check.
	if morale_check_unit():
		# Success
		pass
	else:
		# Failure
		var fail_num = get_fail_number()
		if fail_num <= Reaction.Yell:
			pass
		elif fail_num <= Reaction.Cower:
			pass
		elif fail_num <= Reaction.FightBack:
			pass
		elif fail_num <= Reaction.RunAway:
			pass
		else:
			## Full blown panic
			pass

I wish I could tell you that it works fantastic, but I can’t right now because it’s not entirely done as I’m procrastinating here instead. :sweat_smile:

Hopefully that might help someone at one point or another.

2 Likes

Hi, Not related with what you’re talking about but… Why did you do: enum Colors {
RED = 0,
YELLOW = 1,
GREEN = 2,
CYAN = 3,
BLUE = 4,
MAGENTA = 5

}

Instead of: enum Colors { RED = 0, YELLOW = 1, GREEN = 2, CYAN = 3, BLUE = 4, MAGENTA = 5}

Is there a difference?

Thanks!

Speaking for myself, for readability reasons. It won’t change anything to its parsing or implementation. It’s just easier to read and get.

2 Likes

Wait i have another question (sorry for an explosion of questions) Should i use Nodes for my State Machine or enums? I followed a tutorial a long time ago and it used Nodes as the State Machine (The how to make a Boss with states)
Are enums more professional than Node-based State Machines?

And what is the usual State Machines? With enums? With something else? With Nodes?

Thanks!

To that I’ll answer: it depends on who you ask. I’m sure there’s some companies out there that demand their programmers use way X or Y, but for the most part it doesn’t really matter. That’s even truer for us, gamedev hobbyists. Work the way you feel it works the best for you.

At one point if you want (like REALLY REALLY want) you can go crazy with profiling your code to prove your point in one way or another, but until then… do as you please. XD

As for the other questions, I’ll just state (and it underlines the above), I do it my own way. So… :man_shrugging:

1 Like

Well how about performance based? Which will be faster to load? Enums state machines or Node-based state machines?

There will be few games that will be run on Godot where that will matter, to be honest. For most games, the question in itself is pretty much irrelevant because they are so small, that it won’t matter.

Now, if you’re telling me you’re making a AAA game with Godot then, yes, there might be instances where you need to keep both eyes open on that sort of thing, but in general, a non-issue.

If you REALLY want an answer to that, then I’m not the right guy to give you an answer. The scope of my projects were never large enough for me to worry about such things, so I’ll let others do the honor. :grinning:

1 Like

I use nodes. Then if I want a state to be available, I just attach it StateMachine node.

class_name StateMachine extends Node
## This node is intended to be attached to a character and manage the various
## states of the character. If a charatcer has a state it should be added as a
## child node of the state machine.


## The initial CharacterState for the character when it is added to the game.
@export var starting_state: CharacterState


# The current state of the character. Initially defaults to the first node it
# finds beneath itself if starting_state is not defined.
@onready var _current_state: CharacterState = starting_state if starting_state != null else self.get_child(0)


## Sets up all the states for this character.
func _ready() -> void:
	switch_state(_current_state)
	for state in get_children():
		if state is CharacterState:
			state.activate_state()
	self.connect("child_entered_tree", _on_state_added)
	self.connect("child_exiting_tree", _on_state_removed)


## Switch to the target state from the current state. Fails if:
## 1. The character does not have the passed state.
## 2. The character is already in that state.
## 3. The current state won't allow a transition to happen.
## 4. The target state won't allow a transition (e.g. cooldown timers).
func switch_state(state: CharacterState) -> void:
	if not _character_has_state(state): return
	if _current_state == state: return
	if not _current_state.can_transition: return
	if not state.can_transition: return
	
	_current_state.exit_state()
	_current_state = state
	_current_state.enter_state()


# Returns whether or not the character has this state.
# (A character has a state if the state is a child node of this StateMachine.)
func _character_has_state(state: CharacterState) -> bool:
	for element in get_children():
		if element == state:
			return true
	return false


# Activates a state.
# (Called when a node enters the tree as a child node of this StateMachine.)
func _on_state_added(node: Node) -> void:
	if not node is CharacterState:
		return
	node.activate_state()


# Deactivates a state.
# (Called when a child node of this StateMachine leaves the tree.)
func _on_state_removed(node: Node) -> void:
	if not node is CharacterState:
		return
	node.deactivate_state()

It handles what state the character in, but switching in and out of states is handled by the states themselves. I use a base state that is common for the player, enemies and NPCs.

class_name CharacterState extends Node
# A virtual state for states to inherit.

## Stores a reference to the character to which this state is attached.
@onready var character: Character = get_owner()


## Set to false if a character cannot use an ability or take an action,
## for example when waiting for a cooldown timer to expire.
var can_transition = true


## Initialize the state. Process mode is set so the state can be paused.
## Then all processing (including input and physics) is turned off.
func _ready() -> void:
	process_mode = ProcessMode.PROCESS_MODE_PAUSABLE
	set_physics_process(false)
	set_process(false)
	set_process_input(false)
	set_process_unhandled_input(false)


## Turn processing on for everything but physics when the state is active.
func activate_state() -> void:
	set_process(true)
	set_process_input(true)
	set_process_unhandled_input(true)


## Turn processing off when the state is deactivated.
func deactivate_state() -> void:
	set_process(false)
	set_process_input(false)
	set_process_unhandled_input(false)
	set_physics_process(false)


## Virtual function for inherited classes to implement, for example starting an animation.
func enter_state() -> void:
	set_physics_process(true)


## Virtual function for inherited classes to implement.
func exit_state() -> void:
	set_physics_process(false)

Then a specific state (which inherits from PlayerState which inherits from CharacterState) handles all the things that happen in the state, as well as when it can be entered and exited.

class_name PlayerJumpState extends PlayerState


# If the player presses the Jump action, switch to the Jump state.
func _process(_delta: float) -> void:
	if Input.is_action_just_pressed("jump") and character.is_on_floor():
		character.state_machine.switch_state(self)
		character.velocity.y = character.jump_velocity


# Process the jump every frame and trigger the landing when we land.
func _physics_process(_delta: float) -> void:
	if character.is_on_floor():
		character.animation_state.travel("Jump_Land")
	character.move_and_slide()


# Starts the jump animation, and turns off the ability to transition to
# another state mid-jump.
func enter_state() -> void:
	super()
	character.animation_tree.connect("animation_finished", _on_animation_finished)
	character.animation_state.travel("Jump_Start")
	can_transition = false


func exit_state() -> void:
	super()
	character.animation_tree.disconnect("animation_finished", _on_animation_finished)


# Allows state transition only after the initial Jump_Start animation has been called. This is
# because otherwise the landing animation is called because the first physics frame this class runs
# is from the floor and so is_on_floor() is still true.
func _on_animation_finished(animation_name: String) -> void:
	match animation_name:
		"Jump_Start":
			can_transition = true

This model uses composition and inheritance. Composition is adding to nodes to enable functionality. If I wanted, for example, to make the jump ability a discoverable power, I can only add it to the player when they discover it in game. All the code managing it is encapsulated in that one node. If I want to disable jumping, I can just take the node away, or even move it out from under the StateMachine node temporarily.

StateMachines are at the heart of game development. If you were to code one from scratch, your first state machine would be the game loop that runs, checking to make sure that everything happens every frame: player input, enemies taking actions, etc. Then the player and enemies would all have their own state machines as well. Godot’s game loop is such that you can tap into it any time with the _process() and _physics_process() functions on any node.

There is no usual per se. Whatever works best for you is what’s right for your game.

An enum-based state machine will be faster in theory. But it depends on how you use it. Nodes are optimized by the Godot developers. So while it may seem like nodes are heavier-weight than an enum, you’re better off leaving optimization up to the developers of the language and framework until you run into issues.

For example, every once in a while, I see someone asking about storing ints as 32-bit numbers instead of 64-bit numbers because they want to increase performance. However modern 64-bit processors are optimized to work with 64-bit numbers. If you give it a number in a smaller container, even if it’s just var blah: int = 1, you are actually slowing the processor down.

So don’t worry about performance until you come across performance issues. Also, if you do come across performance issues the answer to your problem is more likely going to be solved by writing code in C# or C++ to take advantage of compiled code.

TLDR: Use whatever makes your code clear to you. Obfuscating things to try and optimize performance in the beginning will slow you down and likely not matter in the long run.

2 Likes

Hahaha I just saw this part of your comment. :smiley: I was speaking from experience myself. :wink:

2 Likes