Engine Architecture
Meep is highly modular. At its core it consists of system, components and plugins.
A typical game will require many systems and objects to work together to create a compelling experience for the user. To facilitate this - we use concept of entities, components and systems or ECS for short.
Meep makes no assumptions about your game, if you run it in default configurations - it will have no systems at all and no components. This helps keep install sizes down and prevent compatibility issues. It's your game - you know best what it should look and feel like.
That said - meep does come with a plethora of systems that you can import and make use of for common features like 3D meshes, particles, sounds etc.
Component
A component defines some aspect of an object in your game. For example, if you want to have combat in your game - you
might create a Health
component like so:
class Health {
current: number
maximum: number
}
Beauty of ECS is that this component can be attached to any entity in your world such as a goblin, a house or a rock.
You might want to track position of your game objects by adding a Position
component
class Position {
x: number
y: number
}
Entity
Entities are just integer IDs used to group components together. An easy way to think of entities and components is to imagine a table, where each row is an entity and each column represents some component class
Entity | Health | Position |
---|---|---|
1 | ✓ | ✓ |
3 | ✓ | |
6 |
In the example above we have 3 entities: 1
, 3
and 6
.
Entity 1
has 2 components: Health
and Position
. Entity 3
has just the Position
. Finally, entity 6
has no component
at all, it is still a valid entity though.
Entities along with their components are stored in and managed by the EntityComponentDataset
, and their representation
is actually very close to that of a table in a database.
System
A system defines some behavior, such as playing sounds, moving objects on screen, communicating with a game server. Here's a sample system that let the player control a character:
// component used to indicate which objects we should be able to move
class Controllable {
canWalkForward: boolean = true
canWalkBack: boolean = true
walkingSpeed: number = 10
}
class MovementControlSystem extends System<Position, Controllable> {
// this will be called by the engine for every simulation tick with `time_delta` being in seconds
update(time_delta: number): void {
// bind keyboard keys 🡐 and 🡒
const keys = engine.devices.keyboard.keys;
const right = keys.right_arrow;
const left = keys.left_arrow;
const dataset = this.entityManager.dataset;
const updateEntity = (position: Position, control: Controllable) => {
// update a single entity that has both `Position` and `Controllable`
if (right.is_down && control.canWalkForward) {
position.x += time_delta * control.walkingSpeed;
}
if (left.is_down && control.canWalkBack) {
position.x -= time_delta * control.walkingSpeed;
}
};
dataset.traverseEntities([ Position, Controllable ], updateEntity);
}
}
Systems are managed by EntityManager
, they can be added and removed at runtime. This means that you can completely
re-configure how your game works without having to restart the engine.