Game foundation system (C++11)
Stuff that i use to make games.
Lightweight & simple entity component system (c++11) havily inspired by artemis entity system framework implemented in Java.
Here is some links to read about this concept:
- Some articles on t-machine
- Entity Systems are the future of MMORPG blog series by Adam Martin
- gameprogrammingpatterns.com
Artemis framework C++ ports:
Other C++ entity systems:
Very simple implementation that provides subscribe
& emit
methods.
Vector2<T>
akaPoint2<T>
Point2f
,Point2i
,Size2i
,Size2f
Color4<T>
akaColor4f
Rectangle<T>
Polygon
Let's define a component that represents a position & rotation in 2D space:
struct Position: public Component {
Position(const Vector2f& vector, const float rotation):
vector{vector}, rotation{rotation} {}
void move(const Vector2f& velocity) {
vector += velocity;
}
void rotate(const float speed) {
rotation += speed;
if (rotation < 0.0f)
rotation += 360.0f;
else if (rotation > 360.0f)
rotation -= 360.0f;
}
Vector2f vector;
float rotation;
};
Next we need a motion component to represent a velocity & dumping + helper method to apply dumping:
struct Motion: public Component {
Motion(const Vector2f& vel, const float angVel, const float& damp):
velocity{vel}, angularVelocity{angVel}, damping{damp} {}
void damp(const float rotation, const float delta) {
if (damping <= 0.0F) return;
auto xd = abs(cos(rotation) * damping * delta);
auto yd = abs(sin(rotation) * damping * delta);
velocity.x += getDampingValue(velocity.x, xd);
velocity.y += getDampingValue(velocity.y, yd);
}
Vector2f velocity;
float angularVelocity;
float damping;
private:
float getDampingValue(const float coord, const float val) {
if (coord > val)
return -val;
else if (coord < -val)
return val;
else
return -coord;
}
};
So a movement system will look like this:
class MovementSystem: public System {
public:
virtual void initialize() override {
watchComponents<Position, Motion>();
}
virtual void processEntity(Entity* entity) override {
auto position = entity.getComponent<Position>();
auto motion = entity.getComponent<Motion>();
auto dt = getDelta();
position->move(motion->velocity, dt);
position->rotate(motion->angularVelocity, dt);
motion->damp(position->rotation, dt);
}
};
Note that systems should be created before adding components to entities, here is why: [1], [2], [3] Be carefull not to add the same component to different entities.
// before main loop
auto world = new World();
auto entityManager = world.getEntityManager();
auto systemManager = world.getSystemManager();
// systems should be created before entities
systemManager->create<MovementSystem>();
systemManager->create<RenderingSystem>();
systemManager->create<CollisionSystem>();
systemManager->create<InputSystem>();
auto asteriod = entityManager->create();
asteriod->addToGroup("asteriods");
asteroid->addComponents({
new Collision(radius),
new Position(Vector2f(x, y), 0.0F),
new Motion(Vector2f(dx, dy), vel, damp),
new Display(AsteroidView(numPoints))
});
auto player = entityManager->create();
player->setTag("player");
player->addComponents({
new Collision(radius),
new Position(Vector2f(cx, cy), 0.0F),
new Motion(Vector2f(dx, dy), vel, damp),
new Display(PlayerView()),
new Gun(),
new GunControl(),
new MotionControl()
});
// somewhere in a game loop
world.setDelta(delta);
world.process();
class FooSystem: public System {
public:
virtual void initialize() override {
// there are 3 ways for a system to process entities
watchComponents<FooComponent, BarComponent>(); // 1
watchTags({"tag-foo", "tag2"}); // 2
watchGroups({"enemy", "bar-group"}); // 3
// 1 - entity contains a component of type
// 2 - entity has a tag
// 3 - entity belongs to group
// if any of the conditions above is true
// then entity will be processed by the system
}
virtual void processEntity(Entity* entity) override {
auto foo = entity.getComponent<FooComponent>();
// ... and here goes entity processing code ...
}
};
Subscription is implemented using bitsets, look here to see the details.
TODO
So if i need to handle explosions i could write smth like this:
class ExplosionListener {
public:
ExplosionListener(EventBus& eventBus) {
eventBus.subscribe<ExplosionEvent>(this, &EventListener::onExplosion);
}
void onExplosion(const ExplosionEvent& event) {
// do smth with it
}
};
Later, somewhere deep in a game logic...
eventBus.emit(ExplosionEvent(...));
--
Here is how i can use it with ECS to handle keyboard events.
First i'll define a KeyEvent struct:
struct KeyEvent: public Event {
KeyEvent(const int keyCode, const char keyChar):
keyCode{keyCode}, keyChar{keyChar} {}
int keyCode;
char keyChar;
};
Level scene emits the event:
void LevelScene::onKey(const int keyCode, const char keyChar) {
world->getEventBus()->emit(KeyEvent(keyCode, keyChar));
}
Than i can subscribe to it in the system:
class BarSystem: public System {
public:
virtual void initialize() override {
getEventBus()->subscribe<KeyEvent>(this, &BarSystem::onKey);
}
void onKey(const KeyEvent& e) {
// handle key event somehow
}
};
I'm using catch cuz of its simplicity.
Here is some example components & systems.
- build a static library:
make
(remove -DDEBUG flags for a release version) - run tests:
make test
- run the example:
make ticket