1079
views
Entity Component System ECS
This tutorial will be about Unity Engine like game object and scene system. this system allows you to manage game objects efficiently. meaning of ECS in Wikipedia: "Entity-Component-System (ECS) is a software architectural pattern mostly used on video game development for the storage of game world objects. An ECS follows the pattern of "entities" with "components" of data. An ECS follows the principle of composition over inheritance, meaning that every entity is defined not by a "type", but by the components that are associated with it. The design of how components relate to entities depend upon the Entity Component System being used." you can implement these system in any other languages easily. this tutorial highly based on Games With Gabe youtube channel. .[http://][https://www.youtube.com/watch?v=HkG8ZdhoXhs&list=PLtrSb4XxIVbp8AKuEAlwNXDxr99e3woGE&index=11] first we will create a event system if you already have you can skip this part this is a C# like event system. #pragma once // Event.hpp typedef void(*Action)(); class Event { private: std::vector<Action> actions; public: void Add(const Action& act) { actions.push_back(act); } void Invoke() const { for (Action act : actions) act(); } void operator()() { Invoke(); } void Clear() { actions.clear(); } }; then we will create Component class #pragma once #include <string> #include <iostream> #include "Event.hpp" namespace ECS { class Entity; class Component { public: std::string name; Event OnDestroyed; Entity* entity; public: Component() : name(std::string(typeid(*this).name()))) { } Component(const std::string& _name) : name(_name) { } // we don't have entity class yet we will write in a moment virtual void SetEntity(Entity* entity) = 0; virtual Entity* GetEntity() = 0; virtual ~Component() { OnDestroyed() ; }; virtual void Update(const float& deltaTime) = 0; }; } actually component code is pretty straightforward, we will inherit classes from this component base class in future. also you can add OnEditor virtual void if you have Imgui or other GUI framework, with reflection it will be awesome (serializing fields and showing in editor like unity does), C++ doesn't have reflection but there are many libraries for that. but you can easily implement that in java and C#. here how I implement it in C#: .[http://][https://github.com/benanil/Zargo--Engine/blob/large/src/Core/Components/Companent.cs] after that wee will create Entity Class. in here instead of std::list you can use a Linked List it would be better // Entity.hpp #pragma once #include <list> #include <cstdint> #include <memory> #include <Event.hpp> namespace ECS { class Entity { public: std::string name; Event OnDestroy; std::list<Component*> components; public: Entity() : name(std::string("Entity") { }; Entity(const std::string& _name) { name = _name; }; ~Entity() ; void Update(const float& deltaTime); Component* GetComponent(const uint16_t& index); template<typename TComp> TComp* GetComponent(); void AddComponent(Component* component); void RemoveComponent(Component* component); void RemoveComponent(const uint16_t& index); template<typename TComp> void RemoveComponent(); template<typename TComp> bool TryGetComponent(TComp** component); template<typename TComp> bool HasComponent(); bool HasComponent(Component* component); }; class TestComponent : Component { public: void SetEntity(Entity* _entity) { entity = _entity; } Entity* GetEntity() { return entity; } void Update(const float&) {} }; } method names are self explanatory then lets take a look at cpp code #include "Entity.hpp" namespace ECS { Entity::~Entity() { OnDestroy(); for (auto& comp : components) comp->~Component(); }; void Entity::Update(const float& deltaTime) { for (auto& comp : components) comp->Update(deltaTime); } // idk is this correct way but hovever. let me now in comments Component* Entity::GetComponent(uint16_t index) { auto begin = components.begin(); for (uint16_t i = 0; i < index; ++i, ++begin); return *begin; // means components[index] } // returns true if component type is same as one of our components template<typename TComp> TComp* Entity::GetComponent() { for (auto& component : components) if (dynamic_cast<TComp>(component))// in C# if (component is TComp) return component; return nullptr; } void Entity::AddComponent(Component* component) { components.push_back(component); } void Entity::RemoveComponent(Component* component) { if (component == nullptr) return; if (!HasComponent(component)) return; components.remove(component); component->~Component(); delete component; } void Entity::RemoveComponent(uint16_t index) { if (index > components.size()) return; Component* component; auto iter = components.begin(); for (uint16_t i = 0; i < index; ++i, ++iter) { if (i == index) { component = *iter; break; } } components.remove(component); component->~Component(); delete component; } template<typename TComp> void Entity::RemoveComponent() { Component* component; for (auto& comp : components) { if (dynamic_cast<TComp>(*comp)) // in C# if (comp is TComp) { *component = *comp; break; } } if (component != nullptr) components.remove(component); component->~Component(); delete component; } bool Entity::HasComponent(Component* component) { for (auto& comp : components) if (comp == component) return true; return false; } template<typename TComp> bool Entity::HasComponent() { for (auto& component : components) if (dynamic_cast<TComp>(component)) // in C# if (component is TComp) return true; return false; } // if our entity has TComp return true otherwise return false template<typename TComp> bool Entity::TryGetComponent(TComp** component) { for (auto& comp : components) { if (comp == component) { *component = comp; return true; } } return false; } } Entity and Component is ready you can test your code. I will post another tutorial about Transform class, you can add transform class to our entity, with transform class you can get, set position, rotation(including euler angles) and scale of the entity(like unity engine and Unreal does) after these we need to create System this system is about Scenes, we can create scenes place entities in it and save the scane after that, in game we can load/destory new scenes(levels) etc. #pragma once // Scene.hpp #include "ECS.hpp" #include <list> #include "../Main/Event.hpp" #include <cstdint> #include <SDL.h> namespace ECS { class Scene { std::list<Entity*> entities; public: std::string name; public: Scene() : name(std::string("New Scene")) {}; Scene(const std::string& _name) : name(_name) {}; ~Scene(); void Update(const float& deltaTime); void ProceedEvent(const SDL_Event* _event); void Unload(); void Load() {}; void Start() {}; #ifndef NEDITOR void UpdateEditor(); #endif void AddEntity(Entity* entitiy); void RemoveEntity(Entity* entitiy); Entity* FindEntityByName(const std::string& name); }; namespace SceneManager { Scene* GetCurrentScene(); void AddScene(Scene* scene); void LoadNewScene(); void LoadScene(uint8_t index); void LoadScene(const std::string& name); } } #include "Scene.hpp" #include "Entity.hpp" namespace ECS { Scene::~Scene() { Unload(); } void Scene::Update(const float& deltaTime) { for (auto& entity : entities) entity->Update(deltaTime); } Entity* Scene::FindEntityByName(const std::string& name) { for (auto& entity : entities) { if (entity->name == name) return entity; } } void Scene::Unload() { for (auto& entity : entities) entity->~Entity(); entities.clear(); } void Scene::AddEntity(Entity* entity) { entities.push_back(entity); } void Scene::RemoveEntity(Entity* entity) { entities.remove(entity); } namespace SceneManager { std::list<Scene*> scenes; Scene* CurrentScene; Scene* GetCurrentScene() { return CurrentScene; } void AddScene(Scene* scene) { scenes.push_back(scene); } void LoadNewScene() { scenes.push_back(new Scene()); CurrentScene = scenes.back(); } void LoadScene(uint8_t index) { if (CurrentScene) { CurrentScene->Unload(); } auto iter = scenes.begin(); for (uint8_t i = 0; index < i; i++, ++iter); CurrentScene = *iter; CurrentScene->Load(); } void LoadScene(const std::string& name) { if (CurrentScene) { CurrentScene->Unload(); } auto iter = scenes.begin(); for (auto& scene : scenes) { if (scene->name == name) { CurrentScene = scene; scene->Load(); break; } } } } } this is an example about how you will use this system SceneManager::LoadNewScene(); Entity* firstEntity = new Entity(); //meshrenderer inherited from Component MeshRenderer* mesh = MeshLoader::LoadMesh("Models/sponza.obj"); SceneManager::GetCurrentScene()->AddEntity(firstEntity); mesh->SetEntity(firstEntity); firstEntity->AddComponent((ECS::Component*)mesh); in your main loop SceneManager::GetCurrentScene()->Update(Time::GetDeltaTime()); with Entities and Components you can create systems like this scene system. thats it I hope that helps you todo: try to save your scene and entities and load them after that
Sign in to comment