59

Yesterday, I've read a presentation from GDC Canada about Attribute / Behaviour entity system and I think it's pretty great. However, I'm not sure how to use it practially, not just in theory. First of all, I'll quickly explain you how this system works.


Each game entity (game object) is composed of attributes (= data, which can be accessed by behaviours, but also by 'external code') and behaviours (= logic, which contain OnUpdate() and OnMessage()). So, for example, in a Breakout clone, each brick would be composed of (example!): PositionAttribute, ColorAttribute, HealthAttribute, RenderableBehaviour, HitBehaviour. The last one could look like this (it's just a non-working example written in C#):

void OnMessage(Message m)
{
    if (m is CollisionMessage) // CollisionMessage is inherited from Message
    {
        Entity otherEntity = m.CollidedWith; // Entity CollisionMessage.CollidedWith
        if (otherEntity.Type = EntityType.Ball) // Collided with ball
        {
            int brickHealth = GetAttribute<int>(Attribute.Health); // owner's attribute
            brickHealth -= otherEntity.GetAttribute<int>(Attribute.DamageImpact);
            SetAttribute<int>(Attribute.Health, brickHealth); // owner's attribute

            // If health is <= 0, "destroy" the brick
            if (brickHealth <= 0)
                SetAttribute<bool>(Attribute.Alive, false);
        }
    }
    else if (m is AttributeChangedMessage) // Some attribute has been changed 'externally'
    {
        if (m.Attribute == Attribute.Health)
        {
            // If health is <= 0, "destroy" the brick
            if (brickHealth <= 0)
                SetAttribute<bool>(Attribute.Alive, false);
        }
    }
}

If you're interested in this system, you can read more here (.ppt).


My question is related to this system, but generally every component based entity system. I've never seen how any of these really work in real computer games, because I can't find any good examples and if I find one, it's not documented, there are no comments and so I don't understand it.

So, what do I want to ask? How to design the behaviours (components). I've read here, on GameDev SE, that the most common mistake is to make many components and simply, "make everything a component". I've read that it's suggested to not do the rendering in a component, but do it outside of it (so instead of RenderableBehaviour, it should maybe be RenderableAttribute, and if an entity has RenderableAttribute set to true, then Renderer (class not related to components, but to the engine itself) should draw it on screen?).

But, how about the behaviours / components? Let's say that I've got a level, and in the level, there's a Entity button, Entity doors and Entity player. When player collides with the button (it's a floor button, which is toggled by pressure), it's pressed. When the button gets pressed, it opens the doors. Well, now how to do it?

I've come up with something like this: the player has got CollisionBehaviour, which checks if player collides with something. If he collides with a button, it sends a CollisionMessage to the button entity. The message will contain all necessary informations: who collided with the button. The button has got ToggleableBehaviour, which will receive CollisionMessage. It'll check who did it collide with and if the weight of that entity is big enough to toggle the button, the button gets toggled. Now, it sets the ToggledAttribute of the button to true. Alright, but what now?

Should the button send another message to all other objects to tell them that it has been toggled? I think that if I did everything like this, I'd have thousands of messages and it'd get pretty messy. So maybe this is better: the doors constantly check if the button which is linked to them is pressed or not, and changes its OpenedAttribute accordingly. But then it means that the doors' OnUpdate() method will be constantly doing something (is it really a problem?).

And the second problem: what if I have more kinds of buttons. One is pressed by pressure, second one is toggled by shoting at it, third one is toggled if water is poured on it, etc. This means that I'll have to have different behaviours, something like this:

Behaviour -> ToggleableBehaviour -> ToggleOnPressureBehaviour
                                 -> ToggleOnShotBehaviour
                                 -> ToggleOnWaterBehaviour

Is this how real games work or am I just stupid? Maybe I could have just one ToggleableBehaviour and it'll behave according to the ButtonTypeAttribute. So if it's a ButtonType.Pressure, it does this, if it's a ButtonType.Shot, it does something else...

So what do I want? I'd like to ask you if I'm doing it right, or I'm just stupid and I didn't understand the point of components. I didn't find any good example of how do the components really work in games, I found just some tutorials describing how to make the component system, but not how to use it.

TomsonTom
  • 703
  • 1
  • 6
  • 8

1 Answers1

46

Components are great, but it can take some time to find a solution that feels good to you. Don't worry, you'll get there. :)

Organizing components

You're pretty much on the right track, I'd say. I'll try to describe the solution in reverse, starting with the door and ending with the switches. My implementation makes heavy use of events; below I describe how you can use events more efficiently so they don't become a problem.

If you have a mechanism for connecting entities between them, I'd have the switch directly notify the door that it has been pressed, then the door can decide what to do.

If you can't connect entities, your solution is pretty close to what I'd do. I'd have the door listen for a generic event (SwitchActivatedEvent, maybe). When the switches get activated, they post this event.

If you have more than one type of switch, I'd have PressureToggle, WaterToggle and a ShotToggle behaviors too, but I'm not sure the base ToggleableBehaviour is any good, so I'd do away with that (unless, of course, you have a good reason for keeping it).

Behaviour -> ToggleOnPressureBehaviour
          -> ToggleOnShotBehaviour
          -> ToggleOnWaterBehaviour

Efficient event handling

As for worrying that there's too many events flying around, there's one thing you could do. Instead of having every component be notified of every single event that occurs, then have the component check if it's the right type of event, here's a different mechanism...

You can have an EventDispatcher with a subscribe method that looks something like this (pseudocode):

EventDispatcher.subscribe(event_type, function)

Then, when you post an event, the dispatcher checks its type and only notifies those functions that have subscribed to that particular type of event. You can implement this as a map that associates types of events with lists of functions.

This way, the system is significantly more efficient: there's a lot less function calls per event, and components can be sure that they received the right type of event and not have to double check.

I've posted a simple implementation of this some time ago on StackOverflow. It's written in Python, but maybe it can still help you:
https://stackoverflow.com/a/7294148/627005

That implementation is quite generic: it works with any kind of function, not just functions from components. If you don't need that, instead of function, you could have a behavior parameter in your subscribe method — the behavior instance that needs to be notified.

Attributes and behaviors

I've come to use attributes and behaviors myself, instead of plain old components. However, from your description of how you'd use the system in a Breakout game, I think you're overdoing it.

I use attributes only when two behaviors need access to the same data. The attribute helps keep the behaviors separate and the dependencies between components (be they attribute or behaviors) do not become entangled, because they follow very simple and clear rules:

  • Attributes do not use any other components (neither other attributes, nor behaviors), they are self sufficient.

  • Behaviors do not use or know about other behaviors. They only know about some of the attributes (those that they strictly need).

When some data is only needed by one and only one of the behaviors, I see no reason to put it in an attribute, I let the behavior hold it.


@heishe's comment

Wouldn't that problem occur with normal components as well?

Anyway, I don't have to check event types because every function is sure to receive the right type of event, always.

Also, the behaviors' dependencies (ie. the attributes that they need) are resolved on construction, so you don't have to go looking for attributes every on every update.

And lastly, I use Python for my game logic code (the engine is in C++, though), so there's no need for casting. Python does its duck-typing thing and everything works fine. But even if I didn't use a language with duck-typing, I'd do this (simplified example):

class SomeBehavior
{
  public:
    SomeBehavior(std::map<std::string, Attribute*> attribs, EventDispatcher* events)
        // For the purposes of this example, I'll assume that the attributes I
        // receive are the right ones. 
        : health_(static_cast<HealthAttribute*>(attribs["health"])),
          armor_(static_cast<ArmorAttribute*>(attribs["armor"]))
    {
        // Boost's polymorphic_downcast would probably be more secure than
        // a static_cast here, but nonetheless...
        // Also, I'd probably use some smart pointers instead of plain
        // old C pointers for the attributes.

        // This is how I'd subscribe a function to a certain type of event.
        // The dispatcher returns a `Subscription` object; the subscription 
        // is alive for as long this object is alive.
        subscription_ = events->subscribe(event::type<DamageEvent>(),
            std::bind(&SomeBehavior::onDamageEvent, this, _1));
    }

    void onDamageEvent(std::shared_ptr<Event> e)
    {
        DamageEvent* damage = boost::polymorphic_downcast<DamageEvent*>(e.get());
        // Simplistic and incorrect formula: health = health - damage + armor
        health_->value(health_->value() - damage->amount() + armor_->protection());
    }

    void update(boost::chrono::duration timePassed)
    {
        // Behaviors also have an `update` function, just like
        // traditional components.
    }

  private:
    HealthAttribute* health_;
    ArmorAttribute* armor_;
    EventDispatcher::Subscription subscription_;
};

Unlike behaviors, attributes don't have any update function — they don't need to, their purpose is to hold data, not to perform complex game logic.

You can still have your attributes perform some simple logic. In this example, a HealthAttribute might ensure that 0 <= value <= max_health is always true. It can also send a HealthCriticalEvent to other components of the same entity when it drops below, say, 25 percent, but it can't perform logic any more complex than that.


Example of an attribute class:

class HealthAttribute : public EntityAttribute
{
  public:
    HealthAttribute(Entity* entity, double max, double critical)
        : max_(max), critical_(critical), current_(max)
    { }

    double value() const {
        return current_;
    }    

    void value(double val)
    {
        // Ensure that 0 <= current <= max 
        if (0 <= val && val <= max_)
            current_ = val;

        // Notify other components belonging to this entity that
        // health is too low.
        if (current_ <= critical_) {
            auto ev = std::shared_ptr<Event>(new HealthCriticalEvent())
            entity_->events().post(ev)
        }
    }

  private:
    double current_, max_, critical_;
};
Paul Manta
  • 3,177
  • 3
  • 30
  • 42
  • Thank you! This is exactly an asnwer I wanted. I also like your idea of EventDispatcher better than simple message passing to all entities. Now, to the last thing you told me: you basically say that Health and DamageImpact don't have to be attributes in this example. So, instead of attributes, they'd be just private variables of the behaviours? That means, that the "DamageImpact" would get passed thru the event? For example EventArgs.DamageImpact? That sounds good... But if I wanted the brick to change colour according to its health, then Health would have to be an attribute, right? Thank you! – TomsonTom Feb 12 '12 at 13:37
  • 2
    @TomsonTom Yes, that's it. Having the events hold whatever data the listeners need to know is a very good solution. – Paul Manta Feb 12 '12 at 13:43
  • 3
    This is a great answer! (as is your pdf) -- When you have a chance, could you elaborate a little on how you handle rendering with this system? This attribute/behavior model is completely new to me, but very intriguing. – Michael Feb 12 '12 at 16:46
  • Well, maybe that is for completely new question, but I'd like to ask the same as Michael, plus how you handle collisions. My best idea is to make a Renderer and CollisionManager, two classes that handles rendering and collisions. Renderer will render all entities with RenderDataAttribute, CollisionManager handles collisions of all entities with CollisionDataAttribute. RenderData contains vertex data, textures, possibly also shaders, etc. CollisionData contains collision mesh, etc. Entities w/o RenderDataAttribute won't be rendered and those w/o CollisionData won't collide. Is it a good idea? – TomsonTom Feb 12 '12 at 16:56
  • @Michael The rendering part is still under development. I've got nothing worthwhile to say yet, unfortunately. I've learned a whole lot from the chapter on animation system from Jason Gregory's Game Engine Architecture, but there's still quite a few things that are not clear to me yet. – Paul Manta Feb 12 '12 at 17:04
  • 1
    @TomsonTom About rendering, see the answer I gave to Michael. As for collisions, I personally took a shortcut. I used a library called Box2D which is pretty easy to use and handles collisions much better than I could. But I don't use the library directly in my game logic code. Every Entity has an EntityBody, which abstracts away all the ugly bits. Behaviors can then read the position from the EntityBody, apply forces to it, use the joints and motors the body has, etc. Having such a high fidelity physics simulation like Box2D certainly brings new challenges, but they are quite fun, imo. – Paul Manta Feb 12 '12 at 17:11
  • @Michael There's nothing really new about attributes and behaviors. Behaviors are components under a different name and attributes are simplified components, suitable for sharing data between behaviors, so behaviors don't become interdependent and hard to manage. – Paul Manta Feb 12 '12 at 17:21
  • Indeed. I just finished a game using my best attempt at an "everything is a component" solution, and while it accomplished a bunch of things nicely, it certainly had its weaknesses. This attribute/behavior model seems like a subtle but important improvement. Now to figure how the heck to do events correctly with a messaging system in C# without overloading my code with strong references and memory leaks :( – Michael Feb 12 '12 at 17:42
  • I'd love to see C++/Java/C# code examples of this. – TravisG Feb 12 '12 at 22:42
  • @heishe Of the component system or of the event system? – Paul Manta Feb 13 '12 at 06:03
  • Component system. – TravisG Feb 13 '12 at 11:23
  • @heishe It's not all that different from a regular component system, the only difference is that you now have two specialized types of components. What is the part that you don't understand? – Paul Manta Feb 13 '12 at 12:55
  • The attributes, I think. The way I understand them now there would be base class called abstractattribute or something and the various Attribute inherit from it. Then at runtime you'd have to resolve everything through casting >> (if message.type == "attack balblabal") AbstractAttribute attrib = static_cast<AbstractAttribute>(myAttributes.get(health_key)); << which seems pretty ugly to me, but is probably the simplest (and possibly the only?) way to resolve this at runtime. – TravisG Feb 13 '12 at 13:12
  • @heishe I added a longer reply to your comment in my answer. – Paul Manta Feb 13 '12 at 13:46
  • Thanks. What exactly are your attributes? Can you show the code for the Attribute and HealthAttribute classes? Looks to me like at the moment an Attribute just has a name and a floating point value, which seems slightly unflexible to me. For example, there might be attributes which don't require a floating point value at all, which seems ugly in the current implementation. – TravisG Feb 13 '12 at 14:35
  • @heishe See my answer again. The base Attribute class is empty. All specific attributes have any kind of members you decide they need, there's absolutely no restrictions. In my case, HeathAttribute would have a current value, a maximum value, and a critical-level value. – Paul Manta Feb 13 '12 at 14:47
  • @Michael If you want to implement the messaging system in C#, I recommend you using custom events. It's quite easy. I didn't try programming this yet, but I think that every entity should have its own EventDispatcher, a class, which will hold all event delegates. And if some Behaviour needs to listen to events of its entity, you can do it like this: Owner.EventDispatcher.OnHit += new EventDispatcher.HitHandler(OnHit); and in public void OnHit(object sender, HitEventArgs data) do what you need to do. – TomsonTom Feb 13 '12 at 15:46
  • @TomsonTom Thanks for the response. I know about .NET events, I'm just worried about garbage collection, strong references, and memory leaks. You can read more here: http://www.codeproject.com/Articles/29922/Weak-Events-in-C – Michael Feb 13 '12 at 21:00
  • @Michael There's nothing more I can tell you than: try it on a small example. However, according to this MSDN blog post, the only real deal is to unregister an event listener before "killing" it. And seriously: when will you need to unregister an event in this system? Only when you remove a behaviour from an entity during run-time. But you will never do it. – TomsonTom Feb 13 '12 at 21:16
  • @Michael If you put an EventDispatcher to each entity (that means: each entity has its own EventDispatcher) and each behaviour listens to events of the owner's EventDispatcher, and you never detach behaviours from entities (why'd you do so?), you can never experience a memory leak. The problem is with garbage collector, that won't collect the removed behaviour, because it's still referenced by the entity. But as soon as you remove the entity, all events are collected as well. You can really experience delegate memory leak only if you forget to remove event listener. – TomsonTom Feb 13 '12 at 22:08
  • That's a good point @TomsonTom. I'm hoping to just keep my event wiring to a minimum for each level, and then remove all entities and hope everything gets cleaned up nicely. Because this weak event reference stuff gets way too complicated :) – Michael Feb 14 '12 at 00:18
  • Remember comments aren't supposed to be used for far-extended discussion. If you think you guys will be discussing this more, please move to a chat room. – Jesse Dorsey Feb 14 '12 at 14:57
  • What a great answer from Paul! This Attribute-Behavior theory is really similar to my library, Artemis C# – thelinuxlich Feb 14 '12 at 19:35
  • 1
    @thelinuxlich So you're the developer of Artemis! :D I've seen the Component/ System scheme referenced a few times on the boards. Our implementations indeed have quite a few similarities. – Paul Manta Feb 14 '12 at 20:03
  • @thelinuxlich and Paul: I might oversimplify this, but I think the two implementations differ in so much that with Artemis, a system is responsible for processing all the entities that contain certain attributes, whereas with Paul Manta's implementation, the Behavior is attached to an entity instance (or the entity instance's attributes). So you will end up with multiple instances of a behavior type.

    These two approaches have interesting side-effects when it comes to the instantiation of behavior / system and attributes. I'm thinking about tooling support.

    – kitsune Jan 09 '13 at 21:18
  • @PaulManta In your solution, you would need to implement a way of dealing with entitiy definitions where a behavior's required attributes are not set. If you write your entity definitions (xml or whatever) you need to remember that a HealthBehavior requires a HealthAttribute and an ArmorAttribute. Not that different from a pure Component approach where an invalid entity definition would also have to be dealt with.

    In Artemis, you basically need to register all systems that could be used by your game. I don't yet have a feeling for the impact of that.

    – kitsune Jan 09 '13 at 21:44
  • @kitsune Isn't that a good thing? Knowing the dependencies between components and having them explicit? – Paul Manta Jan 09 '13 at 22:42
  • @kitsune we've just added support for Aspects on Entity Systems, which can suit many new use cases, check it out: http://thelinuxlich.github.com/artemis_CSharp/ – thelinuxlich Jan 24 '13 at 14:32