Component-based Entities using Properties

Published October 26, 2009
Advertisement

Component-based Entities using Properties



Introduction
This is my second public design for using compoents with entities to prevent deep hierarchy problems and make the game object difinition more data-driven. By data-driven, I mean that you can define your game objects in for example XML.

Third party dependencies
- ClanLib (http://www.clanlib.org)

Some history
Over the last couple years I've been doing a lot of thinking on component-based entities. I really liked the idea of approaching the definition of entities this way. It chuncks up logic in the game nicely, makes it easy to write small scripted chuncks of logic, it's data-driven to the point where you could define entierly new game objects in for example XML.

The last couple of books from the Game Programming Gems series have covered this topic with different approaches, but the approach I'm suggesting here have not been discussed before to my knowledge; -using properties to add the glue between components!

During my time designing these component-based entity systems, I discovered that most of the time, if not all the time, when one component needed to communicate with another component, this was either to change some data, or that someone else had to change some data.

There was a problem here, because often you'd find that a component owning a specific data, (like, you'd want the movement component, to own the data for positioning your entities) had to inform other components about changing data, or change the state of data on other components' request.

Like with the MovementComponent example, it turns out a ton of components would like to know about the Position of their entity. Your dependency chain between components starts to weave and wrap around your logic, the more components depend on this common data that one component owns, the messier it gets, until you're left with an unmanagable pile of garbage code of inter-dependencies!

This raised the idea one day, to let the entities own all the data! Let components add data to their parent entity, let them change it, and let all components in an entity have the option to react to data changing!

This approach allowed components to be written totaly unaware of any other components in the mix. It only cares for it's entity and the properties it want to manipulate with it's logic.

The current version
The current version of the component-based entity system using properties, does not contain any specialized components (like those that can be defined from script) or any system for defining game objects in XML. Those systems should be fairly simple to extend the engine with however, and could (and should) be written externally from the EntityEngine library.

The source-code
http://svn2.xp-dev.com/svn/Trefall-ComponentSystem/Source

HOW TO/ FAQ
1)What is a game object?
- A Game Engine side object that inherits from Entity in Entity engine,
to wrap the Entity engine functionality, and add additional Game Engine
specific functionality to the Game Engine's Game Objects.

2)What is an Entity?
- A wrapper of ComponentContainer and PropertyContainer.

3)What is a ComponentContainer?
- Holds a map of components, and robust/secure methods for adding and manipulating components.

4)What is a PropertyContainer?
- Holds a map of properties, and robust/secure methods for adding/removing and manipulating properties.

5)What is a Property?
- Entity Engine's secure variable for data storage, including a smart pointer. It's owned by it's
ComponentContainer/Entity. It serves as glue between components, as components can listen for an altered
property and act upon such changes. Two components can work on the same property (as Entity is the owner).
This allows multiple components to work on the Position property of an Entity for instance, without them
ever knowing about each other.

6)What is a Component?
- Entity Engine's modular chuncks of logic for entities. Instead of using inheritance to define new entity
types, a modular approach is used. This seperates logic cleanly, makes extending objects simple and the
definition of a new game object data driven. A new game object definition, is simply about defining which
components it holds.

7)How to register new components with the entity engine?
entityManager.GetComponentFactory().RegisterComponent(ExamineComponent::GetType(), &ExamineComponent::Create);entityManager.GetComponentFactory().RegisterComponent(MovementComponent::GetType(), &MovementComponent::Create);


8)How to attach a component to a game object?
- Some initialization method for a zone:

GameObject *objectRobot = new GameObject(entityManager, zone);objectRobot->AddComponent("Examine");objectRobot->AddComponent("Movement");objectRobot->GetProperty("Description") = "It's a useless robot!";objectRobot->GetProperty<float>("MovementSpeed") = 50.0f;


9)How to write a new component from scratch
- ExamineComponent.h
---------------------------

#pragma once#include #include namespace EntityEngine { class Entity; }class ServerPlayer;class ExamineComponent : public EntityEngine::Component{public:	ExamineComponent(EntityEngine::Entity* entity, const CL_String &name);	virtual ~ExamineComponent() {}	virtual void RequestCommands(std::vector &requestCommands, ServerPlayer *player);	virtual void ExecuteCommand(const CL_String &command, ServerPlayer *player);	static CL_String GetType() { return "Examine"; }	static EntityEngine::Component* Create(EntityEngine::Entity* entity, const CL_String &name) { return new ExamineComponent(entity, name); }protected:	EntityEngine::Property description;	void OnExamine(ServerPlayer *player);};

----------------------------
- ExamineComponent.cpp
----------------------------

#include "precomp.h"#include "ExamineComponent.h"#include "../GameObject.h"#include "../ServerPlayer.h"#include #include using namespace EntityEngine;ExamineComponent::ExamineComponent(Entity* entity, const CL_String &name): Component(entity, name){	description = entity->AddProperty("Description", CL_String());}void ExamineComponent::RequestCommands(std::vector &requestCommands, ServerPlayer *player){	requestCommands.push_back("Examine");}void ExamineComponent::ExecuteCommand(const CL_String &command, ServerPlayer *player){	if(command == "Examine")	{		OnExamine(player);	}}void ExamineComponent::OnExamine(ServerPlayer *player){	GameObject *gameObject = (GameObject *)entity;	gameObject->SendViewEvent(CL_NetGameEvent(STC_OBJECT_DESCRIPTION, description.Get()), player->GetConnection());}

------------------------------------
- MovementComponent.h
------------------------------------
#pragma once#include #include namespace EntityEngine { class Entity; }class MovementComponent : public EntityEngine::Component{public:	MovementComponent(EntityEngine::Entity* entity, const CL_String &name);	virtual ~MovementComponent() {}	virtual void Update(int deltaTime);	static CL_String GetType() { return "Movement"; }	static EntityEngine::Component* Create(EntityEngine::Entity* entity, const CL_String &name) { return new MovementComponent(entity, name); }protected:	EntityEngine::Property<float> movementSpeed;	EntityEngine::Property newDestinationPosition;	std::vector movementDestinations;	CL_Slot slotNewDestinationPositionChanged;	void OnNewDestinationPosition(const CL_Pointf &oldValue, const CL_Pointf &newValue);	void ClearDestinationPositions();	void AddDestinationPosition(const CL_Pointf &position);	void SendDestinationPositions();};

----------------------------------------
- MovementComponent.cpp
----------------------------------------
#include "precomp.h"#include "MovementComponent.h"#include "../GameObject.h"//...#include #include using namespace EntityEngine;MovementComponent::MovementComponent(Entity* entity, const CL_String &name): Component(entity, name){	movementSpeed = entity->AddProperty<float>("MovementSpeed", 100.0f);	newDestinationPosition = entity->AddProperty("NewDestinationPosition", CL_Pointf());	slotNewDestinationPositionChanged = newDestinationPosition.ValueChanged().connect(this, &MovementComponent::OnNewDestinationPosition);	GameObject *gameObject = (GameObject*)entity;	gameObject->AddClientComponentRequirement(GetType());}void MovementComponent::OnNewDestinationPosition(const CL_Pointf &oldValue, const CL_Pointf &newValue){	ClearDestinationPositions();	AddDestinationPosition(newValue);	SendDestinationPositions();}void MovementComponent::Update(int deltaTime){	GameObject *gameObject = (GameObject*)entity;		//... perform some movement logic}void MovementComponent::ClearDestinationPositions(){	movementDestinations.clear();}void MovementComponent::AddDestinationPosition(const CL_Pointf &position){	movementDestinations.push_back(position);}void MovementComponent::SendDestinationPositions(){	CL_NetGameEvent movementDestinationEvent(STC_OBJECT_MOVEMENT_LIST);	for(size_t i = 0; i < movementDestinations.size(); ++i)	{		movementDestinationEvent.add_argument(movementDestinations.x);		movementDestinationEvent.add_argument(movementDestinations.y);	}	GameObject* gameObject = (GameObject*)entity;	gameObject->SendViewEvent(movementDestinationEvent);}
0 likes 4 comments

Comments

Staffan E
Interesting. I'm using a similar approach in my game, that is I let the entity object be responsible for variables shared between components. Structurally, the Entity creates and owns an EntityData object, which basically contains a map from strings to floats. Components that are attached to the Entity gets a reference to the EntityData so they can access the data by name.

However, In my system there is no way for components to notify each other that they changed the shared data, so it relies on components being processed in the right order (movement before collisions, etc.) This is not a problem however because the components are not contained in the entity but kept externally by managers (one for each component type) and associated with the entity by id. Since components are kept by type they can be processed by type and in the right order.

You can read more about it in my journal. Much of my inspiration comes from this forum thread.
November 01, 2009 10:05 AM
Trefall
Thank you, Staffan E, for your reply.

Certainly there are as many approaches to Component systems as there are programmers in the world :D

The approach of letting each component type have their own manager is certainly a much used approach. I've also heard of an approach where the component itself is only a data container, and where the manager for that component type holds all the logic for the component.

That type of approach is a good approach I think, but to me, it lacks a bit of sense to it. When I build my objects, to me it's logical that the objects own their abilities and behavior. So when I was looking into the approach you are describing, I found that it didn't fit with how I wanted to do it. Another problem when you rely on an ordered execution of components, is that every time you add in a new component, split a component's logic in two or more components or when you take out a component, you have to make a consious choice and an analyzis of where your new component fit into the ordered list of execution (or when you take one out, if it doesn't create problems with how things are currently ordered).

I think, though, that there are cases where you don't just want your components to run in order. Some times you want to reverse the order of things, and some times you want to play a bit of ping-pong over data between logic that logically reside in different components, and that's when the ability to communicate between components would come in handy. The trick, however, is to communicate between components without binding dependencies between them. I'm not going to say that these problems will occure in every single project, because as you are saying yourself, your ordered approach has worked just fine for yours.

I've also been striving for making components independent of each other, to the point where they shouldn't care what came before it, or what comes after it or that there's a world beyond itself and its parent Game Object's data. Then as an argument, I always hear the Physics vs Rendering component example. I don't understand why people want to put such core functionality into a component, I really don't.

Sure, if I want something to drive my Game Object's position, I'll make a component for that, but whether that's physics or just pure input that drives the game logic for moving my game object is irrelevant, and I want most, if not all, my Game Objects to be rendered, so why would I want to define a Rendering component for every single, new game object I want to define? That sounds like a tedious and unnecessary job to me.

So, personally, what I like to use components for, and where I see the strength of the component system, is not the core components that will be common for 99.99% of my game objects, there's no good reason to modularize such functionality. Rather I focus components on game logic and game play. This makes sense, because you can have a ton of similar game objects, but where you just want a couple of gameplay bits to differ between them. This is where components come in. You can let your designers define entierly new types of game objects, just by stringing together different modules of components and define data to whichever new property they bring allong. Components can be defined in script, so that your scripters can easily define new game logic and game play.

One thing I've experienced with component systems, is that so many try to do everything with them. But why do that? What benefit do you really get for approaching your entire engine as a collection of components? Why do you need to define core components of your engine in modules, when you're going to require the same set of components every single time you run the engine? When I asked myself this question, my answer was that I really didn't need Rendering to be a component, nor the core physics functionality. That might not be the right answer for everyone, but the point is, why do you really need to make everything into a component? Is it worth it? I'd say, use components where they can really make the benefit and the difference, where they can really shine in their data-driven domain. To me, that's where game logic and gameplay comes in, not core engine functionality.
November 02, 2009 03:33 AM
Trefall
And thank you GDNet staff for mentioning my journal entry on your front page. I feel deeply honored!
November 02, 2009 05:09 AM
Trefall
Just commited a fairly large update to the component system. It breaks backwards compatibility, but holds a lot of cleanup and also adds a type serializer, written by Kenneth Gangstø aka Sphair, which I'm doing this component system with (we use it in our game project). It gets rid of the PropertyManager and EntityManager, so updating over properties now goes through your game object (was done over the PropertyManager before). EntityManager wasn't doing much, so no reason to keep it in there.
November 14, 2009 08:23 AM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement