Jump to content

  • Log In with Google      Sign In   
  • Create Account

We're offering banner ads on our site from just $5!

1. Details HERE. 2. GDNet+ Subscriptions HERE. 3. Ad upload HERE.


Need Help Implementing Entity Component Systems


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
12 replies to this topic

#1 Krankles   Members   -  Reputation: 281

Like
0Likes
Like

Posted 31 January 2014 - 12:46 PM

Hello, so I've made a couple of basic games in C++ and AS3, through the traditional object oriented way, and then I came across an article about Entity Component Systems on GameDev. Everything made a lot more sense than having a big hierarchy of classes, but as I try to research more about this on google and gamedev, I only find explanations on how it works (and very partial implementations). I can't wrap my head around getting the design to work.

 

I've seen previous threads that have some code in them, but I still don't quite understand it. Is there any existing tutorials, books, resources, etc, that I could read to further understand how it works? It'd also help immensely if there's code to go along with it, as I don't understand much without some code. Also, if anyone has made a simple game or basic system showing how it works, I'd be very interested in seeing your code, most preferably in C++ as well. Thanks.



Sponsor:

#2 SeanMiddleditch   Members   -  Reputation: 6466

Like
2Likes
Like

Posted 31 January 2014 - 03:31 PM

There's a million different ways to build a component system. You maybe don't want an actual ECS (which is a variant of component-based design) as there are much simpler and perfectly capable variants.

Check out this blog by Randy, a friend of mine: http://www.randygaul.net/2013/05/20/component-based-engine-design/

Here's a link to my first-ever talk on components at https://github.com/EngineArchitectureClub/TalkSlides/tree/master/2012/05-Components-SeanMiddleditch - they're not the highest quality slides possible, but maybe they'll help

#3 BeerNutts   Crossbones+   -  Reputation: 2981

Like
0Likes
Like

Posted 31 January 2014 - 04:03 PM

Hello, so I've made a couple of basic games in C++ and AS3, through the traditional object oriented way, and then I came across an article about Entity Component Systems on GameDev. Everything made a lot more sense than having a big hierarchy of classes, but as I try to research more about this on google and gamedev, I only find explanations on how it works (and very partial implementations). I can't wrap my head around getting the design to work.

 

I've seen previous threads that have some code in them, but I still don't quite understand it. Is there any existing tutorials, books, resources, etc, that I could read to further understand how it works? It'd also help immensely if there's code to go along with it, as I don't understand much without some code. Also, if anyone has made a simple game or basic system showing how it works, I'd be very interested in seeing your code, most preferably in C++ as well. Thanks.

ECS is one of those ideas that you have to keep going over, keep reading, doing some coding, and, at some point, it will just click, and a typical "a Ha!" moment will occur for you.

 

You're welcome to check out my dev journal (linked in my sig) as I have done some ECS-type work.  The earlier journals I explain my 1st attempt at an ECS framework.  In that 1st cut, I had both logic and data in my components, and communication between them (and entities) was done via an event system.  I wasn't totally happy with it, so...

 

I created a 2nd ECS framework (dubbed Escape System), which the components that make up an entity only hold data, and systems are what operate on the components or entities.  I think this is the better way to go about it, but that's not to say an ECS with both systems and events wouldn't be good as well.

 

Good luck.


My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)

#4 Hodgman   Moderators   -  Reputation: 31173

Like
1Likes
Like

Posted 31 January 2014 - 06:35 PM

There's a million different ideas about what ECS is, and what the goals of an ECS library should be. Different ECS libraries are solving different problems. Given this, it's hard to answer how to write an ECS library, unless the goals are stated first.


Also, a big hierarchy of classes (valuing inheritance over composition) is not traditional OOP; it's a complete abuse of OOP!
OO teaches that you should prefer composition over inheritance. I'd suggest simply practicing writing OO code using composition first, to get used to how composition based code is structured, before trying to create your own library that's designed to make composition easier somehow.

#5 rjuang   Members   -  Reputation: 135

Like
0Likes
Like

Posted 31 January 2014 - 06:43 PM

Not sure if you have seen this article by Mick West but I found it very useful for those who are refactoring hierarchy-based game objects into an entity-component system.

 

http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/



#6 Krankles   Members   -  Reputation: 281

Like
1Likes
Like

Posted 31 January 2014 - 08:05 PM

Also, a big hierarchy of classes (valuing inheritance over composition) is not traditional OOP; it's a complete abuse of OOP!
OO teaches that you should prefer composition over inheritance. I'd suggest simply practicing writing OO code using composition first, to get used to how composition based code is structured, before trying to create your own library that's designed to make composition easier somehow.

 

My bad, I guess I got too used to it!

 

Anyways, thanks guys, I started my own attempt at ECS, but I'm having troubles connecting things together. This is what I have so far.

 

Entity.h

#ifndef ENTITY_H
#define ENTITY_H
 
#include <vector>
#include <string>
#include "Component.h"
 
class Entity {
    public:
        Entity();
        ~Entity();
 
        void AddComponent(Component *component);
        Component* GetComponent(std::string name);
        void Update();
 
    private:
        std::vector<Component*> components;
};
 
#endif
 
Entity.cpp
#include "Entity.h"
 
Entity::Entity()
{
 
}
 
Entity::~Entity()
{
 
}
 
void Entity::AddComponent(Component *component)
{
    components.push_back(component);
}
 
Component* Entity::GetComponent(std::string name)
{
    for (unsigned int i = 0; i < components.size(); i++) {
        if (name.compare(components[i]->name) == 0)
            return components[i];
    }
}
 
void Entity::Update()
{
    for (unsigned int i = 0; i < components.size(); i++) {
        components[i]->Update();
    }
}
 
BaseSystem.h
#ifndef BASE_SYSTEM_H
#define BASE_SYSTEM_H
 
class BaseSystem
{
    public:
        BaseSystem();
        virtual ~BaseSystem();
};
 
#endif
 
Component.h
#ifndef COMPONENT_H
#define COMPONENT_H
 
#include <string>
 
class Component
{
    public:
        Component();
        virtual ~Component();
 
        std::string name;
        virtual void Update();
};
 
#endif
 
RenderSystem.h
#ifndef RENDER_SYSTEM_H
#define RENDER_SYSTEM_H
 
#include <SDL2/SDL.h>
 
class RenderSystem : public BaseSystem {
    public:
        void Render(SDL_Renderer* renderer, SDL_Texture* image);
};
 
#endif
 
RenderSystem.cpp
#include "RenderSystem.h"
 
void RenderSystem::Render(SDL_Renderer* renderer, SDL_Texture* image)
{
    SDL_RenderCopy(renderer, image, NULL, NULL);
}
 
RenderComponent.h
#ifndef RENDER_COMPONENT_H
#define RENDER_COMPONENT_H

#include "Component.h"
#include <SDL2/SDL.h>

class RenderComponent : public Component {
    public:
        RenderComponent();
        ~RenderComponent();
        SDL_Texture* image;
};

#endif
 
RenderComponent.cpp
#include "RenderComponent.h"

RenderComponent::RenderComponent(){
    name = "render";
}

 

So that's all I have right now, but I don't know if I'm even doing this right or not. Right now, I don't want to complicate anything, and just want a simple entity (background, player, what-have-you) to draw. Once I have that down, I believe I can implement the rest.

 

I need some help being guided in the right direction, I know my code might look like utter crap right now, but I'm just learning, so please bear with me. When I try creating an entity in my test program, I tried doing this:

Entity background;
background.AddComponent(new RenderComponent());
SDL_Surface *tmp_image = NULL;
tmp_image = IMG_Load("gfx/background.png");
background.GetComponent("render")->image = SDL_CreateTextureFromSurface(renderer, tmp_image);

But it says that "class Component" has no member named "image". Which is obvious, since it's not declared in the class Component, but in RenderComponent. How do I solve this? Or should I NOT be doing this?

 

Thanks.

 

EDIT: Basically what I'm asking is, is that how you correctly add a component? If so, then when I get my component, I want to be able to manipulate it, but it doesn't let me due to the class Component having no member named image. The list of components is an array of Components, which means I can't access the image variable. Also, how are systems supposed to work. I've been googling around and it's somewhat confusing. I feel like I'm doing this completely wrong.


Edited by Krankles, 01 February 2014 - 07:02 AM.


#7 Krankles   Members   -  Reputation: 281

Like
0Likes
Like

Posted 02 February 2014 - 08:05 AM

So I've been researching even more, and I am confused a bit now. Should the System know how to do a function? Or should the Component know how to do a function? For example, JumpComponent and JumpSystem, should the System know how to Jump? Or should the Component know how to jump? I'm confused because I've read that the component should just contain the data, etc, and the system should handle the components. However, in some people's implementations, I've seen them having the component having the data and knowing how to do something, and the system should just have a list of components, and make them interact with each other.

 

Also, I've been thinking, for my RenderComponent and RenderSystem -- to make it work -- should I have the Render function inside my RenderComponent, and then have the RenderSystem loop through all of the RenderComponents and call the respective Render function?

 

Not only that, but I was also thinking that for my problem earlier, I could possibly fix the problem by using my newly created RenderSystem by passing in the component pointer to the RenderSystem component list and then be able to access the member named "image".

 

Lastly, I was wondering, why do people in their Entity class don't include the component header? I see them just doing "Class Component;" and that's it. (Sorry for the noob questions, I didn't fully learn C++, I learned the basics, learned some SDL and went on creating basic games. I also can't test this ideas right now because I'm busy with other things currently.)

 

Thanks.



#8 Krankles   Members   -  Reputation: 281

Like
0Likes
Like

Posted 02 February 2014 - 07:11 PM

Sorry for the triple post! But, ignore what I said earlier, I was being stupid. I think I got a good system going. I need to know if this is how a decent ECS should work though, or even if this is a good basis. I took out the Render component and replaced it with Velocity and Position instead, as I find it much easier to start out with.

#include "Entity.h"
#include "PositionComponent.h"
#include "VelocityComponent.h"
#include "MovementSystem.h"
 
int main(int argc, char const* argv[])
{
    MovementSystem movementSystem;
 
    Entity player;
    player.AddComponent(new PositionComponent(2, 2));
    player.AddComponent(new VelocityComponent(5, 5));
 
    for (int i = 0; i < 10; i++) {
        movementSystem.ProcessEntity(&player);
    }
 
    return 0;
}

That's basically how everything works, I create a movement system, entity, add components to the entity and then use the system to process my entity. I don't know whether or not this is how ECS works or not, but it seems right to me.

 

MovementSystem.h

#ifndef MOVEMENT_SYSTEM_H
#define MOVEMENT_SYSTEM_H
 
#include "EntitySystem.h"
#include <vector>
 
class VelocityComponent;
class PositionComponent;
 
class MovementSystem : public EntitySystem
{
    public:
        MovementSystem();
        ~MovementSystem();
 
        void ProcessEntity(Entity* entity);
 
    private:
        VelocityComponent* velocityComponent;
        PositionComponent* positionComponent;
};
 
#endif

MovementSystem.cpp

#include <iostream>
#include "Entity.h"
#include "MovementSystem.h"
#include "PositionComponent.h"
#include "VelocityComponent.h"
 
MovementSystem::MovementSystem()
{
 
}
 
MovementSystem::~MovementSystem()
{
 
}
 
void MovementSystem::ProcessEntity(Entity* entity)
{
    positionComponent = dynamic_cast<PositionComponent *>(entity->GetComponent("position"));
    velocityComponent = dynamic_cast<VelocityComponent *>(entity->GetComponent("velocity"));
 
    positionComponent->SetX(positionComponent->GetX() + velocityComponent->GetVelocityX());
    positionComponent->SetY(positionComponent->GetY() + velocityComponent->GetVelocityY());
    std::cout << "Player X: " <<  positionComponent->GetX() << std::endl;
}

That's how my movement system works, but I was wondering if there's anything I could improve my system on?


Edited by Krankles, 02 February 2014 - 07:17 PM.


#9 BeerNutts   Crossbones+   -  Reputation: 2981

Like
1Likes
Like

Posted 02 February 2014 - 10:09 PM

That's not really how you should be doing it.  Really, it all needs to be generic and you don't actually call the system's functions.  Rather, you have a ECS World, and you create Systems, and you designate what type of component(s) that system should deal with, and you create Entities, and assign Components to them.

 

Then, in your update function, you simply call EcsWorld->Update(), and it will iterate through all your entities and components through all the systems that operate on certain components.

 

Here's an example from my Escape System (and here's a brief explanation of the ECS)

 

Define an Input Component and give it to a entity:

// in a header file
class TInput : public Esc::TComponent
{
public:
    TInput() : Esc::TComponent("Input") {}
 
};
...
// in a .cpp file
Esc::TEntityPtr Entity1 = pWorld->CreateEntity();
TInput* inputComp(new TInput());
pWorld->AddComponent(Entity1, inputComp);
//Assume there's also a Physical Component, that defines the entity is a physical object
pWorld->AddComponent(Entity1, physicalComp);
 

()

 

Now create a system that acts upon an entity that has both a Physical Object component and an Input component (see the Initialize() function):

// in a header file
class TInputSystem : public Esc::TSystem
{
public:
    TInputSystem(sf::RenderWindow *pApp);
    ~TInputSystem();
 
    void Update(Esc::TEntityPtr entity, uint32_t tickDelta);
    void Initialize();
 
private:
    sf::RenderWindow *mpApp;
 
    uint32_t mCenterY;
    uint32_t mCenterX;
 
};
...
 
// in a .cpp file
InputSystem = new TInputSystem(app);
pWorld->AddSystem(InputSystem);
 
...
 
TInputSystem::TInputSystem(sf::RenderWindow *pApp) :
  Esc::TSystem(), mpApp(pApp)
{
    ...
}
TInputSystem::~TInputSystem()
{
 
}
 
void TInputSystem::Update(Esc::TEntityPtr entity, uint32_t tickDelta)
{
    TPhysicalObject *physicalObject =
      static_cast<TPhysicalObject*>(entity->GetComponent("PhysicalObject"));
    // Do something with entitiy based on the input
...
 
}
 
void TInputSystem::Initialize()
{
    // Set which components we want to deal with
    Esc::TSystem::HandleComponent("PhysicalObject", true);
    Esc::TSystem::HandleComponent("Input", true);
}
 

Now, in your game loop, you'd simply call pWorld->Update(), and it would go through all the systems (in this case the InputSystem), and it would call InputSystem-> with all the entities that have both a PhysicalObject and an Input Component (probably only 1 entity).

 

You probably need a better understanding of C++ before you can get a real good grasp on all this.

 

Good luck.


Edited by BeerNutts, 02 February 2014 - 10:10 PM.

My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)

#10 Krankles   Members   -  Reputation: 281

Like
0Likes
Like

Posted 03 February 2014 - 06:33 AM

That's not really how you should be doing it.  Really, it all needs to be generic and you don't actually call the system's functions.  Rather, you have a ECS World, and you create Systems, and you designate what type of component(s) that system should deal with, and you create Entities, and assign Components to them.

 

Then, in your update function, you simply call EcsWorld->Update(), and it will iterate through all your entities and components through all the systems that operate on certain components.

 

Here's an example from my Escape System (and here's a brief explanation of the ECS)

 

Define an Input Component and give it to a entity:

// in a header file
class TInput : public Esc::TComponent
{
public:
    TInput() : Esc::TComponent("Input") {}
 
};
...
// in a .cpp file
Esc::TEntityPtr Entity1 = pWorld->CreateEntity();
TInput* inputComp(new TInput());
pWorld->AddComponent(Entity1, inputComp);
//Assume there's also a Physical Component, that defines the entity is a physical object
pWorld->AddComponent(Entity1, physicalComp);
 

()

 

Now create a system that acts upon an entity that has both a Physical Object component and an Input component (see the Initialize() function):

// in a header file
class TInputSystem : public Esc::TSystem
{
public:
    TInputSystem(sf::RenderWindow *pApp);
    ~TInputSystem();
 
    void Update(Esc::TEntityPtr entity, uint32_t tickDelta);
    void Initialize();
 
private:
    sf::RenderWindow *mpApp;
 
    uint32_t mCenterY;
    uint32_t mCenterX;
 
};
...
 
// in a .cpp file
InputSystem = new TInputSystem(app);
pWorld->AddSystem(InputSystem);
 
...
 
TInputSystem::TInputSystem(sf::RenderWindow *pApp) :
  Esc::TSystem(), mpApp(pApp)
{
    ...
}
TInputSystem::~TInputSystem()
{
 
}
 
void TInputSystem::Update(Esc::TEntityPtr entity, uint32_t tickDelta)
{
    TPhysicalObject *physicalObject =
      static_cast<TPhysicalObject*>(entity->GetComponent("PhysicalObject"));
    // Do something with entitiy based on the input
...
 
}
 
void TInputSystem::Initialize()
{
    // Set which components we want to deal with
    Esc::TSystem::HandleComponent("PhysicalObject", true);
    Esc::TSystem::HandleComponent("Input", true);
}
 

Now, in your game loop, you'd simply call pWorld->Update(), and it would go through all the systems (in this case the InputSystem), and it would call InputSystem-> with all the entities that have both a PhysicalObject and an Input Component (probably only 1 entity).

 

You probably need a better understanding of C++ before you can get a real good grasp on all this.

 

Good luck.

 

I see! This makes a lot of sense, thanks. And yeah, I should probably learn more of C++ before I start doing this. However, I have a quick question. What if you wanted separate functionality for a specific entity that operates within the same system? Should you just create a new system entirely? Or possibly check if it's a specific entity and run that code inside the system? For example, I have a JumpSystem and the player could double jump but the enemy can't, should you create a new DoubleJumpSystem? Or should you just check within the JumpSystem for the player entity? Or another example to possibly clear the question up, what if I have an AnimationSystem and the 2 different entities animate differently, should it just check for a specific entity or create a new system?

 

Thank you.



#11 NoAdmiral   Members   -  Reputation: 519

Like
0Likes
Like

Posted 03 February 2014 - 07:50 AM

You could very easily include a flag for double-jump (or triple- or n- jump) in the component itself. What if you encounter a situation where you want an enemy to double-jump? You could just set it's "num_jump" variable to 2 and you'd be done.


Inspiration from my tea:

"Never wish life were easier. Wish that you were better" -Jim Rohn

 

herwrathmustbedragons.tumblr.com


#12 haegarr   Crossbones+   -  Reputation: 4438

Like
0Likes
Like

Posted 03 February 2014 - 07:53 AM

IMHO there should be no DoubleJumpSystem, but there should be no JumpSystem either. Going this way will make things needless complex. Instead, an integrated solution should be data driven, perhaps like so:

 

OS / input device originating raw input is fetched from the InputSystem and translated into engine encoded input (we neglect the nitty-gritty details about this topic here). The ControllerComponent instance of the PC recognizes input that denotes "jump", or the AI of the NPC decides to "jump". This abstract command is passed to the state machine of the PC/NPC. The current state defines whether or not jumping is possible, e.g. the current state must provide an outgoing transition which can be triggered by the "jump" command and has no suppressing conditions active. (Well, an AI will usually not ask for a "jump" if it is not possible, but for the sake of simplicity we do so.) The state referring to is the "now jumping" state. The state machine is either part of the animation system, or else the animation system is controlled by the state machine, and hence plays black the animation belonging to "jump" just because the new current state is "jumping".

 

This way differs from having a sub-system for each movement in not grouping the movements by kind over all characters, but the movement abilities for each particular character into a state machine. The state machine can be instantiated from a template for equal characters with equal movement abilities, of course.

 

A animation system should be able to run animations on any animated object, being it the PC, an NPC, an item, or whatever. How the object can be animated at all is a question of data (e.g. tracks of key frames bound to positions and orientations). How animations are actually run is a question of controlling variables, which in turn are controlled by the state machine and CharacterController or AI, resp.



#13 Krankles   Members   -  Reputation: 281

Like
0Likes
Like

Posted 03 February 2014 - 01:53 PM

Thanks for the descriptive answers guys! I think I got it now.






Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS