docs.rs failed to build zero_ecs_build-0.3.4
Please check the
build logs for more information.
See
Builds for ideas on how to fix a failed build,
or
Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault,
open an issue.
Zero ECS
Zero ECS is an Entity Component System that is written with 4 goals
- Only use zero cost abstractions - no use of dyn and Box and stuff zero-cost-abstractions.
- No use of unsafe rust code.
- Be very user friendly. The user should write as little boilerplate as possible.
- Be very fast
It achieves this by generating all code at compile time, using a combination of macros and build scripts.
This is version 0.3.*.
It is almost a complete rewrite from 0.2.*. And has breaking changes.
Instructions
Add the dependency:
cargo add zero_ecs
Your Cargo.toml should look something like this:
[dependencies]
zero_ecs = "0.3.*"
Using the ECS
Use
use zero_ecs::*;
Components
Components are just regular structs.
#[derive(Default)]
struct Position(f32, f32);
#[derive(Default)]
struct Velocity(f32, f32);
It is normal to "tag" entities with a component in ECS to be able to single out those entities in systems.
#[derive(Default)]
struct EnemyComponent;
#[derive(Default)]
struct PlayerComponent;
Entities & World
Entities are a collection of components. Use the #[entity] attribute to define them.
#[entity]
#[derive(Default)]
struct EnemyEntity {
position: Position,
velocity: Velocity,
enemy_component: EnemyComponent,
}
#[entity]
#[derive(Default)]
struct PlayerEntity {
position: Position,
velocity: Velocity,
player_component: PlayerComponent,
}
Define the world using the ecs_world! macro. Must include all entities.
World and entities must be defined in the same crate.
ecs_world!(EnemyEntity, PlayerEntity);
You can now instantiate the world like this:
let mut world = World::default();
And create entities like this:
let player_entity = world.create(PlayerEntity {
position: Position(55.0, 165.0),
velocity: Velocity(100.0, 50.0),
..Default::default()
});
Systems
Systems run the logic for the application. There are two types of systems: #[system] and #[system_for_each].
system_for_each
#[system_for_each] calls the system once for each successful query. This is the simplest way to write systems.
#[system_for_each(World)]
fn print_positions(position: &Position) {
println!("x: {}, y: {}", position.0, position.1);
}
Systems can also mutate and accept resources:
struct DeltaTime(f32);
#[system_for_each(World)]
fn apply_velocity(position: &mut Position, velocity: &Velocity, delta_time: &DeltaTime) {
position.0 += velocity.0 * delta_time.0;
position.1 += velocity.1 * delta_time.0;
}
system
The default way of using systems. Needs to accept world, 0-many queries and optional resources.
#[system(World)]
fn print_enemy_positions(world: &World, query: Query<(&Position, &EnemyComponent)>) {
world.with_query(query).iter().for_each(|(pos, _)| {
println!("x: {}, y: {}", pos.0, pos.1);
});
}
Creating entities and calling systems
fn main() {
let delta_time = DeltaTime(1.0);
let mut world = World::default();
for i in 0..10 {
world.create(EnemyEntity {
position: Position(i as f32, 5.0),
velocity: Velocity(0.0, 1.0),
..Default::default()
});
world.create(PlayerEntity {
position: Position(5.0, i as f32),
velocity: Velocity(1.0, 0.0),
..Default::default()
});
}
world.apply_velocity(&delta_time);
world.print_positions();
world.print_enemy_positions();
}
More advanced
Destroying entities
To destroy entities, query for &Entity to identify them. You can't destroy entities from within an iteration.
#[system(World)]
fn collide_enemy_and_players(
world: &mut World,
players: Query<(&Entity, &Position, &PlayerComponent)>,
enemies: Query<(&Entity, &Position, &EnemyComponent)>,
) {
let mut entities_to_destroy: HashSet<Entity> = HashSet::new();
world
.with_query(players)
.iter()
.for_each(|(player_entity, player_position, _)| {
world
.with_query(enemies)
.iter()
.for_each(|(enemy_entity, enemy_position, _)| {
if (player_position.0 - enemy_position.0).abs() < 3.0
&& (player_position.1 - enemy_position.1).abs() < 3.0
{
entities_to_destroy.insert(*player_entity);
entities_to_destroy.insert(*enemy_entity);
}
});
});
for entity in entities_to_destroy {
world.destroy(entity);
}
}
Get & At
get is identical to query but takes an Entity.
at is identical to query but takes an index.
Let's say you wanted an entity that follows a player:
struct CompanionComponent {
target_entity: Option<Entity>,
}
#[entity]
struct CompanionEntity {
position: Position,
companion_component: CompanionComponent,
}
We can't simply iterate through the companions and get the target position because we can only have one borrow if the borrow is mutable. The solution is to iterate using index, only borrowing what we need for a short time:
#[system(World)]
fn companion_follow(
world: &mut World,
companions: Query<(&mut Position, &CompanionComponent)>,
positions: Query<&Position>,
) {
for companion_idx in 0..world.with_query_mut(companions).len() {
if let Some(target_position) = world
.with_query_mut(companions)
.at_mut(companion_idx) .and_then(|(_, companion)| companion.target_entity) .and_then(|companion_target_entity| {
world
.with_query(positions)
.get(companion_target_entity) .map(|p: &Position| (p.0, p.1)) })
{
if let Some((companion_position, _)) =
world.with_query_mut(companions).at_mut(companion_idx)
{
companion_position.0 = target_position.0;
companion_position.1 = target_position.1;
}
}
}
}
Manual queries
You can create queries outside systems using make_query!. Should rarely be used.
fn print_player_positions(world: &World) {
make_query!(PlayerPositionsQuery, Position, PlayerComponent);
world
.with_query(Query::<PlayerPositionsQuery>::new())
.iter()
.for_each(|(pos, _)| {
println!("x: {}, y: {}", pos.0, pos.1);
});
}