Skip to main content
GameDev.net gamedev.net
🔒 Locked 🎮 Unity

Component-based Entities using Properties

Started by Trefall Oct 26, 2009 at 1:04 PM 23 replies 17.4k views
Original Post
Trefall
Trefall

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<CL_String>("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 <EntityEngine/Component.h>
#include <EntityEngine/Property.h>

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<CL_String> &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<CL_String> description;

	void OnExamine(ServerPlayer *player);
};


---------------------------- - ExamineComponent.cpp ----------------------------
#include "precomp.h"
#include "ExamineComponent.h"
#include "../GameObject.h"
#include "../ServerPlayer.h"
#include <EntityEngine/Entity.h>
#include <NetworkSharedLibrary/NetEvents.h>

using namespace EntityEngine;

ExamineComponent::ExamineComponent(Entity* entity, const CL_String &name)
: Component(entity, name)
{
	description = entity->AddProperty<CL_String>("Description", CL_String());
}

void ExamineComponent::RequestCommands(std::vector<CL_String> &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 <EntityEngine/Component.h>
#include <EntityEngine/Property.h>

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<CL_Pointf> newDestinationPosition;
	std::vector<CL_Pointf> 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 <EntityEngine/Entity.h>
#include <NetworkSharedLibrary/NetEvents.h>

using namespace EntityEngine;

MovementComponent::MovementComponent(Entity* entity, const CL_String &name)
: Component(entity, name)
{
	movementSpeed = entity->AddProperty<float>("MovementSpeed", 100.0f);
	newDestinationPosition = entity->AddProperty<CL_Pointf>("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);
}


[Edited by - Trefall on October 27, 2009 11:05:48 AM]
zyrolasting
zyrolasting
Hey Trefall!

It's nice you want to share something with us, but your post looks to be a candidate for an online journal. It's just that GDNet is very active and this post will not stay at the top for long unless stickied.

Maybe you can throw your code at CodeProject or DevX as an alternative for paying for a GDNet membership and getting a dev journal.

Quote:
Let components add data to their entity, let them change it, and let all components in an entity have the option to react to data changing!


You are implying that components own entities, then imply the exact opposite. May want to edit that.

A little feedback... You are going through something I once went though:
A burning desire to innovate abstract declarations and to network any and all possible combinations thereof.
My friend, this system will burn you out if you are not careful. I did something like this once, and I made things so
complicated I tore down my project. I now know I was being screamingly redundant. Please take this in the spirit it is intended, I am not trying to be condescending.

Note that your engine is really a glorified method of object declaration.
Why write this...

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


When you could write this?

struct Robot{   void Examine();   void Move();   CL_String Description;   float     MovementSpeed;};


We programmers love to experiment, but please note your engine isn't doing anything C++ can't do. We have all the means to declare and define what we want to begin with. If we want a managing object we also write that, as well as any ABCs that can group like objects under a pointer type. Please note one of the LAST things we want to do is to build each and every member of an object one by one each time we want to instantiate it! That's the kind of thing high-level languages hide from us.

It looks like you want run time checking. Here's a little tip: Don't use strings for this. That's a bigger headache than you might think. The sizes are effectively arbitrary and the comparison methods are tedious to type after a while [sick]. I suggest GUIDs or cleverly laid out bit fields queried in a COM-Like fashion. Have a header!

FYI, forgive the virtual inlines. Small methods just shake the inline keyword out of me. [smile]

EDIT: Damn the omission of the backslash in my macros! [crying]
Forgive that too.

//=========================================================================/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *	File: AbstractType.hpp	Date: 2009/09/27	Time: 19:44:30	Description	-----------	An object that offers virtually limitless and accurate	runtime type queries under a strict derivation rule.	Uses GUIDs for unique identification or a 32-bit integer.	It is NOT reccomended to try and mix the two id types	under one project, as certain accomodations are made for each.	#define AT_BITFIELD to use bitfields, else GUID is used.	In this case you must generate a new GUID for every derivation.	NOTES	-----------------------------	Vtable overhead may be vicious. Use sparingly.	Copyright © Sage Lennon Gerard. All Rights Reserved. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *///=========================================================================#pragma once#ifndef __ABSTRACTTYPE_INC__#define __ABSTRACTTYPE_INC__#ifndef AT_BITFIELD#include <rpc.h>typedef GUID ATID;typedef const GUID& inATID;// Comparison functorstruct ATCmp{	inline bool operator()(inATID a, inATID b) const	{ return memcmp(&a,&b,sizeof(ATID)) < 0; }};inline bool operator<(inATID a, inATID b) { return memcmp(&a,&b,sizeof(ATID)) < 0; }// Category and Unit are not really useful when using ATID as unsigned int.inline unsigned short ATIDCategory( inATID a ){	return a.Data2;}inline unsigned short ATIDUnit( inATID a ){	return a.Data3;}#elsetypedef unsigned __int32 ATID;typedef const unsigned __int32 inATID;// Comparison functorstruct ATCmp{	inline bool operator()(inATID a, inATID b) const	{ return a < b; }};inline bool operator<(inATID a, inATID b){	return a < b;}// Category and Unit are only really useful when using ATID as unsigned int.inline unsigned short ATIDCategory( inATID a ){	return (a & 0xffff0000)>>16;}inline unsigned short ATIDUnit( inATID a ){	return (a & 0x0000ffff);}#endif/****************************************************************************AbstractTypeAbstract object that offers COM-Like runtime type queries using minimalisticpolymorphism.*****************************************************************************/struct AbstractType{		inline	AbstractType() {}	virtual inline ~AbstractType() {}	virtual bool	QueryType	( inATID ) const = 0;	virtual inATID	QueryType	() const = 0;};/******************************************************************************Macros to simplify deriving from AbstractType.******************************************************************************//******************************************************************************OFTYPE derives from another derivative of AbstractType.Do NOT use AbstractType itself!******************************************************************************/#define OFTYPE( base_, name_, atid_ ) class _ofType##name_ : public base_ { public: 	inline bool QueryType( inATID i ) const { return (atid_ == i) ? true : base_::QueryType(i); } 	inline inATID QueryType() const { return atid_; } }; class name_ : public _ofType##name_ /******************************************************************************OFNEWTYPE derives from AbstractType ONLY.******************************************************************************/#define OFNEWTYPE( name_, atid_ ) class _ofType##name_ : public AbstractType { public: 	inline bool QueryType( inATID i ) const { return (atid_ == i) ? true : false; } 	inline inATID	QueryType() const { return atid_; } }; class name_ : public _ofType##name_ #endif // __ABSTRACTTYPE_INC__


I hope this helps.
Cheers!
-Zyro
Trefall
Trefall
Thank you for your reply, zyrolasting.

I'll make sure to edit and fix the line that indicates that components own entities, since it's most definately the opposite, which I'm also sure is the impression all readers would sit back with after reading the whole text here.

I understand where you're coming from though, and I also understand that we like to approach defining game objects in two different ways. In fact, the code example I showed:

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


is only the hardcoded way of defining this robot object, while in fact the whole point with a component-based approach is to make object definition data-driven. Like I said in the text, it wouldn't take a lot of programming effort to allow a designer to define new object types via XML.

<object type="Robot">  <components>    <component>Examine</component>    <component>Movement</component>  </components>  <properties>    <property name="Description">It's a useless robot!</property>    <property name="MovementSpeed">50.0</property>  </properties></object>


Imagine how simple it would be to create a flavourish graphical UI that would allow designers to string together components and edit the available properties? No recompilation, no nothing, your designers can define completely new object types 100% outside sourcecode.

Now, it also takes little effort to expose the required functionality to a scripting language, that would allow you to define new component types in script. Now scripters could define new game logic, and immediately test this new logic without ever having to recompile the game.

So, I'm very excited about the possibilities of this approach, because I don't want to shut down the server, recompile the game and run the server again every time I make a change to it's data or it's logic.

I think that every game have different requirements, and for some projects this component-based approach can be a real benefit.

There's a problem with component-based designs, however, and that is that there probably are as many ways to approach it as there are programmers in the world. This is great in some ways, because it makes this a topic of endless discussion. There have been some clear disadvantages with using components, like dependency chains between components (if you're going to use this component, then you have to use this and that, and those components too)... I'm quite excited about the fact that this does not apply to my approach, since there's no dependency between components. Multiple components may have a common data type stored in the Entity that they use, but this is perfectly fine. Just make sure your components react to when it changes...

Another drawback is that you have to iterate over a container of components every time you're updating each entity. This is a sacrifice you'd make for getting the possibilities of the data-driven and modular approach.

Quote:
but please note your engine isn't doing anything C++ can't do. We have all the means to declare and define what we want to begin with. If we want a managing object we also write that, as well as any ABCs that can group like objects under a pointer type. Please note one of the LAST things we want to do is to build each and every member of an object one by one each time we want to instantiate it! That's the kind of thing high-level languages hide from us.


You're totaly right. Like I said, my approach is for those who'd like to make object definition data-driven, because they are working on a project where such an approach would save their team time. And you're absolutely right. You don't want to define every single module and give every single property data every single time you're instanciating an object. Like I explained above to my best ability, you do the definition in XML, from there on you'd just want to do something like

entityFactory->Create("Robot");


or more likely, through some scripting language like

entityFactory:Create("Robot")


Certainly my code does not contain this functionality yet, so you're more than right to point this lack of functionality out. But neither does the open source code here give you XML or Scripting support. You could even do a ton of optimization on that codebase if you'd want to.

I'll be updating and improving this code throughout it's life in the project I'm currently working on. When script support arrives, I'll open up that sourcecode too, when XML support arrives I'll open that up too and when we're in the optimization stage, the sourcecode will surely be updated.

Getting through enums/integers instead of strings is certainly a good optimization. We haven't bothered with this yet, since our game hasn't really required it yet. Same thing goes for using std::map instead of a hashmap for instance... I'm also sure we could approach our virtual methods a bit differently, reducing the number of virtuals.

I appreciate your input. The component-based approach seems to always be a source for a lot of discussion and opinions :)
zyrolasting
zyrolasting
Quote:
Imagine how simple it would be to create a flavourish graphical UI that would allow designers to string together components and edit the available properties? No recompilation, no nothing, your designers can define completely new object types 100% outside sourcecode.

Now, it also takes little effort to expose the required functionality to a scripting language, that would allow you to define new component types in script. Now scripters could define new game logic, and immediately test this new logic without ever having to recompile the game.


It's good in theory. The only problem I'm having with this is that you are not diving to a low enough level to encompass an audience that can change their standards practically overnight. You are trying to make an ambiguous system with a bit of an obstacle course, including a serious danger of code bloat with those templates of yours.

I'm just skimming here so I may have missed it, but I do not see anything that protects the user from choosing non-existent components or properties. If you have that logic, fine, but one thing that's bugging me is that everything you are doing here can be pushed down to a series of byte arrays, which would be more future proof and slightly easier to visualize since you are just treating abstract data as just that... A mindless assortment of 1s and 0s. Each byte array is another object, and any queries to a certain component/piece/whatever would just be a certain number of bytes offset from the beginning of a certain array. At first glance it may require less of the headache of navigating through everything. Just a thought.

But I digress... I just feel this could be simpler. You could go more in the direction Autodesk Maya's attribute memory reminds me of. An entity can only be aware of so many of it's own properties in Update(), but others it can just be at the whim of to other objects in the scene.

#include <string>#include <map>#ifdef _UNICODEtypedef std::wstring property;#elsetypedef std::string property;#endifinline bool propCmp(const property& a, const property& b){	return a.compare(b) < 0;}typedef std::map<property,size_t,propCmp> Properties;/******************************************************************************ABC with property map.******************************************************************************/class Entity{	Properties m_Properties;public:			 Entity();	virtual ~Entity() { m_Properties.clear(); }	virtual bool Update( const float dt );	void SetProperty( const property& name, const size_t value ); // 	int  GetProperty( const property& name ) const; // Return -1 on non-existing};


Just take my word for it, the motivation you have is wonderful, but the approach your taking is so much like one I took that caused me so much grief in the end. I just feel I have to warn you not to try and grab so many "potentially useful" abstract options in a pool of... completely abstract options. It looks like you have a system with too few absolutes that prevent it from standing out in the environment it's in. That's just me. I'd wait to introduce a component like this until you actually have a SPECIFIC engine to attach it to, where you can be more certain of the rules to keep up with.

It's on you, though.

Hope this helps.
Cheers!
-Zyro
Trefall
Trefall
Quote:
I'm just skimming here so I may have missed it, but I do not see anything that protects the user from choosing non-existent components or properties.

For the properties, if a property does not exist, it will be made. One could certainly throw an exception here, or log down that a new property was made, just so that it would be easier to detect and note down if this was unintended, but for a game of thousands of properties, this wouldn't run very well. It shouldn't be a problem to add this in case you would suspect there's such a bug in your system, or make an Entity Inspector that you can detect such properties at run-time.

if(it != properties.end())		return *static_cast< Property<T>* >(it->second);	Property<T> *property = (Property<T> *)factory.CreateProperty<T>(manager, propName);


For components, we throw and exception if the component you tried to add does not exist.

if(component)	{		components.push_back(component);		return component;	}	else		throw new CL_Exception("Unable to create component " + componentType);


When making a system like this, you can only hold a developer's hand so far. What you allow in script is a totally different matter imo, one should never be able to crash a running application with a script, and certainly not a server.

Quote:
one thing that's bugging me is that everything you are doing here can be pushed down to a series of byte arrays, which would be more future proof and slightly easier to visualize since you are just treating abstract data as just that... A mindless assortment of 1s and 0s.


I didn't quite follow you on what you were saying here. Are we still at the struct level? I already told you why I didn't go that route (data-driven requirement), so I'm sure you're getting at something else. Please elaborate.

Quote:
Just take my word for it, the motivation you have is wonderful, but the approach your taking is so much like one I took that caused me so much grief in the end. I just feel I have to warn you not to try and grab so many "potentially useful" abstract options in a pool of... completely abstract options. It looks like you have a system with too few absolutes that prevent it from standing out in the environment it's in. That's just me. I'd wait to introduce a component like this until you actually have a SPECIFIC engine to attach it to, where you can be more certain of the rules to keep up with.


I'm really glad that you are sharing your experience and that you warn me based on your own wrong-goings. This system I've listed here though, is already part of a playable, persistent online game, and we find this way to approach object definition works great for us.

One thing we most definately can agree on, is that you should be very critical to any third party source code you embed into your own project.

I've wanted to post this system publicly for a very long time, but I waited until we got further into production on our game, and until we started to actively use this system in our game. As I had expected, a lot of changes happened in this source code once we actually started using it, and there are probably more changes that will have to occure down the line as new requirements of features needs to be addressed and unforeseen problems arise.

Those things almost certainly will happen, but that's how programming works. Like I said in my last post, this is not a system that will work for everyone, it's not a coding style, or a way of approaching the problem, that will satisfy everyone, or that will meet the requirement of any project. Anyone who'd like to use this system would probably have to tweak the code a bit to fit their needs and requirements, that's a given.

I've went very wrong with component-based designs before, and I know how it feels to have a system failing that is really holding a lot of the foundation in a development framework, and where exchanging it, would really be the same as scraping the entire project and starting over again, more or less. I wouldn't post this piece of sourcecode if I hadn't found it useable in my own project first :)

[Edited by - Trefall on October 27, 2009 11:13:12 AM]
Trefall
Trefall
Commited a small update to the code that now throws an exception if you try to get a component that does not exist. This instead of returning NULL.
jpetrie
jpetrie
Have you considered adding a bit more exposition to this and submitting it to writers@gamedev.net as an article?
Trefall
Trefall
Quote:
Have you considered adding a bit more exposition to this and submitting it to writers@gamedev.net as an article?


Oh, I never considered this worthy of being listed there, but that is a very intriguing thought! Thank you :)
jpmcmu
jpmcmu
Trefall, very interesting implementation. I haven't read every reply in this thread only the main post, so this may have been asked, but how do you manage update order of the components?

For instance, say you have a physics component and a graphic component. How would you insure the graphic component updated the visual representations position after the physics component updated the objects position?

Your post here has definitely given me something to think about for my implementation.
Sleep is for the weak, or at least that is what I tell myself every morning.
Trefall
Trefall
void ComponentContainer::UpdateComponents(int deltaTime){	std::vector<Component*>::iterator it;	for(it = components.begin(); it != components.end(); ++it)		(*it)->Update(deltaTime);}


As you can see, the components are not weighted. This is because we're not using the component system at that low level. The reason I really would discourage that low-level use, is that you create dependencies between components, which is really really bad. Of course, it wouldn't take much to add a simple weight system into the mix here and use an incrementor to iterate over the components by weight, so it's certainly doable, but to me, that's just not what I'd want to do with components.

The reason is that I'd rather put this logic into the base game object of my game engine. This is logic that will be so common between game objects, that having to specify this when defining a new object would just be tedious. Why would you bother having to add RenderComponent and PhysicsComponent to 99.99% of your game object definitions?

If you're very determined that you're going to need a RenderComponent and PhysicsComponent, then I'd put them to use a little bit differently from the rest. You have total control of your GameObject class which inherits from Entity (or ComponentContainer and PropertyContainer). It should be straight foreward to hardcode the two components to exist for the GameObject type, and explicitly call these two components' Update function. So maybe your GameObject's Update function would look something like this:

void GameObject::Update(int deltaTime){        physicsComponent.Update(deltaTime);	UpdateComponents(deltaTime);        renderComponent.Update(deltaTime);}


Personally I'd rather find out which components will be in use for most game objects and define hardcoded bodies for these to then add more components on top of. Examples of this could be ItemGameObject and ActorGameObject. Of course, there are a ton of ways to solve this as well. You could define an ActorGameObject in XML and allow other object definitions to inherit from it, for example...

There is a little bit of overhead in performance in using components though, but if data-driven object definition is something you'd like to have in your project, then I think it's worth it going this route. Whether you want to make RenderComponent and PhysicsComponent part of that data-driven object definition is really up to you.
Trefall
Trefall
I forgot to mention this in my last post, but since you also can listen for properties changing, and this might be something you'd want to do, you could also listen in the Graphics component for when the position property changes, and only do the graphical updating when this occures. Now the physics component could change the position, and neither component could care less about the other. I would imagine though that you'd want to run your graphics component once per frame and not only when position changes.
Stani R
Stani R
Trefall, your system is very much like what I've been cobbling together slowly for Solariad. I mixed this year's GDC Canada slides about the game Prototype (which supports very generic component interfaces, only onUpdate() and onMessage(), much like your own system) with the property design pattern of Wyvern, an online rpg that was released eight years ago. The result was just like yours - a component-based approach with a centralized per-entity blackboard for storing properties. Your system is already far along, but you might want to check out Stevey's thoughts about how to make the property pattern more efficient. Two more takeaways from that discussion are support for transient properties as well as property inheritance.

I think what it boils down to is leaving the magical world of type-safety, efficiency, and explicit dependencies for one that is extremely data-driven, modder-friendly, and loosely coupled. This is probably the gaming equivalent of Web 2.0 - creating games that are easily moddable and extendable by the userbase. Even small games can benefit from this approach, as it allows for a community to form around your game and extends the shelf life. With a traditional game, once I'm done playing it, on the shelf it goes, collecting dust.

You should definitely write this up into an article - there is far too little out there on the nitty gritty details of implementing a component system, although I did see two very bare-bones code bases before (one in C# and one in C++). Most discussions and articles focus on a very high-level perspective and omit the talk about how any such system is wired up and used properly.

I'm not sure I understood the logic behind adding behaviors as separate classes to the components. You mention coroutines, so I guess the point is to parallelize them?
Zbychs
Zbychs
Quote:
zyrolasting
We programmers love to experiment, but please note your engine isn't doing anything C++ can't do. We have all the means to declare and define what we want to begin with.

The component system allows flexibility and code reuse beyond any straightforward C++. Primarily it enables dynamic change of entities' properties/traits, which in turn simplifies the whole entity system. I.e. when you want to suspend user input and drive an entity programmatically for a while, in C++ you have to write this functionality into the object beforehand. With dynamic adding of components it's a matter of swapping a component for a while.
Without a component based approach you'll end up with elephant-sized classes and duplication of code everywhere, code that is not standarized in any way.
Components give clean separation of aspects, better testablility, and easier maintenance. So I very strongly oppose a statement "your engine isn't doing anything C++ can't do". It does.

@Trefall
I very much like the idea of entity-owned properties. But I don't agree that component dependencies are a bad idea. Some of them will be designed to depend on others. The idea of hard coding the most important components into the entity, like in the GraphicsComponent/PhysicsComponent example, would prevent i.e. swapping of the input between user/cpu I mentioned earlier. It can be done your way, but again - you have to design your classes for it from the start, predicting what you might need in *every* entity, and put it into the generic empty entity - this is not good.
I thought about component dependencies before, and it seems to me that it would be reasonable to allow components to form DAGs (directed acyclic graphs) of dependency, sorted topologically. One could also use something like MEF (Managed Extensibility Framework). Anyway, I've implemented a component based entity system for my game before, and I was badly in need of dependencies. In my another project, DAGs worked well for it.
mazelle
mazelle
Trefall, regarding the properties, I assume you're using a tree map or a hash map in managing it. Is the performance hit by using those negligible (especially for properties like position, orientation angles which get updated/accessed every frame)?

What are the specs of the machine where you tried this framework?

I have my own component based entity system that allows dependencies among them (I just make sure there are no circular dependencies). Although, it performs well in release build, the debug build is real slow (it matters to me). It is partly caused by accessing components from the component map:

// usageentity->getComponent<WorldObjectComponent>()->getPosition();// getComponent() definitiontemplate <typename ComponentType>ComponentType* getComponent() {  // I use Type instances as keys to my component map  Type typeInfo = Type::getType( ComponentType );  return dynamic_cast<ComponentType*>( this->componentMap.get( typeInfo ) );}
Kylotan
Kylotan
C++ RTTI is quite slow, at least if you're using it that often. Maybe some other identifier would be more effective (eg. an enum). And since your function has a ComponentType template, why not just use static_cast instead? It's not like you're checking for a NULL return anyway...

You might also consider just using a std::vector of components instead of any sort of map. In fact, I would explicitly recommend it in this case. If you have 10 components on average, then the map lookup will make 3 or 4 comparisons on average whereas the vector will make 5 on average, but the vector contents will be linear in memory and far more cache-friendly than the map which has to jump from one map node to another.
Medice
Medice
Quote:
Original post by Kylotan

You might also consider just using a std::vector of components instead of any sort of map. In fact, I would explicitly recommend it in this case. If you have 10 components on average, then the map lookup will make 3 or 4 comparisons on average whereas the vector will make 5 on average, but the vector contents will be linear in memory and far more cache-friendly than the map which has to jump from one map node to another.


I have read of implementation where it would upon initiation use a vector by default and after certain treshold (20) switch to using map.

mazelle
mazelle
Quote:
Original post by Kylotan
C++ RTTI is quite slow, at least if you're using it that often. Maybe some other identifier would be more effective (eg. an enum).


I don't use enums because it transforms my Entity class to a templated one (I don't wanna use int to generalize enums):
template <typename ComponentKeyType> // use the enum type a certain game uses hereclass Entity {  void addComponent( const ComponentKeyType& key, IComponent* const component );  // casting will be done by callee  IComponent* getComponent( const ComponentKeyType& key );};


My framework is interface based (I don't use function pointers or functors) so an change from a non-templated class to templated one will also require changes to interfaces using that class (there are many of them in my case). I'd say introducing a template class is viral.

Moreover, I won't be able to enforce a certain component type to be related to only one key as good as the current system.
someEntity->addComponent( GameComponentType::Movement, movementComponent );// I can make this mistakeanotherEntity->addComponent( GameComponentType::Movement, physicsComponent );


Unless you have better suggestions for these issues. I'd like to hear them.

But I'd really like to know the performance impact of accessing/updating properties that are attached to entities (outside of components). I like the idea and I'd like to try it in my next game.
Kylotan
Kylotan
Quote:
Original post by mazelle
I don't use enums because it transforms my Entity class to a templated one (I don't wanna use int to generalize enums)

There's no reason templates have to come into your entity class. All I'm talking about is literally a component type ID.

// getComponent() definition, overloaded once per concrete component typetemplate <typename ComponentType>ComponentType* getComponent() {    // Use the class's static id value which you've defined as something unique    IComponent* c = this->componentMap.get(ComponentType::id);    return static_cast<ComponentType*>(c);}


There any number of ways you can hard-code unique values into each component type and then write simple wrappers to access them. They're not as elegant as having C++ generate its own automatic unique values via RTTI but they're almost all faster at run-time. Just choose what is most important to you.

Quote:
But I'd really like to know the performance impact of accessing/updating properties that are attached to entities (outside of components). I like the idea and I'd like to try it in my next game.

I doubt it makes any significant difference. What's more important is likely to be the structures you're using to hold the components or properties.
Trefall
Trefall
Sorry that I haven't replied here for a while. I've been really busy, and I still am. But I just wanted to add a few comments.

ComponentContainer holds a vector of components.
PropertyContainer holds a map of properties.

So yes, we have a map in there, but the trick here is that we reference to the properties in the components, only seldomly will you look them up in the map, so on a general basis, you're speaking directly to the property reference.

One could surely use an integer instead of a string, but with our goal to script most components, using a hardcoded enum is not an option for us, thus the string was chosen.

I've piled up quite a few modifications and cleanups to the code in my game project now, hopefully I'll find time soon to add in those things to the open source version soon.

I'll be back with more thorough answers and discussion at a later point! Thank you all for keeping thisone alive and sharing your opinions!
Trefall
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.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.