This is my spin on developing a (proper) Entity Component System, inspired by Bevy's ECS.
The world is composed of Entities, which act are integers associated with Components: a component is just a plain struct with some data, on which Systems operate.
e.g from the 'simple' example . Define some components
#[derive(Debug)]
struct EntityName(String);
struct Bullet {
direction: [f32; 2],
}
// A struct without any members is called a tag structure
struct Player;
#[derive(Default, Debug)]
struct Transform {
position: [f32; 2],
}
- Define how the systems interact with the components
// A query is an iterator over the entities with the specified components
fn update_bullet_position(query: Query<(&Bullet, &mut Transform)>) {
for (bullet, transform) in query.iter() {
transform.position[0] += bullet.direction[0];
transform.position[1] += bullet.direction[1];
}
}
// You can also get the entity itself
fn print_transform_system(query: Query<(Entity, &EntityName, &Transform)>) {
for (entity, entity_name, transform) in query.iter() {
println!(
"Transform of entity {entity:?} with name '{}' at {:?}",
entity_name.0, transform
);
}
}
fn print_player_position(query: Query<(&Player, Entity, &EntityName, &Transform)>) {
for (_, entity, entity_name, transform) in query.iter() {
println!(
"Transform of player {entity:?} with name '{}' at {:?}",
entity_name.0, transform
);
}
}
- Create a world and spawn the entities
let mut world = World::new();
{
let bullet_entity = world.new_entity();
world.add_component(
bullet_entity,
Bullet {
direction: [1.0, 0.0],
},
);
world.add_component(bullet_entity, Transform::default());
world.add_component(bullet_entity, EntityName("Bullet 0".to_owned()));
}
{
let bullet_entity = world.new_entity();
world.add_component(
bullet_entity,
Bullet {
direction: [-1.0, 0.0],
},
);
world.add_component(bullet_entity, Transform::default());
world.add_component(bullet_entity, EntityName("Bullet 1".to_owned()));
}
{
let player_entity = world.new_entity();
world.add_component(player_entity, Player);
world.add_component(player_entity, Transform::default());
world.add_component(player_entity, EntityName("Player".to_owned()));
}
- Schedule the systems for execution
// Use a label to group systems together
let example = "example";
world.add_system(example, update_bullet_position);
world.add_system(example, print_transform_system);
world.add_system(example, print_player_position);
- Run the systems
// In real code this should belong in an event loop
for i in 0..3 {
println!("Frame {i}");
world.update(example);
println!("\n\n");
}
The execution graph for the sample above is the following
Don't
I was curious on how ECSs work, and i wanted to challenge myself on implementing my own ECS for my homegrown game engine.
Also, i made it for the keks
- Michele 'skypjack' Caini's blog, which describes the choices he made behind his EnTT ECS;
- Bevy's ECS, which is an actual, production ready, ECS;
- Ratysz's 'ECS scheduler thoughts' post