Need Help Implementing Entity Component Systems

Started by
11 comments, last by Krankles 10 years, 2 months ago

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.

Advertisement
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

Sean Middleditch – Game Systems Engineer – Join my team!

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)

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.

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/

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.

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.

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?

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.

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)

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.

This topic is closed to new replies.

Advertisement