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.
Instructions
Create a new project
Add the dependencies
Your Cargo.toml should look something like this:
Create build.rs
Edit build.rs
to call the zero_ecs's build generation code.
use *;
This will generate the entity component system based on the component, entities and systems in main.rs. It accepts a glob so you can use wild cards.
use *;
Using the ECS
Include ECS
In main.rs
Include the ECS like so:
include!;
Components
Define some components:
Position and velocity has x and y
;
;
It is normal to "tag" entities with a component in ECS to be able to single out those entities in systems.
;
;
Entities
Entities are a collection of components, they may also be referred to as archetypes, or bundles, or game objects. Note that once "in" the ECS. An Entity is simply an ID that can be copied.
In our example, we define an enemy and a player, they both have position and velocity but can be differentiated by their "tag" components.
Systems
Systems run the logic for the application. They can accept references, mutable references and queries.
In our example we can create a system that simply prints the position of all entities
Explained:
- world: &World - Since the system doesn't modify anything, it can be an immutable reference
- query: Query<&Position> - We want to query the world for all positions
- world.with_query(query).iter() - creates an iterator over all Position components
Creating entities and calling system
In our fn main
change it to create 10 enemies and 10 players,
Also add the systems_main(&world);
to call all systems.
Running the program now, will print the positions of the entities.
More advanced
Continuing our example
mutating systems
Most systems will mutate the world state and needs additional resources, like texture managers, time managers, input managers etc. A good practice is to group them in a Resources struct. (But Not nescessary)
We also have to change the main function to include resources in the call.
let resources = Resources ;
systems_main;
Destroying entities
Let's say we want to create a rule that if player and enemies get within 3 units of eachother they should both be destroyed. This is how we might implement that:
Get & At entities
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. This is how you could implement that:
Define a component for the companion
Define the Companion Entity. It has a position and a companion component:
Now we need to write the companion system. For every companion we need to check if it has a target. If it has a target we need to check if target exists (it could have been deleted). If the target exists we get the value of target's position and set the companion's position with that value.
We need to query for companions and their position as mutable. And we need to query for every entity that has a position. This means a companion could technically follow it self.
Implementation: We can't simply iterate through the companions, get the target position and update the position because we can only have one borrow if the borrow is mutable (unless we use unsafe code).
We can do what we did with destroying entities, but it will be slow.
The solution is to iterate using index, only borrowing what we need for a short time:
TODO:
- Re use IDs of deleted entities