-
-
Save Midiman/7523144 to your computer and use it in GitHub Desktop.
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 24 lines of C++11. 2013 rlyeh, MIT licensed. | |
#include <set> // Code fragment from kult engine - https://github.com/r-lyeh/kult | |
enum SUBSYSTEM_MODE { JOIN = 0, MERGE = 1, EXCLUDE = 2 }; | |
template<typename T> std::set<unsigned> &system() | |
{ static std::set<unsigned> entities; return entities; } | |
template<typename T, int MODE> std::set<unsigned> subsystem( const std::set<unsigned> &B ) | |
{ std::set<unsigned> newset; const std::set<unsigned> &A = system<T>(); // union first, | |
/**/ if (MODE == MERGE) { newset = B; for( auto &id : A ) newset.insert(id); } // then difference, | |
else if (MODE == EXCLUDE) { newset = B; for( auto &id : A ) newset.erase (id); } // then intersection | |
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< unsigned, 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(); } | |
template<typename T> decltype(T::value_type) &get( unsigned id ) | |
{ static decltype(T::value_type) invalid, reset; | |
return has<T>(id) ? components<T>()[id].value_type : invalid = reset; } | |
template<typename 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<typename T, int NAME> 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 std::set<unsigned> &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