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.

If you have comment, you can post it HN (link can be found at the end of the essay), send me an email at hi AT ruoyusun DOT com or ping me on Twitter @insraq.