Instiating a Generic Node

Godot 4

How do I properly instantiate a generic class?

Hi, I’m very new to Godot, but I’ve got a little bit of experience when it comes to programming otherwise and I’ve been trying to use C# to make use of Interfaces and Generics. I noticed however, that when I wanted to use a generic class as a node, that I can’t properly use GetNode<>() with it. Here’s what I mean:

This is my generic class:

public partial class StateMachine<T> : Node where T : Node, IState<T>

where IState<T> is an Interface for states used in the state machine. Here’s the code for completion:

public interface IState<T> where T : IState<T>

It will be used to create groups of states like this:

public abstract partial class PlayerState : Node, IState<PlayerState>
{
    public Player Player { get; set; }
    public VelocityComponent Velocity { get; set; }

This way I have functions defined which all states need and can add custom properties to every state of a specific state machine using abstract classes.

I hope I’ve painted the picture of what kind of architecture I’m trying to build. This all works fine, but now comes the part, which gives me some headaches:

When trying to use the expression

GetNode<StateMachine<PlayerState>>("StateMachine")

godot tells me during runtime: “ERROR: System.InvalidCastException: Unable to cast object of type ‘Godot.Node’ to type ‘StateMachine`1[PlayerState]’.”, which makes total sense, because the particular instance of StateMachine<T> is not instantiated as a StateMachine<PlayerState>.

Is there any way to change how the class is first instantiated?

My first workaround was replacing the node using ReplaceBy():

public static T ReplaceNodeByGeneric<T>(NodePath nodePath, Node currentNode) where T : Node, new()
{
	var oldNode = currentNode.GetNode(nodePath);
	oldNode.QueueFree();

	var newNode = new T
	{
		Name = nodePath.GetName(nodePath.GetNameCount() - 1)
	};

	currentNode.GetNode(nodePath).ReplaceBy(newNode);

	return newNode;
}

which is called in the parent node, but this seems very much not like a good idea for many reasons.

PS:

While writing this I realized that I can create an empty class which inherits from a specificly typed StateMachine

public partial class PlayerStateMachine : StateMachine<PlayerState>
{
}

and attach it to my StateMachine node. However, this means I still can’t use exported variables, but I have a hunch that that’s impossible as long as StateMachine is generic.

I’m still curios if there is another way to go about this or if even my entire approach is a bad idea. As I said, I’m pretty new to Godot and game engines/development in general, but I wanna have a solid architecture when it comes to stuff like state machines and dependency injection.

I appreciate any tips, ideas and resources! <3

I don’t know much about C# but a decent bit about godot. What’s wrong with using overrides?

Well If I got it to work the way I want, I would never have to touch StateMachine<T> again (unless I wanna add new functionality of course).

By not using generics and deriving other classes from StateMachine and overriding the methods, I would need to write code every time I use StateMachine in a different context, which seems a bit unnecessary to me, if the functionality is basically the same across all usecases. Only the types differ, hence the generic.

I could do it that way, but I’d prefer not to, if there’s a way around it

Solved it myself! Inheriting from StateMachine<T> with a specific T turns out to be a good solution as I found out, that I can declare properties to be abstract:

public abstract partial class StateMachine<T> : Node where T : Node, IState<T>
{
	public abstract T StartState { get; set; }

This way I force the derived class to implement the property and can also export it with minimal extra code:

public partial class PlayerStateMachine : StateMachine<PlayerState>
{
    [Export]
    public override PlayerState StartState { get; set; }
}

So any new state machine I wanna add will only take me a couple lines of code to set up with exportable properties despite using mostly type generic code.

2 Likes