23
\$\begingroup\$

I've been interested in the component based entity system for a while, and read countless articles on it (The Insomiac games, the pretty standard Evolve Your Hierarchy, the T-Machine, Chronoclast ... just to name a few).

They all seem to have a structure on the outside of something like:

Entity e = Entity.Create();
e.AddComponent(RenderComponent, ...);
//do lots of stuff
e.GetComponent<PositionComponent>(...).SetPos(4, 5, 6);

And if you bring in the idea of shared data (this is the best design I've seen so far, in terms of not duplicating data everywhere)

e.GetProperty<string>("Name").Value = "blah";

Yes, this is very efficient. However, it isn't exactly the easiest to read or write; it feels very clunky and working-against-you.

I personally would like to do something like:

e.SetPosition(4, 5, 6);
e.Name = "Blah";

Though of course the only way to get to that kind of design is back in the Entity->NPC->Enemy->FlyingEnemy->FlyingEnemyWithAHatOn kind of hierarchy this design tries to avoid.

Has anyone seen a design for this kind of component system which is still flexible, yet maintains a level of user friendliness? And for that matter, manages to get around the (probably the hardest problem) data storage in a good way?

What designs are there for a component based entity system that are user friendly but still flexible?

\$\endgroup\$

6 Answers 6

13
\$\begingroup\$

One of the things that Unity does is provide some helper accessors on the parent game object to provide a more user friendly access to common components.

For example, you might have your position stored in a Transform component. Using your example you would have to write something like

e.GetComponent<Transform>().position = new Vector3( whatever );

But in Unity, that gets simplified to

e.transform.position = ....;

Where transform is literally just a simple helper method in the base GameObject class (Entity class in your case) that does

Transform transform
{ 
    get { return this.GetComponent<Transform>(); }
}

Unity also does a few other things, like setting a "Name" property on the game object itself instead of in its child components.

Personally I don't like the idea of your shared-data-by-name design. Accessing properties by name instead of by a variable and having the user also have to know what type it is just seems really error prone to me. What Unity does is that they use similar methods as the GameObject transform property within the Component classes to access sibling components. So any arbitrary component you write can simply do this:

var myPos = this.transform.position;

To access the position. Where that transform property does something like this

Transform transform
{
    get { return this.gameObject.GetComponent<Transform>(); }
}

Sure, it's a little more verbose than just saying e.position = whatever, but you get used to it, and it doesn't look as nasty as the generic properties. And yes, you would have to do it the roundabout way for your client components, but the idea is that all your common "engine" components (renderers, colliders, audio sources, transforms, etc) have the easy accessors.

\$\endgroup\$
5
  • \$\begingroup\$ I can see why the shared-data-by-name system may be an issue, but how else could I avoid the problem of multiple components needing data (i.e. which one do I store something in)? Just being very careful at the design stage? \$\endgroup\$ Commented May 21, 2011 at 8:37
  • \$\begingroup\$ Also, in terms of the 'having the user also have to know what type it is', couldn't I just cast it to property.GetType() in the get() method? \$\endgroup\$ Commented May 21, 2011 at 10:01
  • 1
    \$\begingroup\$ What kind of global (in entity scope) data are you pushing to these shared data repositories? Any kind of game object data should be easily accessible through your "engine" classes (transform, colliders, renderers, etc). If you're making game logic changes based on this "shared data", then it's a lot safer to have one class own it and actually make sure that component is on that game object than just blindly asking to see if some named variable exists. So yes, you should handle that in the design phase. You could always make a component that just stores data if you really need it. \$\endgroup\$
    – Tetrad
    Commented May 21, 2011 at 19:45
  • \$\begingroup\$ @Tetrad - Can you elaborate briefly on how your proposed GetComponent<Transform> method would work? Would it loop through all of the GameObject's Components and return the first Component of type T? Or is something else going on there? \$\endgroup\$
    – Michael
    Commented Mar 2, 2012 at 6:46
  • \$\begingroup\$ @Michael that would work. You could just make it a responsibility of the caller to cache that locally as a performance improvement. \$\endgroup\$
    – Tetrad
    Commented Mar 2, 2012 at 16:25
3
\$\begingroup\$

I would suggest some kind of Interface class for your Entity objects would be nice. It could do the error handling with checking to make sure an entity contains a component of the appropriate value as well in one location so you would not have to do it everywhere you access the values. Alternately, most of the designs I have ever done with a component based system, I deal with the components directly, requesting, for example, their positional component and then accessing/updating the properties of that component directly.

Very basic at a high level, the class takes in the entity in question and provides easy-to-use interface to the underlying component parts as follows:

public class EntityInterface
{
    private Entity entity { get; set };
    public EntityInterface(Entity e)
    {
        entity = e;
    }

    public Vector3 Position
    {
        get
        {
            return entity.GetProperty<Vector3>("Position");
        }
        set
        {
            entity.GetProperty<Vector3>("Position") = value;
        }
    }
}
\$\endgroup\$
3
  • \$\begingroup\$ If I assume I'd just have to handle for NoPositionComponentException or something, what's the advantage of this over just putting all that in the Entity class? \$\endgroup\$ Commented May 20, 2011 at 17:14
  • \$\begingroup\$ Being unsure what your entity class actually contains I can not say there is an advantage or not. I personally advocate component systems where there is no base entity per-say simply to avoid the question of 'Well what belongs in it'. However I would consider an interface class like this a good argument for it existing. \$\endgroup\$
    – James
    Commented May 20, 2011 at 18:30
  • \$\begingroup\$ I can see how this is easier (I don't have to query the position through GetProperty), but I don't see the advantage of this way instead of placing it in the Entity class itself. \$\endgroup\$ Commented May 20, 2011 at 18:34
2
\$\begingroup\$

In Python you can intercept the 'setPosition' part of e.SetPosition(4,5,6) via declaring a __getattr__ function on Entity. This function can iterate through the components and find the appropriate method or property and return that, so that the function call or assignment goes to the right place. Perhaps C# has a similar system, but it might not be possible due to it being statically typed - it presumably can't compile e.setPosition unless e has setPosition in the interface somewhere.

You could also perhaps make all your entities implement the relevant interfaces for all components you might ever add to them, with stub methods that raise an exception. Then when you call addComponent, you just redirect the entity's functions for that component's interface to the component. A bit fiddly though.

But perhaps easiest would be to overload the [] operator on your Entity class to search the attached components for the presence of a valid property and return that, so it can be assigned to like so: e["Name"] = "blah". Each component will need to implement its own GetPropertyByName and the Entity calls each one in turn until it finds the component responsible for the property in question.

\$\endgroup\$
4
  • \$\begingroup\$ I had thought of using indexers..this is really nice. I shall wait a bit to see what else appears, but I'm veering towards a mix of this, the usual GetComponent(), and some properties like @James suggested for common components. \$\endgroup\$ Commented May 20, 2011 at 17:43
  • \$\begingroup\$ I may be missing what is being said here but this seems more of a replacement for e.GetProperty<type>("NameOfProperty").Whatever with e["NameOfProperty"].Whatever ?? \$\endgroup\$
    – James
    Commented May 20, 2011 at 18:34
  • \$\begingroup\$ @James It's a wrapper for it (Much like your design)..except it's more dynamic - it does it automatically and cleaner than the GetProperty method..Only downside is the string manipulation and comparison. But that's a moot point. \$\endgroup\$ Commented May 20, 2011 at 19:13
  • \$\begingroup\$ Ah, my misunderstanding there. I assumed the GetProperty was part of the entity (And so would do what was described above) and not the C# method. \$\endgroup\$
    – James
    Commented May 20, 2011 at 20:45
2
\$\begingroup\$

To expand on Kylotan's answer, if you're using C# 4.0, you can statically type a variable to be dynamic.

If you inherit from System.Dynamic.DynamicObject, you can override TryGetMember and TrySetMember (among the many virtual methods) to intercept component names and return the requested component.

dynamic entity = EntityRegistry.Entities[1000];
entity.MyComponent.MyValue = 100;

I wrote a bit about entity systems over the years, but I assume I can't compete with the giants. Some ES notes (somewhat outdated and do not necessarily reflect my current understanding of entity systems, but it's worth a read, imho).

\$\endgroup\$
3
  • \$\begingroup\$ Thanks for that, will help :) Wouldn't the dynamic way have the same kind of effect as just casting to property.GetType(), though? \$\endgroup\$ Commented May 21, 2011 at 12:03
  • \$\begingroup\$ There will be a cast at some point, since TryGetMember has an out object parameter - but all of this will be resolved at runtime. I think it's a glorified way to keep a fluent interface over i.e. entity.TryGetComponent(compName, out component). I'm not sure I did understand the question, however :) \$\endgroup\$
    – raine
    Commented May 21, 2011 at 15:33
  • \$\begingroup\$ The question is that I could use dynamic types everywhere, or (AFAICS) returning (property.GetType())property. \$\endgroup\$ Commented May 21, 2011 at 19:35
1
\$\begingroup\$

I see a lot of interest here in ES on C#. Check it out my port of the excellent ES implementation Artemis:

https://github.com/thelinuxlich/artemis_CSharp

Also, a example game to get you started on how it works(using XNA 4): https://github.com/thelinuxlich/starwarrior_CSharp

Suggestions are welcome!

\$\endgroup\$
2
  • \$\begingroup\$ Certainly does look nice. I personally am not a fan of the idea of so many EntityProcessingSystems - in code, how does that work out in terms of use? \$\endgroup\$ Commented Jun 7, 2011 at 18:46
  • \$\begingroup\$ Every System is an Aspect that process its dependent Components. See this to get the idea: github.com/thelinuxlich/starwarrior_CSharp/tree/master/… \$\endgroup\$ Commented Jun 9, 2011 at 20:59
0
\$\begingroup\$

I decided to use C#'s excellent reflection system. I based it off the general component system, plus a mixture of the answers here.

Components are added very easily:

Entity e = new Entity(); //No templates/archetypes yet.
e.AddComponent(new HealthComponent(100));

And can be queried either by name, type, or (if common ones like Health and Position) by properties:

e["Health"] //This, unfortunately, is a bit slower since it requires a dict search
e.GetComponent<HealthComponent>()
e.Health

And removed:

e.RemoveComponent<HealthComponent>();

Data is, again, accessed commonly by properties:

e.MaxHP

Which is stored component-side; less overhead if it doesn't have HP:

public int? MaxHP
{
get
{

    return this.Health.MaxHP; //Error handling returns null if health isn't here. Excluded for clarity
}
set
{
this.Health.MaxHP = value;
}

The large amount of typing is aided by Intellisense in VS, but for the most part there is little typing - the thing I was looking for.

Behind the scenes, I am storing a Dictionary<Type, Component> and having Components override the ToString method for name checking. My original design used primarily strings for names and things, but it became a pig to work with (oh look, I have to cast everywhere as well the lack of easy to access properties).

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .