Getty Ritter d2e1a7a720 Support trailing commas | 5 years ago | |
---|---|---|
src | 5 years ago | |
.gitignore | 5 years ago | |
Cargo.toml | 5 years ago | |
README.md | 5 years ago |
Many systems defined with specs are relatively mechanical in terms of what they do, and require some redundant structure. For example, a system that moves entities according to their velocity if they have not experiences a collision might look like this:
// define a type so we can implement the system for that type
struct Physics;
// provide the System impl
impl<'a> specs::System<'a> for Physics {
// we need read access to the velocity and collision, and write access to
// the entity's position
type SystemData = (
specs::ReadStorage<'a, Velocity>,
specs::ReadStorage<'a, Collision>,
specs::WriteStorage<'a, Position>,
);
// we take an instance of systemdata as the argument, which we destruct immediately
fn run(&mut self, (velocity, collision, mut position): Self::SystemData) {
// but then join on it to find all entities that contain velocity, collision, and position
for (vel, col, pos) in (&velocity, &collision, &mut position).join() {
// and run something over all of them
if !col.has_collision {
pos.x += vel.dx;
pos.y += vel.dy;
}
}
}
}
This macro makes defining systems like these easier: the above system can in its entirety be replaced by the macro invocation
system!{
Physics(vel: Velocity, col: Collision, mut pos: Position) {
if !col.has_collision {
pos.x += vel.dx;
pos.y += vel.dy;
}
}
}
The body of the "system" is executed in a loop over all the entities that implement the described components, and the presence or absence of mut
dictates whether we want to use WriteStorage
or ReadStorage
. The end result is a unit struct Physics
that already has an operationally identical System
implementation.
Several systems might also require some final cleanup'code: for example, clearing the component storage for a specific component. For example, the following system uses damage.clear()
after the loop in order to remove the SufferDamage
component from every entity that has it:
pub struct DamageSystem;
impl<'a> System<'a> for DamageSystem {
type SystemData = (
WriteStorage<'a, CombatStats>,
WriteStorage<'a, SufferDamage>,
);
fn run(&mut self, (mut stats, mut damage): Self::SystemData) {
for (mut stats, damage) in (&mut stats, &damage).join() {
stats.hp -= damage.amount;
}
damage.clear();
}
}
The system!
macro allows for a finally
block that executes after the loop, in which the same bound names refer to the storage for the component rather than the component itself, which means the above system can be written as:
system! {
DamageSystem (mut stats: CombatStats, mut damage: SufferDamage) {
// damage has type SufferDamage
stats.hp -= damage.amount;
} finally {
// damage has type WriteStorage<SufferDamage>
damage.clear();
}
}
This macro also special-cases the Entity
type, so that you can loop over all entities.
To get access to resources, you can use the resource
or resource mut
keywords. These will be represented as ReadExpect
and WriteExpect
resources in the relevant SystemData
, and will not be looped over in the loop of the macro. Due to implementation restrictions, a resource
cannot be the last thing in the argument list, so it's best to keep resource
s first.
system! {
Move(resource keys: KeySet, _con: Controlled, mut vel: Velocity) {
vel.dx = 0.0;
vel.dy = 0.0;
if keys.contains(&winit::VirtualKeyCode::W) {
vel.dy -= 2.0;
}
if keys.contains(&winit::VirtualKeyCode::A) {
vel.dx -= 2.0;
}
if keys.contains(&winit::VirtualKeyCode::S) {
vel.dy += 2.0;
}
if keys.contains(&winit::VirtualKeyCode::D) {
vel.dx += 2.0;
}
}
}
The system_impl!
macro is similar to system!
but does not automatically loop a body over the joined components. This will result in less terse but more flexible system definitions that can e.g. loop over multiple sets of components. For example, the above DamageSystem
system could be equivalently written as
system_impl! {
DamageSystem (mut stats: CombatStats, mut damage: SufferDamage) {
for (damage, mut stats) in (&damage, &mut stats).join() {
stats.hp -= damage.amount;
}
damage.clear();
}
}