A Command Pattern implementation in Godot with C#

I recently built a Command Pattern system to allow non-programmers to easily create in-game events via the Godot inspector. While this is a Showcase rather than a full tutorial, I wanted to share the implementation to hopefully inspire someone!

I did my best to keep explanations very simple, this is mostly aimed at newcomers to Godot and game development overall. So here we go!

I was asked / hired to port a game from RPGMaker to Godot. Luckily my client didn’t require every single RPGMaker feature to be present, in fact, they only really needed a couple of things. One of which was a simple way to edit maps (scenes) AND to be able to create events in the game without having to write any code. I’ve always enjoyed creating development tools that make dev work easier overall, sometimes I find it even more enjoyable than actually using said tools!

After thinking about the implementation, I’ve quickly decided to go with the Command Pattern for it.

That book explains the pattern really well, but I noticed that many newcomers struggle when it comes to actually implementing these ideas into their own game. While this is C#, the general idea of it can easily be translated into GDScript too, and if you believe this feature could benefit your game in any way, feel free to try and implement it in GDScript based on the C# code! It might even be easier to do so since C# requires a bit more boilerplate for simple things. Again, this isn’t meant to read like a tutorial, I will mostly just be showing off how I achieved things.

At its core, this pattern is just a single abstract class with a single function that we call when we want said command or “action” to execute.
For Godot specifically, I’ve decided to make this abstract class inherit Resource, the simple reason is that they’re serializable, reusable across scenes, and show up very cleanly in the inspector, which is the main point my client needed. Requires no programming knowledge, just a few clicks.

[GlobalClass]
public abstract partial class TriggerAction : Resource
{
    public abstract void Execute(Node caller, Node target = null);
}

Once we have an abstract class, we can start building on top of it!
The very first thing I tried was a very simple debug action, to print something to the console.

[GlobalClass]
public partial class DebugPrintAction : TriggerAction
{
    [Export] private string _printWhat;

    public override void Execute(Node caller, Node target = null)
    {
        GD.Print(_printWhat);
    }
}

So why is any of this useful?
Simple, by doing this, we can implement something like a TriggerArea! These are Area2Ds that will do a certain action as soon as the player walks through them.

public partial class TriggerArea : Area2D
{
	[Export] public Array<TriggerAction> Actions;
	[Export] public bool OneShot = true;

	private bool _hasBeenActivated = false;

	public override void _Ready()
	{
		base._Ready();
		BodyEntered += OnBodyEntered;
	}

	public override void _ExitTree()
	{
		BodyEntered -= OnBodyEntered;
		base._ExitTree();
	}

	private void OnBodyEntered(Node2D body)
	{
		if (OneShot && _hasBeenActivated)
		{
			return;
		}

		if (body is not Player player)
		{
			return;
		}
		
		ExecuteActions(player);
		_hasBeenActivated = true;
	}

	private void ExecuteActions(Player player)
	{
        // This is the core of the pattern! We loop through every resource 
        // in the inspector array and fire them off all at once.
		foreach (TriggerAction action in Actions)
		{
			action.Execute(this, player);
		}
	}
}

Doing this gives you a very easy to use inspect menu, that looks and works like this:

This way, the moment this triggers, all the actions we added here will execute all at once. In this case, it will simply print whatever we wanted in the console.

Of course, there’s an inherent limitation when it comes to Resources, which is the fact that they cannot have knowledge of any given scene.
What this means is that we can’t just drag and drop a specific node we have in the scene to an Action we created, because these Resources don’t inherit Node. They have no knowledge of the scene tree.

To get around this limitation, I’ve created two base classes that still make it possible to interact with nodes in a given scene, and this is why every single TriggerAction requires the caller node to be passed, so I can still get to the scene tree if needed.
One of these base classes will use the Groups feature of Godot.

public abstract partial class NodeGroupBaseAction : TriggerAction
{
    [Export] private StringName _groupToUse;
    public override void Execute(Node caller, Node target = null)
    {
        if (_groupToUse == null)
        {
            GD.PushWarning("Node group was null! Please check your action!");
            return;
        }
        
        if (_groupToUse.IsEmpty)
        {
            GD.PushWarning("Node group name was empty! Please check your action!");
            return;
        }
        
        foreach (Node node in caller.GetTree().GetNodesInGroup(_groupToUse))
        {
            ApplyAction(node);
        }
    }

    protected abstract void ApplyAction(Node node);
}

This allows us to give the Trigger a name for a group. This can be serializable very easily, and we only need to worry about actually connecting the dots during runtime. What this looks like in the inspector:

And for that specific Light2DToggleAction, all I had to do was this:

[GlobalClass]
public partial class Light2DToggleAction : NodeGroupBaseAction
{
    protected override void ApplyAction(Node node)
    {
        if (node is not Light2D light)
        {
            GD.PushWarning("Node was not a Light2D! Please check your groups!");
            return;
        }
        
        light.Enabled = !light.Enabled;
    }
}

And as you saw in the NodeGroupBaseAction code, we call the ApplyAction function for every single item in the group.

The other way to implement with something specific on the scene tree is using Godot’s Scene Unique Nodes!

This is similar to the group one, except we mark a node so we can access it by its unique name, and in code, we can simply do this:

public abstract partial class UniqueNameBaseAction : TriggerAction
{
    [Export] private StringName _nodeToUse;

    public override void Execute(Node caller, Node target = null)
    {
        if (_nodeToUse == null)
        {
            GD.PushWarning("Unique name was empty for Node! Please check your action!");
            return;
        }

        if (_nodeToUse.IsEmpty)
        {
            GD.PushWarning("Unique name was empty for Node! Please check your action!");
            return;
        }

        Node node = caller.GetNodeOrNull($"%{_nodeToUse}")
                    ?? caller.GetTree().CurrentScene?.GetNodeOrNull($"%{_nodeToUse}");

        if (node == null)
        {
            GD.PushWarning($"Node with unique name '%{_nodeToUse}' was not found via caller or CurrentScene!");
            return;
        }
        
        ApplyAction(node);
    }

    protected abstract void ApplyAction(Node node);
}

For this one I have a bit of a fallback, since finding the specific node might not always be immediately obvious, and I have a lot of null checking to make sure we did find the specific node we want to interact with.

So far the only action I made using this class is an action that can remove a specific node, see:

[GlobalClass]
public partial class RemoveNodeAction : UniqueNameBaseAction
{
    protected override void ApplyAction(Node node)
    {
        node?.QueueFree();
    }
}

Using Resources also mean that it’s super easy to change the actions while the game is running, so it’s very easy to experiment and change something!

This is only a very few and easy examples, but of course this can be expanded more and more, I currently have the following actions created:

Hopefully this can inspire someone or give ideas on how this pattern can be used in Godot!
If you have any ideas or improvements, or any questions about the implementation, feel free to comment!

5 Likes

I’m definitely planning to record either a video or post an example for people to use eventually!

2 Likes

This was a really interesting read, as was the Command Pattern link. It got me thinking about two things I was working on this weekend and new ways to refactor them. The first is a Sing-along Song feature. The second is how I share States between Players vs Enemies/NPCs and how the main difference is needing to get player input.

Sing-Along Songs

So I’ve had this idea on the back-burner for a while. The idea was to display lyrics to a song, and then have a bouncing goblin dance on the syllables of the song as it plays in time to the song. It was an idea I wanted to add to the credits of a game jam, and I put it on the back burner.

Yesterday, I started re-building that game jam game from scratch, moving from Godot 4.4 to Godot 4.7, and incorporating all the things (and templates/plugins) I’ve implemented since then. I thought I’d start with the sing-along song feaure.

I decided to implement it using the Localization CSV feature, as I’d already implemented and tested it with my Localization Plugin.

I did this for three reasons. The first, it made it easy to copy and paste the lyrics into Google Sheets. Each line got its own cell. Second, I could use the string replacement feature. Third, I could later add translations if I wanted just by adding columns.

I decided to add the time marks for when each lyric should show up after the song title, and parse that information. this gave me unique strings so that they wouldn’t conflict with other translations. I got it working, and even was able to handle fractions of a second, and instrumental sections by having blank “translations” at timestamps.

Pausing worked, but I thought it would be really cool to rewind and fast-forward. Unfortunately, I didn’t have a way to do that. My implementation was to create a Dictionary with the timestamp as the key, and the lyrics as the value. Once an entry is played, I delete it, and it looks for the next timestamp.

I had considered using an AnimationPlayer. I had considered an iterator. I’m wondering if the Command Pattern would be appropriate here though. Especially for the bouncing goblin, who needs to know where he’s bouncing from and to.

Here’s the current code:

class_name LyricsLabel extends Label

@export var song_title: String
@export_file_path("*.csv") var lyrics_location: String

var timer: float = 0.0
var lyrics: Dictionary[float, String]


func _ready() -> void:
	_load_lyrics()


func _process(delta: float) -> void:
	if lyrics.is_empty():
		return
	
	timer += delta
	
	var first_key = lyrics.keys()[0]
	
	if timer >= first_key:
		text = lyrics[first_key]
		lyrics.erase(first_key)


func _load_lyrics() -> void:
	var file: FileAccess = FileAccess.open(lyrics_location, FileAccess.READ)
	
	if not file:
		print("Error opening file: ", FileAccess.get_open_error())
		return
	
	while !file.eof_reached():
		var line_data: PackedStringArray = file.get_csv_line()
		if line_data[0].contains(song_title.to_upper()):
			var second_mark: float = float(line_data[0].get_slice("_", 2))
			var minute_mark: float = float(line_data[0].get_slice("_", 1)) * 60.0
			var time: float = second_mark + minute_mark
			if line_data[1].is_empty():
				lyrics[time] = ""
			else:
				lyrics[time] = line_data[0]

States

I have a base Character scene. (See above.) It has states in it that are shared by everyone. Idle, Fall, Hurt, and Death.

But then the Player needs specific states that handle player input. Run needs input every frame from the player to stay active. Jump requires a button press, and allows you to let go early. Slide requires the slide button to be help down every frame. Attack requires a button press. The last three all have various cooldown timers.

When an Enemy moves, it typically moves towards a patrol point or chases the Player. The logic is not the same. Likewise when it attacks, I use the Hitbox to determine if the player is in range and then make the attack.

When I made the first version of the game, the Enemy AI was really hard to design. But I’m thinking that if I created a Command Pattern to issue the same commands to the Player and Enemy objects, it would not only simplify the number of custom states I need, but also allow for more natural-looking Enemy AI. As a bonus, I think it might solve another problem I’ve been struggling with, which is how to create cutscenes.

One way to do this would be to translate the way the AI works. Instead of moving towards a destination, an Enemy could send the Command to move towards a destination. Just like a plyer would hold down the movement key and decide whether or not to move towards the enemy.

Conclusion

Anyway, just some things I’m working out based on what I read here, and thought I’d share and see if anyone else had any thoughts.

2 Likes

I actually use the command pattern for input handling in this game! The player and the NPCs have some functions that can move them around, but they have no knowledge of the Inputs at all. I can simply put an InputHandler node under either the player OR any NPC to be able to move them around, since all that does is give back a command based on inputs:

public partial class InputHandler : Node
{
    public bool IsRunHeld => Input.IsActionPressed("Sprint");
    
    public ICommand HandleInput()
    {
        if (Input.IsActionPressed("MoveUp"))
        {
            return new Move(Vector2I.Up);
        }

        if (Input.IsActionPressed("MoveDown"))
        {
            return new Move(Vector2I.Down);
        }

        if (Input.IsActionPressed("MoveLeft"))
        {
            return new Move(Vector2I.Left);
        }

        if (Input.IsActionPressed("MoveRight"))
        {
            return new Move(Vector2I.Right);
        }

        if (Input.IsActionJustPressed("Interact"))
        {
            return new Interact();
        }
        
        if (Input.IsActionJustPressed("ClickMove"))
        {
            Vector2 targetPos = GetViewport().GetMousePosition();
            return new FollowPath(targetPos);
        }

        return null;
    }
}

And as an example, this is what the Move command looks like:

public class Move : ICommand
{
    private readonly Vector2I _direction;

    public Move(Vector2I direction)
    {
        _direction = direction;
    }

    public void Execute(Node actor)
    {
        if (actor is IGridAction { IsMoving: false } gridAction)
        {
            gridAction.TryGridMove(_direction);
        }
    }
}
2 Likes

It’s funny, I thought about an InputHandler type Node, but then I decided to extend the movement classes for the Player. But you’re right, I could also do an AI Node and have that create the inputs for NPCs and Enemies. That would mean that they wouldn’t need separate scenes perhaps.

1 Like