Lets say you’ve got a simple system that spawns something in your game:
fn spawn_boid(mut commands: Commands, assets: Res<BoidAssets>) {
commands.spawn((
BoidBundle::new(0., 0.),
MaterialMesh2dBundle {
mesh: assets.mesh.clone().into(),
material: assets.material.clone(),
..default()
},
));
}
We can extract out our logic into a much more testable Command
by using custom
commands:
pub struct SpawnBoid {
pub position: Vec2,
}
impl SpawnBoid {
pub fn random() -> Self {
let mut rng = rand::thread_rng();
let x = rng.gen_range(-200.0..200.0);
let y = rng.gen_range(-200.0..200.0);
Self {
position: Vec2::new(x, y),
}
}
}
impl Command for SpawnBoid {
fn apply(self, world: &mut World) {
let assets = world.get_resource::<BoidAssets>();
if let Some(assets) = assets {
world.spawn((
BoidBundle::new(self.position.x, self.position.y),
MaterialMesh2dBundle {
mesh: assets.mesh.clone().into(),
material: assets.material.clone(),
..default()
},
));
}
}
}
fn setup(mut commands: Commands) {
for _ in 0..100 {
commands.add(SpawnBoid::random());
}
}
We define custom commands by implementing Command
which expects us to define
apply
to affect our World
when commands are scheduled to run.
Instead of your normal system parameters you have direct mutable access to the
World
.
To call our commands we simply call Commands::add
and pass the struct that
implements Command
with whatever options we want.
This can be a great way to break up complicated logic for creating nested
entities, especially with UI nodes. It also makes the logic much easier to test
as all we need to do is pass in a World
we can create in our tests and observe
the changes.
When you are using a World
directly, some things can be somewhat less
ergonomic. For example instead of having a BoidAssets
resource, if we wanted
to create the mesh and material in the command we could use WorldCell
access:
let cell = world.cell();
let mut meshes = cell.get_resource_mut::<Assets<Mesh>>().unwrap();
let mut materials = cell.get_resource_mut::<Assets<ColorMaterial>>().unwrap();
let mesh = meshes.add(Mesh::from(Circle::new(BOID_SIZE)));
let material = materials.add(ColorMaterial::from(Color::rgb(1., 1., 1.)));
This allows mutable access to multiple resources without running into problems borrowing the world mutably twice.
As a safer alternative there is World::resource_scope
which can give safer
scoped access to our resources:
let mesh = world.resource_scope(|_world, mut meshes: Mut<Assets<Mesh>>| {
meshes.add(Mesh::from(Circle::new(BOID_SIZE)))
});
let material = world.resource_scope(|_world, mut materials: Mut<Assets<ColorMaterial>>| {
materials.add(ColorMaterial::from(Color::rgb(1., 1., 1.)))
});
We are also not just limited to world based Command
. If instead we wanted to
implement commands for a specific entity we could:
struct RemoveEntity;
impl EntityCommand for RemoveEntity {
fn apply(self, id: Entity, world: &mut World) {
world.despawn(id);
}
}
fn cleanup_things(
mut commands: Commands,
query: Query<Entity>
) {
for entity in &query {
commands.get_entity(entity).unwrap().add(RemoveEntity);
}
}