Saturday, January 2, 2016

c# - Using component based entity system practically


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(Attribute.Health); // owner's attribute
brickHealth -= otherEntity.GetAttribute(Attribute.DamageImpact);
SetAttribute(Attribute.Health, brickHealth); // owner's attribute

// If health is <= 0, "destroy" the brick
if (brickHealth <= 0)
SetAttribute(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(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.



Answer



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 attribs, EventDispatcher* events)

// For the purposes of this example, I'll assume that the attributes I
// receive are the right ones.
: health_(static_cast(attribs["health"])),
armor_(static_cast(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(),
std::bind(&SomeBehavior::onDamageEvent, this, _1));
}

void onDamageEvent(std::shared_ptr e)
{
DamageEvent* damage = boost::polymorphic_downcast(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(new HealthCriticalEvent())
entity_->events().post(ev)
}
}

private:
double current_, max_, critical_;
};

No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...