Ruoyu Sun's
Thoughts on design, code and venture capital

C# with Godot: Custom Attributes, Reflection and Extension Method

24 Mar 2018

It’s been a long time since I last wrote any serious C# and my last experience was actually surprisingly pleasant (given that I am a long time Linux / Mac user). And I’ve been using Godot game engine for a long time for my games. When Godot 3.0 introduced C# support, I immediately decide to give it a whirl.

Godot, if you are not familiar with, comes with GDScript, which is a Python inspired scripting language for the engine. When writing a scene (think GameObject in Unity), it’s common to write the following code:

var mylabel

func _ready():
    mylabel = get_node("MyLabel")

This basically gets hold of a node, which is commonly used. So GDScript provides a syntactic sugar which basically does the same trick:

onready var mylabel = get_node("MyLabel")

func _ready():
    # No need to write anything here

However, in C# bindings, this is not provided, so you have to do something like this:

public class Char : Node
{
    private Label label1;
    private Label label2;
    private Label label3;
    public override void _Ready()
    {
        label1 = (Label) GetNode("MyLabel1");
        label2 = (Label) GetNode("MyLabel2");
        label3 = (Label) GetNode("MyLabel3");
    }
}

I’ve been thinking of ways to make this less cumbersome. From my very limited C# experience, I think it’s possible to achieve this with a custom attribute, some reflections and extension method:

public class Char : Node
{
    [Node("MyLabel1")] private Label label1;
    [Node("MyLabel2")] private Label label2;
    [Node("MyLabel3")] private Label label3;
    public override void _Ready()
    {
        // Unfortunate this is line is unavoidable 
        // unless you can directly modify the base class, which we can't
        this.WireNodes();
    }
}

So let’s first define our custom attribute.

using System;

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class NodeAttribute : Attribute
{
    public string nodePath;

    public NodeAttribute(string np)
    {
        nodePath = np;
    }
}

After this, we have [Node("nodePath")] custom attribute. But this only defines the meta information: to make use of it, we need a bit more. Extension methods in C# allow us to add methods to an existing type without creating a new derived type or modifying the original type. To achieve this, we can define a static class with a special static method:

using Godot;
using System;
using System.Reflection;

public static class Extensions
{
    public static void WireNodes(this Node node)
    {

    }
}

To retrieve the information from attributes, we need a bit of reflection (I know how you feel, but there’s no way around it):

public static class Extensions
{
    public static void WireNodes(this Node node)
    {
        // Get all fields
        FieldInfo[] info = node
            .GetType()
            .GetFields(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

        foreach (var f in info)
        {
            // Search for Node attribute
            NodeAttribute attr = (NodeAttribute)Attribute.GetCustomAttribute(f, typeof(NodeAttribute));
            if (attr != null)
            {
                // Get the node using NodePath and set the field value
                f.SetValue(node, node.GetNode(attr.nodePath));
            }
        }
    }
}

And there you go. As a static type language, I am pleasantly surprised at the extensibility of C#: it’s relatively easy to extend the language and existing libraries without breaking static typing.