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);}
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.