Last active
December 9, 2019 06:28
-
-
Save r-lyeh-archived/7462948 to your computer and use it in GitHub Desktop.
Component-entity system in 16 lines of C++11. extract from kult engine (https://github.com/r-lyeh/kult)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <map> // Component-entity system in 16 lines of C++11. 2013 rlyeh, MIT licensed | |
#include <set> // Code fragment from kult engine - https://github.com/r-lyeh/kult | |
enum {JOIN,MERGE,EXCLUDE};using set=std::set<unsigned>;template<typename T> set&system(){ | |
static set entities;return entities;}template<typename T,int MODE>set subsystem(const set | |
&B){set newset;const set&A=system<T>();if(MODE==MERGE){newset=B;for(auto&id:A)newset.ins\ | |
ert(id);}else if(MODE==EXCLUDE){newset=B;for(auto&id:A)newset.erase(id);}else if(A.size() | |
<B.size()){for(auto&id:A)if(B.find(id)!=B.end())newset.insert(id);}else{for(auto&id:B)if( | |
A.find(id)!=A.end())newset.insert(id);}return newset;}template<typename T>std::map<unsig\ | |
ned,T>&components(){static std::map<unsigned,T>objects;return objects;}template<typename\ | |
T>bool has(unsigned id){return components<T>().find(id)!=components<T>().end();}templat\ | |
e<typename T>decltype(T::value_type)&get(unsigned id){static decltype(T::value_type)inva\ | |
lid,reset;return has<T>(id)?components<T>()[id].value_type:invalid=reset;}template<typen\ | |
ame T>decltype(T::value_type)&add(unsigned id){return system<T>().insert(id),components<T | |
>()[id]=components<T>()[id],get<T>(id);}template<typename T>bool del(unsigned id){return\ | |
add<T>(id),components<T>().erase(id),system<T>().erase(id),!has<T>(id);}template<typena\ | |
me T,int>struct component{T value_type;}; | |
// example: | |
#include <cassert> | |
#include <string> | |
#include <iostream> | |
// gamedev types and constants | |
typedef std::pair<int, int> vec2i; | |
typedef std::pair<float,float> vec2f; | |
const vec2f zero = { 0.f, 0.f }, one = { 1.f, 1.f }; | |
// component aliases | |
using friendly = component< bool, 'team' >; | |
using health = component< int, 'heal' >; | |
using mana = component< int, 'mana' >; | |
using coins = component< int, 'coin' >; | |
using name = component< std::string, 'name' >; | |
using position = component< vec2f, 'pos2' >; | |
// sugars | |
template<class T, class U> std::set< unsigned > join() { return subsystem<T,JOIN>( system<U>() ); } | |
template<class T, class U, class V> std::set< unsigned > join() { return subsystem<T,JOIN>( join<U,V>() ); } | |
template<class T, class U, class V, class W> std::set< unsigned > join() { return subsystem<T,JOIN>( join<U,V,W>() ); } | |
template<class T> std::set< unsigned > exclude( const set &B ) { return subsystem<T,EXCLUDE>(B); } | |
int main() | |
{ | |
// entities | |
int none = 0, player = 1, enemy = 2; | |
// components | |
assert( !has<name>(player) ); | |
assert( !has<position>(player) ); | |
assert( !has<coins>(enemy) ); | |
assert( !has<health>(enemy) ); | |
add<name>(player) = "Hero"; | |
add<position>(player) = zero; | |
add<health>(player) = 100; | |
add<coins>(player) = 200; | |
add<mana>(player) = 4000; | |
add<friendly>(player) = true; | |
add<name>(enemy) = "Orc"; | |
add<position>(enemy) = one; | |
add<health>(enemy) = 200; | |
add<coins>(enemy) = 50; | |
add<mana>(enemy) = 10; | |
assert( get<health>(player) == 100 ); // :> | |
assert( has<name>(player) ); | |
assert( !has<vec2i>(player) ); | |
assert( has<position>(player) ); | |
assert( has<health>(player) ); | |
assert( get<name>(player) == "Hero" ); | |
assert( get<position>(player) == zero ); | |
assert( get<health>(player) == 100 ); | |
// systems; here we intersect a system of all elements with <name> and <position>. | |
assert( (join<name, position>().size() == 2) ); | |
// systems; render game state | |
auto display = []() { | |
std::cout << "- "; | |
for( auto &id : join<name, coins, health, position>() ) { | |
std::cout | |
<< get<name>(id) << " at " | |
<< "(" << get<position>(id).first << "," << get<position>(id).second << ")" | |
<< " " << get<health>(id) << "HP" | |
<< " " << get<coins>(id) << "$, "; | |
} | |
std::cout << std::endl; | |
}; | |
display(); | |
// systems; simulate movement | |
for( auto &id : join<name, position>() ) { | |
std::cout << get<name>(id) << " says: im moving!" << std::endl; | |
vec2f &pos = get<position>(id); | |
pos.first += 10; | |
pos.second ++; | |
} | |
// systems; simulate a spell bomb in entities of any type | |
for( auto &id : system<mana>() ) { | |
std::cout << "spellboomb!!!" << std::endl; | |
get<mana>(id) -= 50; | |
} | |
// systems; simulate a powerup (+$100) for all players | |
for( auto &id : join<name, coins, friendly>() ) { | |
get<coins>(id) += 100; | |
std::cout << get<name>(id) << " says: money! :)" << std::endl; | |
} | |
// systems; simulate a poison (-50%HP) to all entities that are not friendly (so enemies) | |
for( auto &id : exclude<friendly>( join<name, health>() ) ) { | |
get<health>(id) *= 0.5; | |
std::cout << get<name>(id) << " says: ugh! poisoned :(" << std::endl; | |
} | |
display(); | |
assert( get<health>(player) == 100+0 ); | |
assert( get<health>(enemy) == 200/2 ); | |
assert( get<coins>(player) == 200+100 ); | |
assert( get<coins>(enemy) == 50+0 ); | |
assert( get<mana>(player) == 4000-50 ); | |
assert( get<mana>(enemy) == 10-50 ); | |
assert( del<position>(player) ); | |
assert( !has<position>(player) ); | |
assert( del<name>(player) ); | |
assert( !has<name>(player) ); | |
assert( (join<name, position>().size() == 1) ); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment