What would be best practice for implementing an ability system similar to Kirby?

Godot Version

4.4.1

Question

I’m working on my first ever large project, a 2.5D side-scroller akin to Kirby’s Return to Dreamland. (Because it’s my first actual project, I’m fine with it not being that original)

I’ve been stumped thinking on how I should implement the copy ability system, because every single copy ability has different moves that trigger during different circumstances (Pressing Attack VS Pressing Attack and Down while Running in midair), or change the Player’s movement passively (Slow falling).

I could make a simple state machine system inside the Player Script, but I’d rather have all the abilities as separate nodes so that it’d be easy to edit/change how an ability operates without worrying about it interfering with the other abilities, but I’m not sure how I would go about this.

I would approach it by creating an abstract class Ability that extends Resource and will be extended by your actual abilities, like MeleeAttack, RangedAttack, Dash, Copy, etc.

class_name Ability
extends Resource

func trigger() -> void:
	pass # we don't want any logic here, as it should be overridden by actual abilities
class_name MeleeAttack
extends Ability

func trigger() -> void:
	# your melee attack logic
class_name RangedAttack
extends Ability

func trigger() -> void:
	# your ranged attack logic

Once you have the *.gd scripts with the logic, you need to create the actual Resource *.tres.
Your character should have a list of abilities that you can then assign through inspector.
Let’s assume your abilities are triggered with buttons 1, 2, …, 9, etc. the logic would be something like that. You just need to create 9 actions in your project settings called “ability_1”, “ability_2”, …, “ability_9”, etc. and assign 1-9 buttons to them respectively.

class_name Character
extends CharacterBody3D

@export var abilities: Array[Ability]

func _unhandled_input(event: InputEvent) -> void:
	for i in range(1, 10):
		if event.is_action_pressed("ability_%s" % i) and abilities.size() >= i:
			abilities[i - 1].trigger()

That’s more or less how I implemented it in my turn based game, where several units types have unique abilities.
This way it’s modular and isn’t hardcoded per character. You can add and remove abilities from your character even at runtime if you like.

As for your copy ability specifically - I haven’t played the game you mentioned, but you probably want to perform the same ability that your opponent has performed last. In this case, you should store the last ability performed by a character in a variable, and then when you trigger your copy ability, it would look at this variable on the opponent character and trigger that specific ability.

Let me know in case you have issues implementing this in your game.

1 Like

I should have explained this in the original post, but copy abilities work like this.

The player encounters an enemy, lets say a little water spirit. That water spirit maybe occasionally shoots some water projectile at the player.
The player has the option to absorb the enemy’s power, completely changing the player’s moveset to one based on water. Different water-based enemies when absorbed would also change the player into that exact same water form.
Knight enemies change the player into a sword-wielding form, Bat enemies change the player into a vampire form, so on so forth.

My exact issue comes from that all these different forms require different ways of activating their abilities, which they also have differing amounts of.
For example, the Water and Sword form may have an attack that activates when the player presses the attack button while running, while the vampire form doesn’t. The vampire ability has an ability that activates if the player presses the jump button in midair, while the Water and Sword form don’t.

I could think of one way to implement this, which would be having an array of every move on the current frame and checking each frame if a move’s condition is met, but I’m worried about how that would affect the performance.

Your answer did help a lot though, I’ll try implementing it as far as I can, specialized for my exact situation, and I’ll let you know how it goes afterwards.

Thanks for the explanation.
Regardless, the approach I mentioned should still be viable for this. You’d just need to prepare a set of abilities per enemy type and then swap the abilities that are currently being used by your character to the ones you want to use.
Looking forward to see if you manage to implement it. Let me know!

1 Like

I managed to figure it out!
This is a very simplified version of what I did (If anyone wants a more detailed version, feel free to ask):

I have an enum-based state machine in my player controller that handles the general states that the player can be in regardless of current ability. Then, I also have a node-based state machine inside of my player scene that has every ability state as it’s child. All the states have a reference to the Player, and can thus check the current general state the player is in.
(This video helped a lot with making the node-based state machine: https://www.youtube.com/watch?v=ow_Lum-Agbs )