Component-based Entities using Properties

Post those lines of code you feel like sharing or find what you require for your project here; or simply use them as tutorials.
Trefall
Posts: 45
Joined: Tue Dec 05, 2006 8:49 pm

Component-based Entities using Properties

Post by 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)
Note that the ClanLib dependency is very easy to replace. It's mostly CL_String and CL_SmartPointer that has been used.

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-Comp ... tem/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?

Code: Select all

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:

Code: Select all

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
---------------------------

Code: Select all

#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
----------------------------

Code: Select all

#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
------------------------------------

Code: Select all

#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
----------------------------------------

Code: Select all

#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[i].x);
		movementDestinationEvent.add_argument(movementDestinations[i].y);
	}

	GameObject* gameObject = (GameObject*)entity;
	gameObject->SendViewEvent(movementDestinationEvent);
}
Last edited by Trefall on Tue Oct 27, 2009 4:24 pm, edited 3 times in total.
serengeor
Posts: 1712
Joined: Tue Jan 13, 2009 7:34 pm
Location: Lithuania

Post by serengeor »

Great job man.
I wonder why no one has replied to this :roll:
Last edited by serengeor on Fri Mar 11, 2011 3:42 pm, edited 1 time in total.
Working on game: Marrbles (Currently stopped).
sudi
Posts: 1686
Joined: Fri Aug 26, 2005 8:38 pm

Post by sudi »

Hey Trefall is see u have been working on your component system.
Well i am just trying out your approach. Maybe i have a playable demo tomorrow to see how well(performance wise) your system works.
We're programmers. Programmers are, in their hearts, architects, and the first thing they want to do when they get to a site is to bulldoze the place flat and build something grand. We're not excited by renovation:tinkering,improving,planting flower beds.
Trefall
Posts: 45
Joined: Tue Dec 05, 2006 8:49 pm

Post by Trefall »

Ah, that's great to hear Sudi! And thank you Serengeor for a bit of enthusiasm ;)

In fact we're currently using it, mainly on the server, of our game now, and have nothing but good experiences with it thus far. We haven't stress tested it yet though, nor pulled it through any optimization, so it will be very interesting to see which results you produce, and or which parts of the engine could use some love (in terms of speedups) :)
Murloc992
Posts: 272
Joined: Mon Apr 13, 2009 2:45 pm
Location: Utena,Lithuania

Post by Murloc992 »

Great work Trefall. I'm looking forward to see Sudi's demo too. :)
sudi
Posts: 1686
Joined: Fri Aug 26, 2005 8:38 pm

Post by sudi »

Ok here you go.
there is only a linux 64bit release sry.

http://www.file-upload.net/download-197 ... se.7z.html

ingame press 1 to select standard tower for building.
We're programmers. Programmers are, in their hearts, architects, and the first thing they want to do when they get to a site is to bulldoze the place flat and build something grand. We're not excited by renovation:tinkering,improving,planting flower beds.
Murloc992
Posts: 272
Joined: Mon Apr 13, 2009 2:45 pm
Location: Utena,Lithuania

Post by Murloc992 »

And some love for Windows users :)

Win32 .exe with source code:
http://www.file-upload.net/download-197 ... e.rar.html

If you want compile yourself:
1.Copy every source(.cpp) file to your project and headers into directory named 'h'.
2.In project properties add 'h' as include dir and add irrlicht.lib as library.
3.Enjoy! 8)


_____________

I will study the amazing source tomorrow, going to sleep atm :3 Thanks for the work Sudi, but 33 warnings are not cool, not cool :D
sudi
Posts: 1686
Joined: Fri Aug 26, 2005 8:38 pm

Post by sudi »

Thanks Murloc992 for providing a windows build :D

Btw what do you think about the implementation? And the game as itself a learning expirience for programming a game?
I will clean up the player class bit more and will load the entities from file.
And then i would write a step by step tut on how to code that game but only if anyone is interessted.

oh trefall is that actually the implementation you ment? or did i get your system discribtion?
We're programmers. Programmers are, in their hearts, architects, and the first thing they want to do when they get to a site is to bulldoze the place flat and build something grand. We're not excited by renovation:tinkering,improving,planting flower beds.
Trefall
Posts: 45
Joined: Tue Dec 05, 2006 8:49 pm

Post by Trefall »

Great job her Sudi! You took some of the ideas and put them to use with how you like to structure your code, and it worked great :D

I saw some dangerous stuff in there, that myself I'd be a little careful doing, like you pass the properties with events through the Base_Property without managing type safety. It would crash your game only at runtime and only when you try to cast a property into a wrong type. I'd do a dynamic_cast here to make sure it's type-safe, or handle that a bit safer in some other way. I went back to my own property code and added dynamic_cast to my GetProperty and AddProperty functions. I guess this type checking could be set in an #ifdef _DEBUG if one cares for those types of optimizations... but it's good to handle those types of crashes I think. I throw an exception, so that I have the option to catch it if I want, without crashing the game (and I'm specially thinking about a server running).

I also saw some duplication of code in there. Like the GetPropertyValue and GetProperty was virtually the same, only the former doing the property->GetValue() at the end. So, if you'd like to run a tutorial over it for newbies, you should probably go over it and fix up those things. That said, I'm stunned at which speed you managed to implement this approach of component-based entities using properties! Didn't even take you a day to get it running! I think that excuses these small issues ;)

Great idea you had with just adding int& Health for instance to your components. Since you don't listen for properties changing, there's no need for a full-blown property there, so just extracting a reference to your property's value is perfect. This is a great example that you should never just copy/paste code, but rather integrate it with how you like to do things. Your project didn't have the need for the extra property functionality in the components, so why use it? :)

You really put the property idea to the test with a running game of tower defence, and it looks like it works great! I'm really glad to see that! :D
Murloc992
Posts: 272
Joined: Mon Apr 13, 2009 2:45 pm
Location: Utena,Lithuania

Post by Murloc992 »

Sudi wrote:Thanks Murloc992 for providing a windows build :D

Btw what do you think about the implementation? And the game as itself a learning expirience for programming a game?
I will clean up the player class bit more and will load the entities from file.
And then i would write a step by step tut on how to code that game but only if anyone is interessted.

oh trefall is that actually the implementation you ment? or did i get your system discribtion?
Interested to the maximum :) The hardest part for me is writing a nice framework for a game. :|
Trefall
Posts: 45
Joined: Tue Dec 05, 2006 8:49 pm

Post by Trefall »

Sudi wrote:oh trefall is that actually the implementation you ment? or did i get your system discribtion?
I'm not sure I understand what you're asking here. Are you asking if you implemented the system how I intended for it to be used?

I'd have to say, to a certain degree you did :)

Myself, I'd probably use a listener on the property changing instead of the way you handle events though. So when the Health property changes, I'd listen for that instead of sending the receiveDamage. Your approach is perfectly fine though, and a method of handling such communication and logic that is very common so there's nothing wrong with it :) Myself, I just like the keep the communication channels as few as possible.
Murloc992 wrote:The hardest part for me is writing a nice framework for a game.
Oh yes. This is harder then making the game on top of it :P My pennies of advice is to approach the framework and game with iterative development and refactoring. Don't try to make the perfect framework before you start thinking about building your game, because you will never be able to make a perfect framework. Rather, start building the framework and the game in parallell. Build small pieces at a time to get you moving forward at a controlled pace, then iterate over what you have and do the refactoring you need. Continue this process, and you'll one day end up with a game who's framework is nothing more than the game requires it to be, but nothing less either. You can spend years on making the perfect framework, only to realize that you're forced to refactor major parts of it due to some unforeseen requirements in your game.
sudi
Posts: 1686
Joined: Fri Aug 26, 2005 8:38 pm

Post by sudi »

Trefall wrote:I saw some dangerous stuff in there, that myself I'd be a little careful doing, like you pass the properties with events through the Base_Property without managing type safety. It would crash your game only at runtime and only when you try to cast a property into a wrong type. I'd do a dynamic_cast here to make sure it's type-safe, or handle that a bit safer in some other way. I went back to my own property code and added dynamic_cast to my GetProperty and AddProperty functions. I guess this type checking could be set in an #ifdef _DEBUG if one cares for those types of optimizations... but it's good to handle those types of crashes I think. I throw an exception, so that I have the option to catch it if I want, without crashing the game (and I'm specially thinking about a server running).
You are right there. but i had two reasons for not doing a type safe integration.
  • 1. I already have a typesafe implementation of a boost::any like class with listeners for changes and even setting value via string(console commands) which i could easily use.
    2. When u write the game and you use the properties you will probably even never mixup the types. If several people are working on the code it might happen but it could be reduced by just documenting all used variables with name and type, same with all signals.
    3. I know i just sayed two reasons but the third is just the result it would would havetaken me way to long to write a typesafe integration from scratch. and using one just makes the code bloated.
Trefall wrote: I'm not sure I understand what you're asking here. Are you asking if you implemented the system how I intended for it to be used?

I'd have to say, to a certain degree you did Smile
I actually missed a wrong in the end of the sentence but you answered correct :D

Trefall wrote: I also saw some duplication of code in there. Like the GetPropertyValue and GetProperty was virtually the same, only the former doing the property->GetValue() at the end. So, if you'd like to run a tutorial over it for newbies, you should probably go over it and fix up those things. That said, I'm stunned at which speed you managed to implement this approach of component-based entities using properties! Didn't even take you a day to get it running! I think that excuses these small issues Wink
Just realized that...yeah that are small errors. Don't remember why i actually copied the code instead ofcalling the other function...we will never know :D
We're programmers. Programmers are, in their hearts, architects, and the first thing they want to do when they get to a site is to bulldoze the place flat and build something grand. We're not excited by renovation:tinkering,improving,planting flower beds.
Trefall
Posts: 45
Joined: Tue Dec 05, 2006 8:49 pm

Post by Trefall »

Yeah, that's a perfectly justifiable reason. Don't want to bloat code too much when using it for learning. Like MrMike says in his Game Coding Complete books, he would've liked to have error checking and exception handling going out the roof, but then nobody would be able to read the code :P

I think it's a great idea of yours though, to write a tutorial around this implementation. You've got a lot of systems in there that holds a lot of good room for explanation and eye opening. I'd read it ;)
Trefall
Posts: 45
Joined: Tue Dec 05, 2006 8:49 pm

Post by 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.
serengeor
Posts: 1712
Joined: Tue Jan 13, 2009 7:34 pm
Location: Lithuania

Post by serengeor »

Trefall keep up the good work Image

Edit: the sample code is outdated (I mean the main.cpp in SVN ) :cry:

PS. is the game you're creating available for download ? I would like to try it :)
Working on game: Marrbles (Currently stopped).
Post Reply