Bevy Commands
In Bevy, each Command
represents a mutation we want to apply to the state of our world.
Each command, eventually executes a function that receives a mutable reference to your World
that it uses to change its state.
Because each of your commands needs this mutable reference, its not safe to run while other systems might be running in parallel.
So Bevy schedules them to all run together inside one system in the order they are added to the CommandQueue
, which we do through the Commands
system parameter.
Once per game loop this system is run and each command applies itself to your world.
Scheduling commands
To execute a command, first we have to schedule them.
We don't manually add them to a CommandQueue
but instead use a system parameter: Commands
which is the public API to the queue.
fn spawn_an_entity(
mut commands: Commands,
) {
commands.spawn_empty();
}
This is the most simple command we can write. It will simply spawn an empty Entity
without any components.
To despawn that same entity we would also use a command:
fn despawn_an_entity(
mut commands: Commands,
query: Query<Entity>
) {
for entity in query.iter() {
commands.entity(entity).despawn();
}
}
Commands
is the system parameter holding all of the methods to schedule changes, including custom commands you can write yourself.
Its important to note that neither command actually executes here. Instead they were queued to run the next time all your commands run together. This will happen any time we transition to the next schedule during the apply_deferred
system that was added by the DefaultPlugins
.
Spawning components and bundles
All changes to your world state should come from these commands, including spawning and despawning.
#[derive(Component)]
struct Player;
fn spawn_player(mut commands: Commands) {
// Here we are `Commands`
commands
.spawn_empty()
// We are now an `EntityCommands`
// for the entity we just spawned
.insert(Player)
.insert(Transform::default());
}
After we spawn_empty
we actually get back an EntityCommands
for the particular entity we spawned.
EntityCommands
allow us to insert
components onto the new entity. Insert itself will return EntityCommands
which lets us chain these calls together.
Spawning an entity with some kind of component is so common that there is a shorter version of this:
fn spawn_player_shorter(mut commands: Commands) {
commands.spawn(Player).insert(Transform::default());
}
Here instead of spawn_empty
we use spawn
which takes a component to add to the newly spawned entity.
This also passes back EntityCommands
which we can use to further insert components on the particular entity we spawned.
In fact, spawn
can also take a Bundle
of components:
#[derive(Bundle)]
struct PlayerBundle {
player: Player,
transform: Transform,
}
fn spawn_with_bundle(mut commands: Commands) {
commands.spawn(PlayerBundle {
player: Player,
transform: Transform::default(),
});
}
And because tuples of components are already Bundle
types this can be shortened even further to:
fn spawn_player_shortest(mut commands: Commands) {
let bundle = (Player, Transform::default());
commands.spawn(bundle);
}
Commands are delayed until the next schedule
When you use the system parameter Commands
you are enqueuing your commands to run when we transition to the next Schedule
.
A Schedule
is one part of the lifecycle of a frame. These schedules are collections of systems and instructions on exactly how to run them.
For example, Startup
and Update
are both common schedules you have likely seen before.
If we have only one stage, even if we have systems that run before or after each other, the actual commands will not be applied to the world until the next frame.
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Update, take_the_castle.after(raising_the_gates))
.run();
}
Lets say we have a system take_the_castle
that runs after another system raise_the_gates
.
If you're smart, you'd think that by the time you go to take_the_castle
you have already raised the gates.
This however, is not the case, because when take_the_castle
runs our commands have only been enqueued and not executed.
Since each command requires exclusive access to the World
(we are mutating state), all queued commands are applied in sequence when the builtin apply_deferred
system runs.
The solution here would be to ensure that we run our apply_deferred
after we have enqueued our commands but before we run take_the_castle
:
fn main() {
let raise_the_gates = apply_deferred.after(raise_the_gates);
let ordered_systems = (raise_the_gates, take_the_castle).chain();
App::new()
.add_systems(Update, ordered_systems)
.run();
}
The chain
call here just ensures that the first system apply_deferred.after(raise_the_gates)
will run and then take_the_castle
will run right after.
Custom commands
We can implement our own commands to make our logic more understandable.
Imagine we have have a game that spawns a big planet in the middle of the screen. We might have a system that looks like:
#[derive(Component)]
struct Planet {
radius: f32,
}
#[derive(Bundle)]
struct PlanetBundle {
planet: Planet,
transform: Transform,
}
impl PlanetBundle {
fn new(radius: f32) -> Self {
Self {
planet: Planet { radius },
transform: Transform::default(),
}
}
}
// Spawns a blue planet in the middle of the screen
fn spawn_planet_manually(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
let size = 100.0;
let shape = Circle::new(size);
let color = Color::srgb(0.0, 0.0, 1.0);
commands.spawn((
PlanetBundle::new(size),
ColorMesh2dBundle {
mesh: meshes.add(shape).into(),
material: materials.add(color),
..Default::default()
},
));
}
However, for every system that spawns a planet, we will need our meshes
and materials
resources.
Its also not entirely clear without our comment and system name what exactly is happening here.
To shorten our parameter lists, and encapsulate our logic, we can create our own custom commands by implementing Command
on a struct holding our parameters:
// Our custom command
struct SpawnPlanet {
radius: f32,
position: Vec2,
}
use bevy::sprite::MaterialMesh2dBundle;
impl Command for SpawnPlanet {
fn apply(self, world: &mut World) {
// Resource scope works by removing the resource from the world
// and then running our closure. This lets us mutably borrow
// the world safely multiple times
let mesh_handle =
world.resource_scope(|_world, mut meshes: Mut<Assets<Mesh>>| {
let circle = Circle::new(self.radius);
meshes.add(circle)
});
let color_material = world.resource_scope(
|_world, mut materials: Mut<Assets<ColorMaterial>>| {
let blue = Color::srgb(0.0, 0.0, 1.0);
materials.add(blue)
},
);
world.spawn((
PlanetBundle::new(self.radius),
MaterialMesh2dBundle {
mesh: mesh_handle.into(),
material: color_material,
transform: Transform::from_translation(self.position.extend(0.)),
..default()
},
));
}
}
That's a lot more code, but now our ability to spawn planets has become much more simplified:
fn spawn_planet(mut commands: Commands) {
commands.add(SpawnPlanet {
radius: 100.,
position: Vec2::new(0., 0.),
});
}
Extending the commands API
Sometimes it would be even nicer if we could extend the API of our commands. Something like commands.spawn_planet(Vec2::Zero, 100.0)
might work even better. To do so we can use a trait extension.
Trait extensions allow you to add methods to an existing type via a new implementation. We can see an example in bevy_hierarchy
:
// https://github.com/bevyengine/bevy/blob/13d46a528ac6e8c2e08e8b9ba436abb9baaefefc/crates/bevy_hierarchy/src/hierarchy.rs#L84
// Trait that holds functions for despawning recursively down the transform hierarchy
pub trait DespawnRecursiveExt {
// Despawns the provided entity alongside all descendants.
fn despawn_recursive(self);
// Despawns all descendants of the given entity.
fn despawn_descendants(&mut self);
}
impl<'w, 's, 'a> DespawnRecursiveExt for EntityCommands<'w, 's, 'a> {
// Despawns the provided entity and its children.
fn despawn_recursive(mut self) {
let entity = self.id();
self.commands().add(DespawnRecursive { entity });
}
fn despawn_descendants(&mut self) {
let entity = self.id();
self.commands().add(DespawnChildrenRecursive { entity });
}
}
Testing commands
We can write tests for our custom commands and manually push
them to a CommandQueue
and finally trigger an apply
:
use bevy::ecs::system::Command;
use bevy::prelude::*;
struct MyCommand;
impl Command for MyCommand {
fn apply(self, world: &mut World) {
info!("Hello, world!");
}
}
#[cfg(test)]
mod tests {
use bevy::ecs::world::CommandQueue;
#[test]
fn test_my_command() {
let mut world = World::default();
let mut command_queue = CommandQueue::default();
// We could manually add our commands to the queue
command_queue.push(MyCommand);
// We can apply the commands to a given world:
command_queue.apply(&mut world);
}
}