Page 1 of 2
Wrapping My Head Around a Component-Based Architecture
Posted: Sat Jul 16, 2011 1:39 am
by EddieRingle
UPDATE: The system I describe in this post is outdated, see
this post for my current system.
So I'm back to work on my game engine. One of the first things I wanted to do was to revamp the way entities are handled. For awhile now I _thought_ I had the component ideology down, but it turns out I was way off, and was just using a hierarchy-based system masquerading as a component-based system.
I've done a ton of reading, and want to be sure I have things straight.
When I first read about the component architecture a long, long time ago, the idea of data-driven objects completely flew over my head. I think that tonight, when I realized how critical it was to the architecture, something clicked and I understood everything a whole lot better than I did previously.
So here's my understanding of it all:
- There's a Behavior superclass, which all other behaviors inherit from:
Behavior.h
Code: Select all
enum Behaviors {
PHYSICS,
RENDER,
INPUT,
...
};
class Behavior {
protected:
const char *m_name;
Entity *m_entity;
public:
Behavior(const char *_name, Entity *_entity);
virtual ~Behavior();
virtual const char *GetName();
virtual void Update(float _delta);
static const char *Names[];
}
const char *Behavior::Names[] = {
"physics",
"render",
"input",
...
};
Behavior.cpp
Code: Select all
Behavior::Behavior(const char *_name, Entity *_entity)
: m_name(newStr(_name)),
m_entity(_entity)
{
}
Behavior::~Behavior()
{
m_entity = NULL;
}
const char *Behavior::GetName()
{
return m_name;
}
void Behavior::Update(float _delta)
{
}
- The actual Entity class should be relatively barebones, basically looking something like this:
Entity.h
Code: Select all
class Entity {
protected:
const char *m_name;
Data::HashTable<Behavior *> m_behaviors;
public:
Entity(const char* _name);
virtual ~Entity();
virtual bool HasBehavior(const char *_name);
virtual Behavior *GetBehavior(const char *_name);
virtual bool AddBehavior(const char *_name, Behavior *_behavior);
virtual bool RemoveBehavior(const char *_name);
virtual void Update(float _delta);
virtual void Render(float _delta);
}
Entity.cpp
Code: Select all
Entity::Entity(const char *_name)
: m_name(newStr(_name))
{
}
Entity::~Entity()
{
for (size_t i = 0; i < m_behaviors.used(); i++) {
delete m_behaviors[i];
m_behaviors.erase();
}
}
bool Entity::HasBehavior(const char *_name)
{
return m_behaviors.exists(_name);
}
Behavior *Entity::GetBehavior(const char *_name)
{
return m_behaviors.find(_name);
}
bool Entity::AddBehavior(const char *_name, Behavior *_behavior)
{
return m_behaviors.insert(_name, _behavior) > -1;
}
bool Entity::RemoveBehavior(const char *_name)
{
if (m_behaviors.exists(_name)) {
delete m_behaviors.find(_name);
return m_behaviors.erase(_name);
} else {
return false;
}
}
void Entity::Update(float _delta)
{
for (size_t i = 0; m_behaviors.used(); i++) {
if (strcmp(m_behaviors[i]->GetName(), Behavior::Names[RENDER]) continue;
m_behaviors[i]->Update(_delta);
}
}
void Entity::Render(float _delta)
{
if (HasBehavior(Behavior::Names[RENDER])) {
GetBehavior(Behavior::Names[RENDER])->Update(_delta);
}
}
-
Individual entities are described in text/binary data files written in a description language. So for example, I have a player entity in my game, defined in a lua file:
player.lua:
Code: Select all
name = "player"
behaviors = {
{
name = "physics",
feels_gravity = true
-- other physics behavior fields specific to the player
},
{
name = "render",
texture = "player.png",
-- ...
},
{
name = "input"
triggers = {
"keyup",
"keydown"
}
},
...
}
Now, when I load the entity, I would have the engine parse the lua file and do something like this:
Code: Select all
/* ... dofile player.lua ... */
Entity *e = new Entity(/* pass in name from lua file */);
/* ... loop through behaviors table ... */
/* ... if/else tree deciding what behavior class to use based on behavior's name defined in the lua file, for example, RenderBehavior: ... */
RenderBehavior *b = new RenderBehavior(e);
b->SetTexture(/* pass in texture filename from lua file */);
e->AddBehavior("render", b);
/* ... Add the rest of the behaviors ... */
// Add entity to the scene
g_game->GetScene()->AddEntity(e);
And that's the gist of it. I didn't really intend on writing out whole implementations, but I'm kind of glad I did anyway. One main question I have is: How do Component/BehaviorManagers fit in to everything?
So, how's my understanding? Is it still way off? Almost there? Spot on?
Re: Wrapping My Head Around a Component-Based Architecture
Posted: Sat Jul 16, 2011 8:43 am
by dandymcgee
Some you new guys need to understand something. Looking at that much code in scrolling text boxes with no syntax highlighting isn't comfortable. If you want someone to review an entire project's worth of code, then just post the whole project as an attachment. Make a simple, but interesting little demo program and anyone interested can download it, review it in the familiarity of their IDE, then post back with their opinion.
Re: Wrapping My Head Around a Component-Based Architecture
Posted: Sat Jul 16, 2011 10:18 am
by EddieRingle
dandymcgee wrote:Some you new guys need to understand something. Looking at that much code in scrolling text boxes with no syntax highlighting isn't comfortable. If you want someone to review an entire project's worth of code, then just post the whole project as an attachment. Make a simple, but interesting little demo program and anyone interested can download it, review it in the familiarity of their IDE, then post back with their opinion.
I understand where you're coming from. However, I honestly didn't think it was "that much code." I wrote out a rough implementation to show my understanding of the topic. I wrote the rest of the code because it was 2:30 in the morning, I wanted a basic implementation, and I didn't really care whether it was in a forum post or a text editor. Really, all that I ask is that you look at the header files and the small bit of code at the end, which sums up to be about 45 lines. I figured the source files would be some sort of bonus, or something.
Not trying to be rude or anything, I just wanted opinions on my understanding of the architecture, which I figured people here would be glad to help out with.
Re: Wrapping My Head Around a Component-Based Architecture
Posted: Sat Jul 16, 2011 10:51 am
by dandymcgee
EddieRingle wrote:dandymcgee wrote:Some you new guys need to understand something. Looking at that much code in scrolling text boxes with no syntax highlighting isn't comfortable. If you want someone to review an entire project's worth of code, then just post the whole project as an attachment. Make a simple, but interesting little demo program and anyone interested can download it, review it in the familiarity of their IDE, then post back with their opinion.
I understand where you're coming from. However, I honestly didn't think it was "that much code." I wrote out a rough implementation to show my understanding of the topic. I wrote the rest of the code because it was 2:30 in the morning, I wanted a basic implementation, and I didn't really care whether it was in a forum post or a text editor. Really, all that I ask is that you look at the header files and the small bit of code at the end, which sums up to be about 45 lines. I figured the source files would be some sort of bonus, or something.
Not trying to be rude or anything, I just wanted opinions on my understanding of the architecture, which I figured people here would be glad to help out with.
Yeah, I wasn't trying to be rude either. Just saying more than a few lines of code is pretty hard to look at when it's all green and you can only see a small portion at a time. It's a very open-ended question, which is great to start a discussion but requires anyone replying to look at all the code first so what they say makes sense. That's all I was getting at.
I think what you've got there in the lua would be a pretty good component system. It seems like you've got a pretty good hold on the basic structure of managing attached behaviors. You should go ahead and let this evolve and see what issues you might run across.
Re: Wrapping My Head Around a Component-Based Architecture
Posted: Sat Jul 16, 2011 11:00 am
by EddieRingle
dandymcgee wrote:Yeah, I wasn't trying to be rude either. Just saying more than a few lines of code is pretty hard to look at when it's all green and you can only see a small portion at a time. It's a very open-ended question, which is great to start a discussion but requires anyone replying to look at all the code first so what they say makes sense. That's all I was getting at.
Understood.
dandymcgee wrote:I think what you've got there in the lua would be a pretty good component system. It seems like you've got a pretty good hold on the basic structure of managing attached behaviors. You should go ahead and let this evolve and see what issues you might run across.
I like to hear the reassurance. I would have integrated it into my engine beforehand, but I was getting tired of writing a system and then tearing it down or giving up for awhile because it was basically crap. One thing that probably needs to be added is some sort of attribute management as well, because there are certain things (like health, for example), that really don't have any logic on their own, but instead hold values specific to the entity.
Re: Wrapping My Head Around a Component-Based Architecture
Posted: Sat Jul 16, 2011 7:12 pm
by EccentricDuck
What you've got there looks really good. You've definitely got the bones of a good entity system.
EddieRingle wrote:One main question I have is: How do Component/BehaviorManagers fit in to everything?
Part of this really comes down to you how you orient your system. Is most of the logic within the behavior classes, or is most of the logic within the systems classes (which basically just looks at the behaviors as fancy identifiers with a few methods). The way you have it is that most of the logic is within the behavior classes (hence the update loops). Both ways are fine but they appeal to different architectures.
Having a more system driven architecture is better on a large scale because you're essentially just serializing/deserializing lists of attached behaviors which are easy to store and send. It's seems like the ideal MMO architecture:
http://t-machine.org/index.php/2007/09/ ... nt-part-1/
With such a system, you're treating the entities and their attached components/behaviors more like a relational database than you are like an object-oriented system (although the managers/systems are fully object-oriented).
An architecture that has the logic inside the components is simpler and would allow you to more easily add behaviors via scripting. What you have there seems to be a good way of handling it.
I'm pretty sure that the ES engine uses a combination of the two. Commonly used behaviors are defined within the engine in the systems, but scripted behaviors can be added and called via an update loop that all scripted components have. This gives them a combination of speed (for the commonly used behaviors defined in the engine) as well as making the act of creating new behaviors easy. I don't know how it is for serialization with the two separate systems.
Re: Wrapping My Head Around a Component-Based Architecture
Posted: Mon Jul 18, 2011 5:52 pm
by EddieRingle
I spent the entire day yesterday redesigning the entity system, as it was pretty inflexible in my opinion and incurred a lot of unnecessary overhead. There's no longer a base Component class to extend from, for one thing. Here's how the system is laid out now:
- Properties - Renamed from "Attributes," each one is no longer a class. Instead, a property can be a value of many types. You'll see how this works when I explain the refactored Entity class.
- Behaviors - Like properties, there is no longer a class for each behavior. Rather, it is a C function that takes a pointer to an Entity and the time since the last frame as parameters. Example:
Code: Select all
void behavior_render(Entity *_entity, float _delta) {
const char *spritesheet = NULL;
if (_entity->GetProperty("spritesheet", spritesheet)) {
g_graphics->LoadTexture(spritesheet);
}
g_graphics->DrawEntity(_entity);
if (spritesheet != NULL) {
g_graphics->UnloadTexture();
}
}
- The Entity class (full source) - The link will send you to a GitHub Gist containing the source to the header, since I know syntax highlighting is preferred. Basically contains a hashmap of properties and a hashmap of pointers to behavior functions. You can see what I give Lua access to, as well. I give access to SetProperty and GetProperty as well, but I sent Lua my own functions to handle them to allow for more dynamic behavior.
- Entity definition files (.lua) - In case you were wondering, the structure for the Entity definition files looks something like this:
Code: Select all
name = "darwinian" -- Yes, I'm using a darwinian sprite to test :3
properties = {
width = 31,
height = 32,
spritesheet = "darwinian.png"
}
behaviors = {
"input",
"physics",
"render"
}
function onKeyDown(key)
-- Do stuff based on what key was pressed --
end
function onKeyUp(key)
-- Do stuff based on what key was released --
end
function onMouseDown(btn)
-- Do stuff based on what mouse button was clicked --
end
Handling Input
As seen in the entity definition lua code above, I defined some input-specific event functions. When parsing the entity file, I check to see if various functions have been defined in the file, and register with the Input system for the entity to be notified of the functions' corresponding events. For functions that deal with the mouse, I check to make sure the click was actually performed on the entity before sending it to Lua. I think I'll also do something similar for key presses, so the lua function only gets executed when a key it's registered to has changed state. I'm not sure how often this feature should be used in games, as calling lua functions from c incurs overhead, but it's nice to have. Perhaps a better route would be to send off input events to behaviors.
Overall, I really like the way things are laid out now. I don't have to deal with any sort of message passing, as properties define how behaviors will act. I can easily add new behaviors through C or Lua now, as well, which is nice. If anyone has suggestions for improving this design, let me know.
Re: Wrapping My Head Around a Component-Based Architecture
Posted: Mon Jul 18, 2011 6:14 pm
by XianForce
The only potential issue I see with your design... What happens when you need triggers of some sort? How would you fit those in at all?
Re: Wrapping My Head Around a Component-Based Architecture
Posted: Mon Jul 18, 2011 6:20 pm
by EddieRingle
XianForce wrote:The only potential issue I see with your design... What happens when you need triggers of some sort? How would you fit those in at all?
What sort of triggers?
Re: Wrapping My Head Around a Component-Based Architecture
Posted: Mon Jul 18, 2011 8:07 pm
by XianForce
EddieRingle wrote:XianForce wrote:The only potential issue I see with your design... What happens when you need triggers of some sort? How would you fit those in at all?
What sort of triggers?
When two objects collide, you obviously push one of the objects back so they aren't colliding, but for most collisions, something other than that happens... It's usually something that will be different for each type of object. Some collisions might harm the player (with enemies) some might heal the player (health items?) some may teleport the player? There's just plenty of things like that where initiating a trigger is useful. Depending on the type of game you make, will determine the types of triggers you need.
Re: Wrapping My Head Around a Component-Based Architecture
Posted: Mon Jul 18, 2011 8:56 pm
by EddieRingle
XianForce wrote:EddieRingle wrote:XianForce wrote:The only potential issue I see with your design... What happens when you need triggers of some sort? How would you fit those in at all?
What sort of triggers?
When two objects collide, you obviously push one of the objects back so they aren't colliding, but for most collisions, something other than that happens... It's usually something that will be different for each type of object. Some collisions might harm the player (with enemies) some might heal the player (health items?) some may teleport the player? There's just plenty of things like that where initiating a trigger is useful. Depending on the type of game you make, will determine the types of triggers you need.
Call the onCollide lua function in the script of the entity the player has collided with, passing the player entity as a parameter? (Not sure how expensive that would be, though)
A similar route can be taken when an entity is "used" (e.g., a potion).
Re: Wrapping My Head Around a Component-Based Architecture
Posted: Mon Jul 18, 2011 9:24 pm
by EccentricDuck
XianForce wrote:EddieRingle wrote:XianForce wrote:The only potential issue I see with your design... What happens when you need triggers of some sort? How would you fit those in at all?
What sort of triggers?
When two objects collide, you obviously push one of the objects back so they aren't colliding, but for most collisions, something other than that happens... It's usually something that will be different for each type of object. Some collisions might harm the player (with enemies) some might heal the player (health items?) some may teleport the player? There's just plenty of things like that where initiating a trigger is useful. Depending on the type of game you make, will determine the types of triggers you need.
You just handle that by having a component that corresponds with different results. A trigger could be nothing but an entity with no model that has collision, and it could have a warp component, or a sound component (playing a sound cue), or a spawn enemies component. Triggers are actually really simple with an entity system. You just load an entity with a particular component or two and have them do their thing. Maybe they're collision based, maybe they act on a timer, or maybe they're some combination of both. All of the above can easily be achieved simply by attaching the appropriate components. That's the beauty of it.
Re: Wrapping My Head Around a Component-Based Architecture
Posted: Mon Jul 18, 2011 10:03 pm
by XianForce
@EccentricDuck - I know that... I'm talking specifically about his implementation of "components" simply being function pointers. With how dynamic his system is, it's unlikely that he'll have systems to manage different behavior types. Then when you have a collision occur, and you want to access some type of "collision response" component, how will that happen? I'm not saying it's impossible, that's just a probable roadblock I see.
Re: Wrapping My Head Around a Component-Based Architecture
Posted: Mon Jul 18, 2011 10:25 pm
by EddieRingle
XianForce wrote:@EccentricDuck - I know that... I'm talking specifically about his implementation of "components" simply being function pointers. With how dynamic his system is, it's unlikely that he'll have systems to manage different behavior types. Then when you have a collision occur, and you want to access some type of "collision response" component, how will that happen? I'm not saying it's impossible, that's just a probable roadblock I see.
In my system, there would never be a "collision response" component. Whatever behavior is checking for collisions (if I have the behaviors doing that all, that might even be handled elsewhere), should fix the entity's position (if the entity collided with a solid object), and execute the appropriate lua function (if it exists).
Re: Wrapping My Head Around a Component-Based Architecture
Posted: Tue Jul 19, 2011 12:41 am
by XianForce
EddieRingle wrote:XianForce wrote:@EccentricDuck - I know that... I'm talking specifically about his implementation of "components" simply being function pointers. With how dynamic his system is, it's unlikely that he'll have systems to manage different behavior types. Then when you have a collision occur, and you want to access some type of "collision response" component, how will that happen? I'm not saying it's impossible, that's just a probable roadblock I see.
In my system, there would never be a "collision response" component. Whatever behavior is checking for collisions (if I have the behaviors doing that all, that might even be handled elsewhere), should fix the entity's position (if the entity collided with a solid object), and execute the appropriate lua function (if it exists).
The fact that you said it would "execute the appropriate lua function", shows that it IS a behavior, by the way you've defined your system...? (I was using component/behavior interchangeably)