Reflection Series – Part 3: Attributes are Magic

Attributes in C# are those things wrapped in “[]” that are sprinkled on classes, properties, or variables. In Unity, I usually use [SerializeField] so that a variable becomes editable in the editor but still remains private or protected in code if I wanted to. There are other attributes, too, like Range, ExecuteInEditMode, HideInInspector, DisallowMultipleComponent, etc. Somewhere in Unity’s processes access these attributes through reflection and executes code that the attribute dictates. The coolest part is you can make your own attributes. The following is a very simple attribute:

[AttributeUsage(AttributeTargets.Property)]
public class PropertyGroup : Attribute {

    private readonly string name;

    public PropertyGroup(string name) {
        this.name = name;
    }

    public string Name {
        get {
            return name;
        }
    }

}

It’s an attribute that can be attached to properties (AttributeTargets.Property). It requires a string name. This is how it can be used:

        ...

        [PropertyGroup("Settings")]
        public string Gender {
            get {
                return gender;
            }

            set {
                this.gender = value;
            }
        }

        [PropertyGroup("Settings")]
        public string Type {
            get {
                return type;
            }

            set {
                this.type = value;
            }
        }

        [PropertyGroup("OrientationIds")]
        public string DownSpriteId {
            get {
                return downSpriteId;
            }

            set {
                this.downSpriteId = value;
            }
        }

        [PropertyGroup("OrientationIds")]
        public string RightSpriteId {
            get {
                return rightSpriteId;
            }

            set {
                this.rightSpriteId = value;
            }
        }

        ...

We use PropertyGroup attribute as a grouping mechanism for our generic editor. What we do is in our editor code, we look for this attribute and collect such properties in their own container before rendering them. There are many ways to access an attribute through reflection. The following is one way:

public static T GetCustomAttribute<T>(PropertyInfo property) where T : Attribute {
    Attribute attribute = Attribute.GetCustomAttribute(property, typeof(T));
    return attribute as T;
}

// Usage
PropertyGroup group = GetCustomAttribute<PropertyGroup>(propertyInfo);

// Do something with the attribute like access its name
List<PropertyInfo> propertyList = GetPropertyList(group.Name);

With this, we can change the look of our editor by just adding attributes to our classes.

GroupedProperties

Other Uses

Persistence

In Part 2 of this series, I discussed an XML writer/reader by reading properties of classes. Aside from this, we also have a property attribute named Persist to selectively write/read properties with such attribute. The attribute also allows specification of a default value. We heavily use this attribute in our persistence system. This is useful for properties that are not present in old save files and you want a default value for them.

    [AttributeUsage(AttributeTargets.Property)]
    public class Persist : Attribute {

        private object defaultValue;

        public Persist() {
        }

        public Persist(object defaultValue) {
            this.defaultValue = defaultValue;
        }

        public object DefaultValue {
            get {
                return defaultValue;
            }
        }

    }

    // Sample usage
    [Persist]
    public float PolledTime {
        get {
            return polledTime;
        }

        set {
            this.polledTime = value;
        }
    }

    [Persist(1)]
    public int Day {
        get {
            return day;
        }

        set {
            this.day = value;
        }
    }

    [Persist(1)]
    public int Year {
        get {
            return year;
        }

        set {
            this.year = value;
        }
    }

    ...

Class Browser

In Part 1, I wrote about loading classes using their class names. As such, we have a lot of different editors where a user can select from a selection of different classes that derive from a single base class. The amount of classes written for these editors can get out of hand so we made a class level property to group classes into their respective domains.

    [AttributeUsage(AttributeTargets.Class)]
    public class Group : Attribute {

        private readonly string name;

        public Group(string name) {
            this.name = name;
        }

        public string Name {
            get {
                return name;
            }
        }

    }

    // Usage
    [Group("Game.CharacterNeeds")]
    public class AddNeedAmount : ComponentAction<CharacterNeeds> {
        ...
    }

    [Group("Game.Task")]
    class GetCurrentTaskEffortPerTile : ComponentAction<TaskDoer> {
    }

With this attribute, we can make our class browsers to look like this:

ClassBrowser

Conclusion

Reflection is a worthy tool to add to your gamedev toolbox. It’s powerful and useful. What I did in this series is probably just the tip of an iceberg. I’ve read about modders who opened up, ripped, and replaced game parts of C# games using reflection. Yikes!

I’m going to end the series for now. I may add to it later. I hope I have convinced you that reflection is a cool toy to learn.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s