Posts
Search
Contact
Cookies
About
RSS

Godot, Global Enums and behaviours

Added 7 Nov 2024, 1:47 a.m. edited 7 Nov 2024, 4:17 a.m.

Before showing how to implement global Enums, lets look at a simple scenario and discuss why they might be really very useful, and how we can take related ideas further

You have a player (with a health stat) and you want to implement multiple enemy types - lets say goblins and evil wizards, and you want your enemies to be able to damage the player.  One naive way to do this would be for each enemy is to just directly change the players health variable, this is a bad idea for a number of reasons. 

Imagine you later decide you want different types of damage, then you have to start implementing bits of player code replicated in different enemy types.  Easy to miss implementing something for every enemy type and suddenly you have (likely replicated) player related code all over the place and your lovely game falls to the floor in a nervous heap of spaghetti code.

A better solution is to have a function in the player script to accept damage, as no doubt the enemy type probably has a target variable, you have access to the player, the function signature might look something like this initially.

func damage( amount:float ) -> void

That's a good start, so lets add an enum for the damage type

func damage( amount:float, type:DamageType ) -> void 
Now, there is a problem because both the player script and the various enemies all need access to the enum, while you could put the enum in the player script, what if you later decide you want an enemy that can attack the player and also other enemies or even buildings, while still usable, its going to result in less readable code in the end, for example in your code where an enemy damages a building - at first glance it looks like the player is involved because your need the damage type enum.

Lets instead make an enum script

class_name E

enum DAMAGE {
CLUB,
ARROW,
FIREBALL
}

I prefer to abbreviate the class name to E, you may prefer Enums, in use it looks like this

# player.gd

func damage( amount:float, type:E.DAMAGE ) -> void
...

# calling when the enemy hits the player

target.damage( 42, E.DAMAGE.ARROW )

If you also implement a damage function in your enemy types then the player can also damage enemies

Your damage function can then subtract health and apply knock back force for club or burn status as appropriate for each damage type the player receives, a giant might not implement knock back and imp might not implement burn status.

As a bonus technique if you have another script called behaviours with a bunch of static functions, you could make an entirely generic enemy script, with general behaviours, damage behaviours etc, you can run these from a state engine for example take a look at this code:

static func impWalk(str):
print("WALKING "+str)

static func impRun(str):
print("RUNNING "+str)

a variable can hold the current behaviour

behaviour = Callable(Behaviours.impWalk)

# later something changes the behavior
behaviour = Callable(Behaviors.impRun)

# in process do whatever behaviour is set
behaviour.call("imp1")

obviously rather than a string you'd want to send some instance data, such as the particular enemies health and other data specific to an individual enemy

This need not be a single variable you could have an array of behaviours, maybe some arrows are poisoned and add a poison behaviour that does a certain amount of damage every second and removes itself from the array once its run for so many seconds, are you starting to see how powerful this is !

Add a "factory" function that creates specific enemies on demand and it can take a generic entity class, set its model property and its instance specific data, and then add it into the scene tree...

tip:

void call_group(group: StringName, method: StringName, ...) vararg 

can be useful....

# in main scene process method
get_tree
().call_group("entities", "do_behaviour")

This would call do_behaviours on all objects in the scene "tagged" with the "entities" group

If you're not familiar with state engines, I'd recommend that as a useful avenue of research.


I think I've probably give you more than enough to think about... so...

Enjoy!