Search code examples
c#xna

Sharing Fields in Component-Based Game Design


I am at what I believe to be the last big logical leap before completing my component based game engine in C# using XNA. I have my Entity class and abstract components defined. My issue is arising in my EntityFactory.

When I want to create a new entity I pass in an EntityType enum to a static method in the factory, and it goes through a switch/case statement finding which components to put together. The problem is, I am trying to create a way that the components can share fields with other components in the same Entity without them having access to everything. For instance, if two components have Vector2 fields that represents position, they should both point to the same Vector2.

I can do this by initializing all fields in the entity factory and requiring that they be passed into the constructor of the component (and using ref for the primitives), but this would be extremely hard to maintain, since any time I expanded or changed a component, I would have to rewrite code in the factory in every place that component was used. I really want to avoid this solution, but if I can't find a better way, I will put up with it.

My current solution is to create a wrapper class called Attribute. It contains two fields:

private AttributeType type;
private Object data;

Attribute Type is an enum, representing the purpose of the attribute. So there are entries in the enum of Position, Rotation, Texture, etc.

The EntityFactory creates an empty list of attributes, and passes it to each component constructor. The setField method would be called by the component's constructor rather than initializing the fields. Here is the Attribute class and the setField method.

 public class Attribute
{
    private AttributeType type;
    private Object data;

    public AttributeType Type
    {
        get { return this.type; }
    }
    public Object Data
    {
        get { return this.data; }
    }

    public Attribute(AttributeType type, Object data)
    {
        this.type = type;
        this.data = data;
    }

    public static void setField<T>(List<Attribute> attributeList, AttributeType type, out T field, T defaultValue)
    {
        bool attributeFound = false;
        field = defaultValue;

        foreach (Attribute attribute in attributeList)
        {
            if (attribute.Type == type)
            {
                field = (T)attribute.Data;
                attributeFound = true;
                break;
            }
        }

        if (!attributeFound)
        {
            attributeList.Add(new Attribute(type, field));
        }
    }
}

My problem is when the attribute contains data that is of a primitive type. I considered writing a method in the Attribute class of

public void getData<T>(out T field) { field = this.data; }

however I can't seem to pass in data to the Attribute constructor using ref. I can't make Attribute generic since it wouldn't go into a list. I am just wondering if there is a way to handle value type as well as reference type data, or am I making a logical mistake somewhere in this whole thing.


Solution

  • Snarky version: Congratulations you've reinvented the variable. Badly. Or, at best, a property on an interface.

    Useful version:

    I can see a few issues with your design.

    The first issue is simply that it's complicated. Complication is best avoided unless you have a compelling and existent reason for it (ie: not a "maybe in the future" need). Otherwise YAGNI. You should always try to express concepts directly in code before resorting to creating systems to express those concepts in data (like what I said about reinventing the variable; also consider this).

    But let's assume that you do have a good reason for a component based design...

    Second issue is boxing. Boxing happens anywhere you store a value type (eg: int, float, Vector2, any struct) directly as a reference type (eg: object, IEquatable). Boxed objects are immutable - so every time your position changes, a new boxed object is created. Boxing a variable is (relatively) slow. Boxed objects are stored on the heap - so they are considered during, and can cause, a garbage collection. So the design you propose in your question is going to perform horribly.

    I assume your idea for a component based design is similar to the one explained in this article. Here's a helpful diagram:


    (source: cowboyprogramming.com)

    And that brings me to the third issue: you shouldn't have more than one component that holds a position anyway! (You seem to be going way more granular than you need to, in your design.)

    Basically a component-based design is about reinventing the class, rather than the variable. In a normal design, you might have a "Render" function like this:

    public void Draw()
    {
        spriteBatch.Draw(texture, this.Position, Color.White);
    }
    

    But in a component based design you will have Draw and Position in different classes. Which, by the way, I would have implementing interfaces like:

    interface IRenderComponent { void Draw(); }
    interface IPositionComponent { Vector2 Position { get; set; } }
    

    So how does Draw access Position? Well, you need a way to express this (if you're going to reinvent classes, this is perhaps the most important concept you need to include).

    How would you do this? Here's a rough design idea for you:

    I would make each component class inherit from a Component class with a property Self. I would make Self return some kind of ComposedObject with a mechanism for accessing any of the other components that make up the composed object by interface. So perhaps your render component might look like:

    class SimpleRenderer : Component, IRenderComponent
    {
        public void Draw()
        {
            sb.Draw(texture, Self.Get<IPositionComponent>().Position, Color.White);
        }
    }
    

    (This works similarly to GameServiceContainer (ie: the Game.Services property). The idea here is that no ComposedObject should have more than one instance of each interface. If your number of interfaces is small, ComposedObject need not even use a list - simply store each directly. You could have components that implement more than one interface, though.)

    Now if that is too verbose for you, perhaps you could add a few convenience properties on ComposedObject (or use extension methods) for common pieces of data, like Position, like this:

    public Vector2 Position { get { return Get<IPositionComponent>().Position; } }
    

    Then your draw function could simply be this:

    spriteBatch.Draw(texture, Self.Position, Color.White);