├── .gitignore ├── LICENSE ├── README.md ├── ecs └── ecs.h ├── game ├── include │ ├── test_components.h │ └── test_systems.h ├── premake5.lua └── src │ ├── main.cpp │ └── test_systems.cpp ├── premake-VisualStudio.bat ├── premake-mingw.bat ├── premake5 ├── premake5.exe ├── premake5.lua ├── premake5.osx ├── raylib_premake5.lua └── resources ├── LICENSE ├── ambient.ogg ├── coin.wav └── mecha.png /.gitignore: -------------------------------------------------------------------------------- 1 | raylib-master 2 | _build 3 | _bin 4 | .vs 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020-2021 Jeffery Myers 2 | 3 | This software is provided "as-is", without any express or implied warranty. In no event 4 | will the authors be held liable for any damages arising from the use of this software. 5 | 6 | Permission is granted to anyone to use this software for any purpose, including commercial 7 | applications, and to alter it and redistribute it freely, subject to the following restrictions: 8 | 9 | 1. The origin of this software must not be misrepresented; you must not claim that you 10 | wrote the original software. If you use this software in a product, an acknowledgment 11 | in the product documentation would be appreciated but is not required. 12 | 13 | 2. Altered source versions must be plainly marked as such, and must not be misrepresented 14 | as being the original software. 15 | 16 | 3. This notice may not be removed or altered from any source distribution. 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Raylib Simple ECS 2 | Very simple ECS example 3 | 4 | # Work In Progress 5 | This is a work in progress example. It is functional but may not be fully documented/commented 6 | 7 | ## Building 8 | Uses game-premake https://github.com/raylib-extras/game-premake 9 | 10 | ### Windows MinGW 11 | Run the premake-mingw.bat and then run make in the folder 12 | 13 | ### Windows Visual Studio (not VSC) 14 | Run premake-VisualStudio.bat and then open the fasteroids.sln that is generated 15 | 16 | ### Linux 17 | CD into the directory, run ./premake5 gmake2 and then run make 18 | 19 | #### MacOS 20 | CD into the directory, run ./premake5.osx gmake2 and then run make 21 | 22 | ## Using 23 | ecs.h is single header library file, and uses the _ECS_IMPLEMENTATION #define to work 24 | You must have 25 | #define _ECS_IMPLEMENTATION 26 | #include "ecs.h" 27 | 28 | In one and only one cpp file. 29 | 30 | # ECS Basics 31 | The goal of an ECS is to break gamplay objects into discrete components and have systems that itterate over those components quickly to do things. 32 | An entity ID is used to link components that apply to the same game object together. 33 | ECS frameworks are very common in the game industry because they make it easy to build up game objects via composition (a bundle of parts) rather than inheritance (a tree of dependent types). 34 | They also keep things that are processed together near each other in memory to help prevent cache misses when processing items. 35 | 36 | This example is a very basic ECS implementation and just meant as an introduction to the concepts. 37 | 38 | ## Entity 39 | An enity is just an ID. It has no inherent data. It's just a way to define components that are attached to the same logical 'thing'. Every component has an Entity ID and components of different types may use the same entity ID, indicating that they are both part of the same entity. There is no 'master list' of entities, because entities themselves are never itterated, only components. 40 | 41 | The entity ID is just a number. 42 | 43 | ## Components 44 | A component is a thing that has data. Components have an entity ID to reference what entity is is attached too. Components contain data that is used by systems such as; 45 | * Transform 46 | * Color 47 | * Shape 48 | 49 | Components are defined as class that is derived from Component and includes some common functions. Component Types have a unique ID so they can be referenced. 50 | 51 | ## Component Tables 52 | All components of a single type are stored together in a component table. At it's core the table is just a big vector of components. This allows rapid itteration of components by systems, and can help minimize cache misses. many components will be loaded into cache because they are in the same memory block in the vector. 53 | 54 | The component table is a generic interface to these lists of components and contains a map of Entity IDs to component index in the vector. This allows one a system to look up other compoonents for a specific entity ID. An example of this is when the render system has to look up the color component for an entity with a transform. 55 | 56 | ## System 57 | A system is something that itterates one or more component types and does something with the entities that contain them. The RenderSystem is the most basic example of a system. It itterates all the entities with a transform, gets color and shape compoonents and then draws the results to the screen. 58 | 59 | ## ECS 60 | This is simply a container for a set of component tables and registersed systems. It's all the data and logic that makes an ECS instance. It's a class because you may have multiple ECS systems for different uses (multiple rooms on a server for instance). 61 | 62 | # Code Structure 63 | 64 | ## ECS library 65 | The ECS library contains the base code for the system. It has no actuall components or systems, just the base classes and code used. 66 | 67 | To use the ECS you define a new variable fo the type ECS; 68 | 69 | ``` 70 | ECS myECS; 71 | ``` 72 | 73 | ## Defining a component 74 | You define a component by creating a class derived from Component. You must call the DEFINE_COMPONENT macro in the public section of your class. This macro is what addes in the code needed to define the unqique component ID for your component type. The passed in argument is used to create this code. 75 | 76 | ``` 77 | class YourComponent : public Component 78 | { 79 | public: 80 | DEFINE_COMPONENT(YourComponent); 81 | 82 | int SomeData = 0; 83 | float SomeOtherData = 1.234f; 84 | }; 85 | ``` 86 | You can then define any other data you want. You can also define methods on the component if you wish. Systems will have full acceess to the component class when processing it. 87 | 88 | ## Registering a component 89 | Components are registerd with the ECS set with a call to RegisterComponent. This will create the component table for this type. 90 | 91 | ``` 92 | myECS.RegisterComponent(); 93 | ``` 94 | 95 | ## Creating entities 96 | Entities are just an ID, so they are created with a call to GetNewEntity() on the ECS. 97 | 98 | ``` 99 | uint64_t newEntity = myECS.GetNewEntity(); 100 | ``` 101 | 102 | ## Adding components to entities 103 | You can add a component to an entity by simply asking the ECS for the component on that entity, if the component does not exist for that entity it'll be created. GetComponent will return a pointer to the component for the entity. 104 | 105 | ``` 106 | YourComponent* componentPtr = myECS.GetComponent(newEntity); 107 | ``` 108 | 109 | ## Setting data in components 110 | Once you have a component pointer, just manipulate the data in it as normal 111 | ``` 112 | componentPtr->SomeData = 1000; 113 | ``` 114 | 115 | ## Defining Systems 116 | Systems are defined similar to components by deriving from the System class. 117 | ``` 118 | class MySystem : public System 119 | { 120 | public: 121 | SYSTEM_CONSTRUCTOR(MySystem); 122 | 123 | void Update() override; 124 | }; 125 | ``` 126 | Like components, you must call the macro SYSTEM_CONSTRUCTOR in the public part of your class. This defines the correct construtor needed for systems to work. 127 | 128 | ### The update method in systems 129 | The update method is called when your system is run. Within it you can do logic on all compnents of one or multiple types using the DoForEachComponent method with a callback or lambda. You can also manually itterate components yourelf by getting the component table for a component type from the ECS. Every System has a reference to the ECS it is registered with as the ECSContainer member. 130 | 131 | ``` 132 | void MySystem::Update() 133 | { 134 | DoForEachComponent([this](YourComponent& component) 135 | { 136 | if (component->SomeData > 10) 137 | component->SomeOtherData = 5; 138 | }); 139 | } 140 | ``` 141 | 142 | ## Registering Systems 143 | Systems are registered the same way as components, using the ECS object. 144 | ``` 145 | myECS.RegisterSystem(); 146 | ``` 147 | 148 | ## Updating the ECS 149 | calling ECS::Update(); will update all systems in the order they are registered. 150 | ``` 151 | myECS.Update(); 152 | ``` 153 | 154 | # More reading 155 | * https://en.wikipedia.org/wiki/Entity_component_system 156 | * https://www.simplilearn.com/entity-component-system-introductory-guide-article 157 | * Entity Component System Overview in 7 Minutes https://www.youtube.com/watch?v=2rW7ALyHaas 158 | * Is There More to Game Architecture than ECS? https://www.youtube.com/watch?v=JxI3Eu5DPwE 159 | 160 | -------------------------------------------------------------------------------- /ecs/ecs.h: -------------------------------------------------------------------------------- 1 | /* 2 | Simple ECS system 3 | 4 | You must use #define _ECS_IMPLEMENTATION before you include ecs.h in one and only one cpp file 5 | 6 | */ 7 | 8 | 9 | #ifndef _ECS_H 10 | #define _ECS_H 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | class System; 20 | 21 | // base class for a component 22 | class Component 23 | { 24 | public: 25 | uint64_t EntityId = uint64_t(-1); // entity that is attached to this component 26 | inline virtual size_t GetTypeId() const { return size_t(-1); } // a way to get the type ID of this component when all you have is the base class 27 | 28 | virtual ~Component() = default; 29 | }; 30 | 31 | // macro to define common code that all components need 32 | // computes a runtime unique ID for each component using the name 33 | // NOTE that this ID is not deterministic, a real ECS would hash the name and use a better ID 34 | #define DEFINE_COMPONENT(T) \ 35 | static const char* GetComponentName() { return #T;} \ 36 | static size_t GetComponentId() { return reinterpret_cast(#T); } \ 37 | inline size_t GetTypeId() const override { return GetComponentId(); } 38 | 39 | // base class for a generic table of components that the Entity Set can hold and not care about the type 40 | class ComponentTableBase 41 | { 42 | public: 43 | virtual Component* Add(uint64_t id) = 0; 44 | virtual void Remove(uint64_t id) = 0; 45 | virtual bool ContainsEntity(uint64_t id) = 0; 46 | virtual Component* Get(uint64_t id) = 0; 47 | virtual Component* TryGet(uint64_t id) = 0; 48 | 49 | virtual ~ComponentTableBase() = default; 50 | }; 51 | 52 | // specific version of table class that stores the entities in a vector. 53 | template 54 | class ComponentTable : public ComponentTableBase 55 | { 56 | public: 57 | std::vector Components; 58 | 59 | inline Component* Add(uint64_t id) override 60 | { 61 | Components.emplace_back(T()); 62 | T* component = &Components.back(); 63 | component->EntityId = id; 64 | EntityIds[id] = Components.size() - 1; 65 | 66 | return component; 67 | } 68 | 69 | inline void Remove(uint64_t id) override 70 | { 71 | if (Components.empty()) 72 | return; 73 | 74 | const auto slot = EntityIds.find(id); 75 | if (slot == EntityIds.end()) 76 | return; 77 | 78 | // quick swap delete. 79 | // we get the last item in the list and copy it into the slot we want to delete, then delete off the end. 80 | // this prevents all the following items from having 'side down' into the empty slots 81 | const auto& componentToDelete = Components[slot->second]; 82 | const auto& endComponent = Components[Components.size() - 1]; 83 | 84 | Components[slot->second] = endComponent; 85 | 86 | EntityIds[componentToDelete.EntityId] = slot->second; 87 | EntityIds.erase(slot); 88 | 89 | Components.erase(Components.begin() + (Components.size()-1)); 90 | } 91 | 92 | inline bool ContainsEntity(uint64_t id) override 93 | { 94 | return EntityIds.find(id) != EntityIds.end(); 95 | } 96 | 97 | // gets or adds a component for this entity ID 98 | inline Component* Get(uint64_t id) override 99 | { 100 | std::unordered_map::iterator entityItr = EntityIds.find(id); 101 | if (entityItr == EntityIds.end()) 102 | return Add(id); 103 | 104 | return &Components[entityItr->second]; 105 | } 106 | 107 | // gets or adds a component for this entity ID 108 | inline Component* TryGet(uint64_t id) override 109 | { 110 | std::unordered_map::iterator entityItr = EntityIds.find(id); 111 | if (entityItr == EntityIds.end()) 112 | return nullptr; 113 | 114 | return &Components[entityItr->second]; 115 | } 116 | 117 | protected: 118 | 119 | // a map of entity IDs to component index to make lookups fast 120 | std::unordered_map EntityIds; 121 | }; 122 | 123 | // a container for a set of entities, and a set of registered systems 124 | class ECS 125 | { 126 | protected: 127 | std::stack DeadIds; 128 | uint64_t NextId = 0; 129 | 130 | public: 131 | std::vector> Systems; 132 | std::unordered_map Tables; 133 | 134 | void Update(); 135 | 136 | template 137 | inline T* RegisterSystem() 138 | { 139 | Systems.emplace_back(std::move(std::make_unique(*this))); 140 | return reinterpret_cast(Systems.back().get()); 141 | } 142 | 143 | template 144 | inline ComponentTable* RegisterComponent() 145 | { 146 | ComponentTableBase* tablePtr = new ComponentTable(); 147 | Tables[T::GetComponentId()] = tablePtr; 148 | 149 | return GetComponentTable(); 150 | } 151 | 152 | template 153 | inline ComponentTable* GetComponentTable() 154 | { 155 | return reinterpret_cast*>(Tables[T::GetComponentId()]); 156 | } 157 | 158 | template 159 | inline T* GetComponent(uint64_t id) 160 | { 161 | auto table = Tables.find(T::GetComponentId()); 162 | if (table == Tables.end()) 163 | return nullptr; 164 | 165 | return reinterpret_cast(table->second->Get(id)); 166 | } 167 | 168 | template 169 | inline T* TryGetComponent(uint64_t id) 170 | { 171 | auto table = Tables.find(T::GetComponentId()); 172 | if (table == Tables.end()) 173 | return nullptr; 174 | 175 | return reinterpret_cast(table->second->TryGet(id)); 176 | } 177 | 178 | uint64_t GetNewEntity(); 179 | 180 | void RemoveEntity(uint64_t id); 181 | 182 | virtual ~ECS(); 183 | }; 184 | 185 | class System 186 | { 187 | public: 188 | System(ECS& ecsContainer) 189 | :ECSContainer(ecsContainer) 190 | { 191 | } 192 | 193 | virtual void Update() = 0; 194 | 195 | protected: 196 | ECS& ECSContainer; 197 | 198 | template 199 | inline void DoForEachComponent(std::function callback) 200 | { 201 | if (!callback) 202 | return; 203 | 204 | for (T& comp : ECSContainer.GetComponentTable()->Components) 205 | callback(comp); 206 | } 207 | }; 208 | 209 | #define SYSTEM_CONSTRUCTOR(T) T(ECS& ecsContainer) : System(ecsContainer){} 210 | 211 | #endif // _ECS_H 212 | 213 | #ifdef _ECS_IMPLEMENTATION 214 | void ECS::Update() 215 | { 216 | for (auto& system : Systems) 217 | system->Update(); 218 | } 219 | 220 | ECS::~ECS() 221 | { 222 | for (auto table : Tables) 223 | delete(table.second); 224 | } 225 | 226 | void ECS::RemoveEntity(uint64_t id) 227 | { 228 | DeadIds.push(id); 229 | 230 | for (auto& table : Tables) 231 | table.second->Remove(id); 232 | } 233 | 234 | inline uint64_t ECS::GetNewEntity() 235 | { 236 | uint64_t id = NextId; 237 | if (!DeadIds.empty()) 238 | { 239 | id = DeadIds.top(); 240 | DeadIds.pop(); 241 | } 242 | else 243 | { 244 | NextId++; 245 | } 246 | 247 | return id; 248 | } 249 | #endif //_ECS_IMPLEMENTATION -------------------------------------------------------------------------------- /game/include/test_components.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include "ecs.h" 5 | 6 | #include "raylib.h" 7 | #include "raymath.h" 8 | 9 | #include 10 | #include 11 | 12 | class TransformComponent : public Component 13 | { 14 | public: 15 | DEFINE_COMPONENT(TransformComponent); 16 | 17 | Vector2 Position = { 0,0 }; 18 | float Angle = 0; 19 | 20 | uint64_t Parent = uint64_t(-1); 21 | }; 22 | 23 | class ColorComponent : public Component 24 | { 25 | public: 26 | DEFINE_COMPONENT(ColorComponent); 27 | 28 | Color Tint = WHITE; 29 | Color TintB = PURPLE; 30 | float TintSpeed = 0; 31 | 32 | inline void UpdateColor() 33 | { 34 | TintParam += GetFrameTime() / TintSpeed; 35 | } 36 | 37 | inline Color GetColor() 38 | { 39 | if (TintSpeed == 0) 40 | return Tint; 41 | 42 | if (TintParam >= 1) 43 | { 44 | TintParam = 1; 45 | TintSpeed *= -1; 46 | return TintB; 47 | } 48 | 49 | if (TintParam <= 0) 50 | { 51 | TintParam = 0; 52 | TintSpeed *= -1; 53 | return Tint; 54 | } 55 | 56 | return Color{ 57 | (unsigned char)Lerp(Tint.r,TintB.r, TintParam), 58 | (unsigned char)Lerp(Tint.g,TintB.g, TintParam), 59 | (unsigned char)Lerp(Tint.b,TintB.b, TintParam) , 60 | (unsigned char)Lerp(Tint.a,TintB.a, TintParam) }; 61 | } 62 | protected: 63 | float TintParam = 0; 64 | }; 65 | 66 | class CircleComponent : public Component 67 | { 68 | public: 69 | DEFINE_COMPONENT(CircleComponent); 70 | 71 | float Radius = 0; 72 | }; 73 | 74 | class RectangleComponent : public Component 75 | { 76 | public: 77 | DEFINE_COMPONENT(RectangleComponent); 78 | 79 | Rectangle Bounds = { 0,0,0,0 }; 80 | }; 81 | 82 | class PlayerInputComponent : public Component 83 | { 84 | public: 85 | DEFINE_COMPONENT(PlayerInputComponent); 86 | 87 | float LinearSpeed = 100; 88 | float RotationSpeed = 180; 89 | }; 90 | 91 | class SpinnerComponent : public Component 92 | { 93 | public: 94 | DEFINE_COMPONENT(SpinnerComponent); 95 | 96 | float RotationSpeed = 180; 97 | }; 98 | 99 | class ECS; 100 | 101 | class Collision2dComponent : public Component 102 | { 103 | public: 104 | DEFINE_COMPONENT(Collision2dComponent); 105 | 106 | // does this move or not? static items are not checked against each other 107 | bool IsStatic = false; 108 | 109 | // called when something collides with this 110 | // thisID otherID ECS ref 111 | std::function OnCollide; 112 | }; -------------------------------------------------------------------------------- /game/include/test_systems.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ecs.h" 3 | 4 | // gets input and updates the transform postion based on the speed in the component 5 | class PlayerUpdateSystem : public System 6 | { 7 | public: 8 | SYSTEM_CONSTRUCTOR(PlayerUpdateSystem); 9 | 10 | void Update() override; 11 | }; 12 | 13 | class ColorCyclerSystem : public System 14 | { 15 | public: 16 | SYSTEM_CONSTRUCTOR(ColorCyclerSystem); 17 | 18 | void Update() override; 19 | }; 20 | 21 | // spins a transform based on the speed in the component 22 | class SpinnerSystem : public System 23 | { 24 | public: 25 | SYSTEM_CONSTRUCTOR(SpinnerSystem); 26 | 27 | void Update() override; 28 | }; 29 | 30 | // draws all shapes that have a transform in the color they specify 31 | class RenderSystem : public System 32 | { 33 | public: 34 | SYSTEM_CONSTRUCTOR(RenderSystem); 35 | 36 | void Update() override; 37 | }; 38 | 39 | class Collision2dComponent; 40 | 41 | // detects basic collisions on objects 42 | class CollisionSystem : public System 43 | { 44 | public: 45 | SYSTEM_CONSTRUCTOR(CollisionSystem); 46 | 47 | void Update() override; 48 | 49 | protected: 50 | bool Collision(Collision2dComponent* mover, Collision2dComponent* obstacle); 51 | }; -------------------------------------------------------------------------------- /game/premake5.lua: -------------------------------------------------------------------------------- 1 | 2 | baseName = path.getbasename(os.getcwd()); 3 | 4 | project (baseName) 5 | kind "ConsoleApp" 6 | location "../_build" 7 | targetdir "../_bin/%{cfg.buildcfg}" 8 | 9 | filter "action:vs*" 10 | debugdir "$(SolutionDir)" 11 | 12 | filter {"action:vs*", "configurations:Release"} 13 | kind "WindowedApp" 14 | entrypoint "mainCRTStartup" 15 | 16 | filter{} 17 | 18 | vpaths 19 | { 20 | ["Header Files/*"] = { "include/**.h", "include/**.hpp", "src/**.h", "src/**.hpp", "**.h", "**.hpp"}, 21 | ["Source Files/*"] = {"src/**.c", "src/**.cpp","**.c", "**.cpp"}, 22 | } 23 | files {"**.c", "**.cpp", "**.h", "**.hpp", "../ecs/**.h"} 24 | 25 | includedirs { "./"} 26 | includedirs {"src"} 27 | includedirs {"include"} 28 | includedirs {"../ecs"} 29 | link_raylib() 30 | -- To link to a lib use link_to("LIB_FOLDER_NAME") -------------------------------------------------------------------------------- /game/src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Raylib ECS Example 3 | */ 4 | 5 | #include "raylib.h" 6 | 7 | #include "test_components.h" 8 | #include "test_systems.h" 9 | 10 | #define _ECS_IMPLEMENTATION 11 | #include "ecs.h" 12 | 13 | int main () 14 | { 15 | // set up the window 16 | InitWindow(1280, 800, "ECS Example"); 17 | SetTargetFPS(144); 18 | 19 | // our ECS container 20 | ECS ecs; 21 | 22 | // set up components 23 | ecs.RegisterComponent(); 24 | ecs.RegisterComponent(); 25 | ecs.RegisterComponent(); 26 | ecs.RegisterComponent(); 27 | ecs.RegisterComponent(); 28 | ecs.RegisterComponent(); 29 | ecs.RegisterComponent(); 30 | 31 | // set up systems (updated in order) 32 | ecs.RegisterSystem(); 33 | ecs.RegisterSystem(); 34 | ecs.RegisterSystem(); 35 | ecs.RegisterSystem(); 36 | ecs.RegisterSystem(); 37 | 38 | // set up entities 39 | // spinning entity 40 | uint64_t blockId = ecs.GetNewEntity(); 41 | ecs.GetComponent(blockId)->Position = Vector2{ 800, 400 }; 42 | ColorComponent* color = ecs.GetComponent(blockId); 43 | color->Tint = RED; 44 | color->TintB = ColorAlpha(PURPLE, 0.25f); 45 | color->TintSpeed = 1; 46 | 47 | ecs.GetComponent(blockId)->Bounds = Rectangle{ -50,-50,100,100 }; 48 | ecs.GetComponent(blockId)->RotationSpeed = 90; 49 | 50 | uint64_t child = ecs.GetNewEntity(); 51 | TransformComponent* transform = ecs.GetComponent(child); 52 | transform->Position = Vector2{ 150,0 }; 53 | transform->Parent = blockId; 54 | color = ecs.GetComponent(child); 55 | color->Tint = DARKBLUE; 56 | ecs.GetComponent(child)->Radius = 20; 57 | 58 | // a static collideable entity 59 | uint64_t obstacleId = ecs.GetNewEntity(); 60 | ecs.GetComponent(obstacleId)->Position = Vector2{ 300, 400 }; 61 | ecs.GetComponent(obstacleId)->Tint = DARKGREEN; 62 | ecs.GetComponent(obstacleId)->Bounds = Rectangle{ -40,-40, 80, 80 }; 63 | ecs.GetComponent(obstacleId)->IsStatic = true; 64 | 65 | // player entity 66 | uint64_t playerID = ecs.GetNewEntity(); 67 | ecs.GetComponent(playerID)->Position = Vector2{ 50,50 }; 68 | ecs.GetComponent(playerID)->Tint = BLUE; 69 | ecs.GetComponent(playerID)->Radius = 25; 70 | ecs.GetComponent(playerID)->LinearSpeed = 200; 71 | Collision2dComponent* component = ecs.GetComponent(playerID); 72 | component->OnCollide = [](uint64_t entityId, uint64_t obstacleEntityId, ECS& ecsSystem) 73 | { 74 | ColorComponent* color = ecsSystem.GetComponent(entityId); 75 | if (color->Tint.r > BLUE.r) 76 | color->Tint = BLUE; 77 | else 78 | color->Tint = RED; 79 | }; 80 | 81 | // game loop 82 | while (!WindowShouldClose()) 83 | { 84 | // game loop 85 | BeginDrawing(); 86 | ClearBackground(BLACK); 87 | 88 | // run all systems 89 | ecs.Update(); 90 | 91 | DrawFPS(5, 5); 92 | 93 | EndDrawing(); 94 | } 95 | 96 | // cleanup 97 | CloseWindow(); 98 | return 0; 99 | } -------------------------------------------------------------------------------- /game/src/test_systems.cpp: -------------------------------------------------------------------------------- 1 | #include "test_systems.h" 2 | #include "test_components.h" 3 | #include "ecs.h" 4 | 5 | #include "raylib.h" 6 | #include "raymath.h" 7 | #include "rlgl.h" 8 | 9 | void PlayerUpdateSystem::Update() 10 | { 11 | DoForEachComponent([this](PlayerInputComponent& component) 12 | { 13 | float deltaSpeed = component.LinearSpeed * GetFrameTime(); 14 | 15 | TransformComponent* transform = ECSContainer.TryGetComponent(component.EntityId); 16 | if (!transform) 17 | return; 18 | 19 | Vector2 move = { 0,0 }; 20 | 21 | if (IsKeyDown(KEY_W)) 22 | move.y -= deltaSpeed; 23 | 24 | if (IsKeyDown(KEY_S)) 25 | move.y += deltaSpeed; 26 | 27 | if (IsKeyDown(KEY_A)) 28 | move.x -= deltaSpeed; 29 | 30 | if (IsKeyDown(KEY_D)) 31 | move.x += deltaSpeed; 32 | 33 | transform->Position = Vector2Add(transform->Position, move); 34 | }); 35 | } 36 | 37 | 38 | void PushTransformComponent(const TransformComponent& transform, ECS& ecs) 39 | { 40 | if (transform.Parent != uint64_t(-1)) 41 | { 42 | TransformComponent* parentTransform = ecs.TryGetComponent(transform.Parent); 43 | if (parentTransform) 44 | PushTransformComponent(*parentTransform, ecs); 45 | } 46 | 47 | rlPushMatrix(); 48 | rlTranslatef(transform.Position.x, transform.Position.y, 0); 49 | rlRotatef(transform.Angle, 0, 0, 1); 50 | } 51 | 52 | void PopTransformComponent(const TransformComponent& transform, ECS& ecs) 53 | { 54 | rlPopMatrix(); 55 | 56 | if (transform.Parent != uint64_t(-1)) 57 | { 58 | TransformComponent* parentTransform = ecs.TryGetComponent(transform.Parent); 59 | if (parentTransform) 60 | PopTransformComponent(*parentTransform, ecs); 61 | } 62 | } 63 | 64 | void RenderSystem::Update() 65 | { 66 | DoForEachComponent([this](TransformComponent& component) 67 | { 68 | PushTransformComponent(component, ECSContainer); 69 | 70 | Color tint = WHITE; 71 | ColorComponent* color = ECSContainer.TryGetComponent(component.EntityId); 72 | if (color) 73 | tint = color->GetColor(); 74 | 75 | CircleComponent* circle = ECSContainer.TryGetComponent(component.EntityId); 76 | if (circle) 77 | DrawCircleV(Vector2Zero(), circle->Radius, tint); 78 | 79 | RectangleComponent* rectangle = ECSContainer.TryGetComponent(component.EntityId); 80 | if (rectangle) 81 | DrawRectangleRec(rectangle->Bounds, tint); 82 | 83 | PopTransformComponent(component, ECSContainer); 84 | }); 85 | } 86 | 87 | void SpinnerSystem::Update() 88 | { 89 | DoForEachComponent([this](SpinnerComponent & component) 90 | { 91 | float deltaSpeed = component.RotationSpeed * GetFrameTime(); 92 | 93 | TransformComponent* transform = ECSContainer.TryGetComponent(component.EntityId); 94 | if (!transform) 95 | return; 96 | 97 | transform->Angle += deltaSpeed; 98 | }); 99 | } 100 | 101 | void ColorCyclerSystem::Update() 102 | { 103 | DoForEachComponent([this](ColorComponent& component) 104 | { 105 | component.UpdateColor(); 106 | }); 107 | } 108 | 109 | void CollisionSystem::Update() 110 | { 111 | std::vector staticEntities; 112 | std::vector dynamicEntities; 113 | 114 | DoForEachComponent([this, &staticEntities, &dynamicEntities](Collision2dComponent& component) 115 | { 116 | if (component.IsStatic) 117 | staticEntities.push_back(&component); 118 | else 119 | dynamicEntities.push_back(&component); 120 | }); 121 | 122 | for (Collision2dComponent* mover : dynamicEntities) 123 | { 124 | // check against all the static entities 125 | for (Collision2dComponent* obstacle : staticEntities) 126 | { 127 | if (Collision(mover, obstacle)) 128 | { 129 | if (mover->OnCollide) 130 | mover->OnCollide(mover->EntityId, obstacle->EntityId, ECSContainer); 131 | 132 | if (obstacle->OnCollide) 133 | obstacle->OnCollide(obstacle->EntityId, mover->EntityId, ECSContainer); 134 | } 135 | } 136 | 137 | // check against the other non dynamic entities 138 | for (Collision2dComponent* obstacle : dynamicEntities) 139 | { 140 | if (obstacle != mover && Collision(mover, obstacle)) 141 | { 142 | if (mover->OnCollide) 143 | mover->OnCollide(mover->EntityId, obstacle->EntityId, ECSContainer); 144 | 145 | if (obstacle->OnCollide) 146 | obstacle->OnCollide(obstacle->EntityId, mover->EntityId, ECSContainer); 147 | } 148 | } 149 | } 150 | } 151 | 152 | bool CollisionSystem::Collision(Collision2dComponent* mover, Collision2dComponent* obstacle) 153 | { 154 | if (!mover || !obstacle) 155 | return false; 156 | 157 | TransformComponent* moverTransform = ECSContainer.TryGetComponent(mover->EntityId); 158 | TransformComponent* obstacleTransform = ECSContainer.TryGetComponent(obstacle->EntityId); 159 | if (!moverTransform || !obstacleTransform) 160 | return false; 161 | 162 | 163 | CircleComponent* moverCircle = ECSContainer.TryGetComponent(mover->EntityId); 164 | RectangleComponent* moverRectangle = ECSContainer.TryGetComponent(mover->EntityId); 165 | if (!moverCircle && !moverRectangle) 166 | return false; 167 | 168 | CircleComponent* obstacleCircle = ECSContainer.TryGetComponent(obstacle->EntityId); 169 | RectangleComponent* obstacleRectangle = ECSContainer.TryGetComponent(obstacle->EntityId); 170 | 171 | if (!obstacleCircle && !obstacleRectangle) 172 | return false; 173 | 174 | if (obstacleCircle && moverCircle) // circle circle, easy 175 | return CheckCollisionCircles(moverTransform->Position, moverCircle->Radius, obstacleTransform->Position, obstacleCircle->Radius); 176 | 177 | // utility to help us fix up rectangles in the transform space 178 | auto translateRect = [](const Rectangle& r, const Vector2& pos) 179 | { 180 | return Rectangle{ pos.x + r.x, pos.y + r.y, r.width,r.height }; 181 | }; 182 | 183 | if (obstacleRectangle && moverRectangle) // rect rect, easy 184 | return CheckCollisionRecs(translateRect(moverRectangle->Bounds, moverTransform->Position), translateRect(obstacleRectangle->Bounds, obstacleTransform->Position)); 185 | 186 | if (obstacleRectangle && moverCircle) // rect circle 187 | return CheckCollisionCircleRec(moverTransform->Position, moverCircle->Radius, translateRect(obstacleRectangle->Bounds, obstacleTransform->Position)); 188 | 189 | if (obstacleCircle && moverRectangle) // rect circle 190 | return CheckCollisionCircleRec(obstacleTransform->Position, obstacleCircle->Radius, translateRect(moverRectangle->Bounds, moverTransform->Position)); 191 | 192 | return false; 193 | } 194 | -------------------------------------------------------------------------------- /premake-VisualStudio.bat: -------------------------------------------------------------------------------- 1 | premake5.exe vs2022 || pause 2 | -------------------------------------------------------------------------------- /premake-mingw.bat: -------------------------------------------------------------------------------- 1 | premake5.exe gmake2 || pause 2 | -------------------------------------------------------------------------------- /premake5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raylib-extras/simple_ecs/fe8e4666227758a09c9fe073f66f36b840da506e/premake5 -------------------------------------------------------------------------------- /premake5.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raylib-extras/simple_ecs/fe8e4666227758a09c9fe073f66f36b840da506e/premake5.exe -------------------------------------------------------------------------------- /premake5.lua: -------------------------------------------------------------------------------- 1 | 2 | newoption 3 | { 4 | trigger = "graphics", 5 | value = "OPENGL_VERSION", 6 | description = "version of OpenGL to build raylib against", 7 | allowed = { 8 | { "opengl11", "OpenGL 1.1"}, 9 | { "opengl21", "OpenGL 2.1"}, 10 | { "opengl33", "OpenGL 3.3"}, 11 | { "opengl43", "OpenGL 4.3"} 12 | }, 13 | default = "opengl33" 14 | } 15 | 16 | function string.starts(String,Start) 17 | return string.sub(String,1,string.len(Start))==Start 18 | end 19 | 20 | function link_to(lib) 21 | links (lib) 22 | includedirs ("../"..lib.."/include") 23 | includedirs ("../"..lib.."/" ) 24 | end 25 | 26 | function download_progress(total, current) 27 | local ratio = current / total; 28 | ratio = math.min(math.max(ratio, 0), 1); 29 | local percent = math.floor(ratio * 100); 30 | print("Download progress (" .. percent .. "%/100%)") 31 | end 32 | 33 | function check_raylib() 34 | if(os.isdir("raylib") == false and os.isdir("raylib-master") == false) then 35 | if(not os.isfile("raylib-master.zip")) then 36 | print("Raylib not found, downloading from github") 37 | local result_str, response_code = http.download("https://github.com/raysan5/raylib/archive/refs/heads/master.zip", "raylib-master.zip", { 38 | progress = download_progress, 39 | headers = { "From: Premake", "Referer: Premake" } 40 | }) 41 | end 42 | print("Unzipping to " .. os.getcwd()) 43 | zip.extract("raylib-master.zip", os.getcwd()) 44 | os.remove("raylib-master.zip") 45 | end 46 | end 47 | 48 | workspaceName = path.getbasename(os.getcwd()) 49 | 50 | if (string.lower(workspaceName) == "raylib") then 51 | print("raylib is a reserved name. Name your project directory something else.") 52 | -- Project generation will succeed, but compilation will definitely fail, so just abort here. 53 | os.exit() 54 | end 55 | 56 | workspace (workspaceName) 57 | configurations { "Debug", "Release"} 58 | platforms { "x64", "x86", "ARM64"} 59 | 60 | defaultplatform ("x64") 61 | 62 | filter "configurations:Debug" 63 | defines { "DEBUG" } 64 | symbols "On" 65 | 66 | filter "configurations:Release" 67 | defines { "NDEBUG" } 68 | optimize "On" 69 | 70 | filter { "platforms:x64" } 71 | architecture "x86_64" 72 | 73 | filter { "platforms:Arm64" } 74 | architecture "ARM64" 75 | 76 | filter {} 77 | 78 | targetdir "_bin/%{cfg.buildcfg}/" 79 | 80 | if(os.isdir("game")) then 81 | startproject(workspaceName) 82 | end 83 | 84 | cdialect "C99" 85 | cppdialect "C++17" 86 | check_raylib(); 87 | 88 | include ("raylib_premake5.lua") 89 | 90 | if(os.isdir("game")) then 91 | include ("game") 92 | end 93 | 94 | folders = os.matchdirs("*") 95 | for _, folderName in ipairs(folders) do 96 | if (string.starts(folderName, "raylib") == false and string.starts(folderName, "_") == false and string.starts(folderName, ".") == false) then 97 | if (os.isfile(folderName .. "/premake5.lua")) then 98 | print(folderName) 99 | include (folderName) 100 | end 101 | end 102 | end 103 | -------------------------------------------------------------------------------- /premake5.osx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raylib-extras/simple_ecs/fe8e4666227758a09c9fe073f66f36b840da506e/premake5.osx -------------------------------------------------------------------------------- /raylib_premake5.lua: -------------------------------------------------------------------------------- 1 | 2 | function platform_defines() 3 | defines{"PLATFORM_DESKTOP"} 4 | 5 | filter {"options:graphics=opengl43"} 6 | defines{"GRAPHICS_API_OPENGL_43"} 7 | 8 | filter {"options:graphics=opengl33"} 9 | defines{"GRAPHICS_API_OPENGL_33"} 10 | 11 | filter {"options:graphics=opengl21"} 12 | defines{"GRAPHICS_API_OPENGL_21"} 13 | 14 | filter {"options:graphics=opengl11"} 15 | defines{"GRAPHICS_API_OPENGL_11"} 16 | 17 | filter {"system:macosx"} 18 | disablewarnings {"deprecated-declarations"} 19 | 20 | filter {"system:linux"} 21 | defines {"_GNU_SOURCE"} 22 | -- This is necessary, otherwise compilation will fail since 23 | -- there is no CLOCK_MONOTOMIC. raylib claims to have a workaround 24 | -- to compile under c99 without -D_GNU_SOURCE, but it didn't seem 25 | -- to work. raylib's Makefile also adds this flag, probably why it went 26 | -- unnoticed for so long. 27 | -- It compiles under c11 without -D_GNU_SOURCE, because c11 requires 28 | -- to have CLOCK_MONOTOMIC 29 | -- See: https://github.com/raysan5/raylib/issues/2729 30 | 31 | filter{} 32 | end 33 | 34 | function get_raylib_dir() 35 | if (os.isdir("raylib-master")) then 36 | return "raylib-master" 37 | end 38 | if (os.isdir("../raylib-master")) then 39 | return "raylib-master" 40 | end 41 | return "raylib" 42 | end 43 | 44 | function link_raylib() 45 | links {"raylib"} 46 | 47 | raylib_dir = get_raylib_dir(); 48 | includedirs {"../" .. raylib_dir .. "/src" } 49 | includedirs {"../" .. raylib_dir .."/src/external" } 50 | includedirs {"../" .. raylib_dir .."/src/external/glfw/include" } 51 | platform_defines() 52 | 53 | filter "action:vs*" 54 | defines{"_WINSOCK_DEPRECATED_NO_WARNINGS", "_CRT_SECURE_NO_WARNINGS"} 55 | dependson {"raylib"} 56 | links {"raylib.lib"} 57 | characterset ("MBCS") 58 | 59 | filter "system:windows" 60 | defines{"_WIN32"} 61 | links {"winmm", "kernel32", "opengl32", "gdi32"} 62 | libdirs {"../_bin/%{cfg.buildcfg}"} 63 | 64 | filter "system:linux" 65 | links {"pthread", "GL", "m", "dl", "rt", "X11"} 66 | 67 | filter "system:macosx" 68 | links {"OpenGL.framework", "Cocoa.framework", "IOKit.framework", "CoreFoundation.framework", "CoreAudio.framework", "CoreVideo.framework"} 69 | 70 | filter{} 71 | end 72 | 73 | function include_raylib() 74 | raylib_dir = get_raylib_dir(); 75 | includedirs {"../" .. raylib_dir .."/src" } 76 | includedirs {"../" .. raylib_dir .."/src/external" } 77 | includedirs {"../" .. raylib_dir .."/src/external/glfw/include" } 78 | platform_defines() 79 | 80 | filter "action:vs*" 81 | defines{"_WINSOCK_DEPRECATED_NO_WARNINGS", "_CRT_SECURE_NO_WARNINGS"} 82 | 83 | filter{} 84 | end 85 | 86 | project "raylib" 87 | kind "StaticLib" 88 | 89 | platform_defines() 90 | 91 | location "_build" 92 | language "C" 93 | targetdir "_bin/%{cfg.buildcfg}" 94 | 95 | filter "action:vs*" 96 | defines{"_WINSOCK_DEPRECATED_NO_WARNINGS", "_CRT_SECURE_NO_WARNINGS"} 97 | characterset ("MBCS") 98 | 99 | filter{} 100 | 101 | raylib_dir = get_raylib_dir(); 102 | print ("Using raylib dir " .. raylib_dir); 103 | includedirs {raylib_dir .. "/src", raylib_dir .. "/src/external/glfw/include" } 104 | vpaths 105 | { 106 | ["Header Files"] = { raylib_dir .. "/src/**.h"}, 107 | ["Source Files/*"] = { raylib_dir .. "/src/**.c"}, 108 | } 109 | files {raylib_dir .. "/src/*.h", raylib_dir .. "/src/*.c"} 110 | 111 | removefiles {raylib_dir .. "/src/rcore_android.c", raylib_dir .. "/src/rcore_template.c", raylib_dir .. "/src/rcore_drm.c", raylib_dir .. "/src/rcore_web.c", raylib_dir .."/src/rcore_desktop.c"} 112 | 113 | filter { "system:macosx", "files:" .. raylib_dir .. "/src/rglfw.c" } 114 | compileas "Objective-C" 115 | 116 | filter{} 117 | -------------------------------------------------------------------------------- /resources/LICENSE: -------------------------------------------------------------------------------- 1 | Assets license. 2 | -------------------------------------------------------------------------------- /resources/ambient.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raylib-extras/simple_ecs/fe8e4666227758a09c9fe073f66f36b840da506e/resources/ambient.ogg -------------------------------------------------------------------------------- /resources/coin.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raylib-extras/simple_ecs/fe8e4666227758a09c9fe073f66f36b840da506e/resources/coin.wav -------------------------------------------------------------------------------- /resources/mecha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raylib-extras/simple_ecs/fe8e4666227758a09c9fe073f66f36b840da506e/resources/mecha.png --------------------------------------------------------------------------------