Bevy Commands
Commands let us change the state of the World inside our systems without taking exclusive access of it.
If a system took a &mut World directly, it would block every other system from running, even the ones that don't touch the same data. So instead we hand our changes off and Bevy applies them later.
Each Command represents one mutation we want to apply. Every system that takes the Commands parameter owns its own CommandQueue, and Bevy applies the queued commands in the order they were added.
Spawning components
We spawn components and entities through commands. To do so we use the Commands system parameter.
fn spawn_an_entity(mut commands: Commands) {
commands.spawn_empty();
}
This will simply spawn an empty Entity without any components when it runs together with your other commands.
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();
}
}
Its important to note: neither command actually executes here. Instead, they are queued, and Bevy applies them later by calling System::apply_deferred on each system that queued commands.
#[derive(Component)]
#[require(Transform)]
struct Player;
fn spawn_player(mut commands: Commands) {
// Here we are `Commands`
commands
.spawn_empty()
// Now we are 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 returns a &mut 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 bundle 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.
Tuples of components are themselves Bundle types, so we can pass a tuple straight to spawn:
fn spawn_player_shortest(mut commands: Commands) {
let bundle = (Player, Transform::default());
commands.spawn(bundle);
}
Bundles are still useful for grouping components, but since 0.15 Bevy has also had required components. Because Player declares #[require(Transform)], we can spawn just the Player component and a Transform is added automatically:
#[derive(Component)]
#[require(Transform)]
struct Player;
fn spawn_player_required(mut commands: Commands) {
commands.spawn(Player);
}
These required components get their defaults added unless we override them by providing our own. This is much less boilerplate and much easier to keep in our heads when trying to think how to spawn components that depend on each other. For grouping components into reusable scenes, Bevy 0.19 also adds BSN.
Commands execute at sync points
When you use the system parameter Commands you are enqueuing your commands. They don't run right away, because each command needs exclusive access to the World and would block every other system from running in parallel.
Instead, Bevy waits for a sync point and then applies all the queued commands in order. In Bevy a sync point is an ApplyDeferred system, and Bevy inserts them into your schedule for you.
Bevy's Main schedule drives each frame:
- It runs the
Startupschedule once - Then the per-frame schedules such as
UpdateandFixedUpdate
In between each of these schedules, Bevy applies all the queued commands in the ApplyDeferred system.
When a schedule is built, Bevy walks the schedule's dependency graph and inserts an ApplyDeferred after any system that uses deferred params (like Commands) and is depended on by another system. This makes sure your commands are applied before the other systems that depend on them run.
Lets say we have a system take_the_castle that runs after another system raising_the_gates:
fn raise_the_gates(mut commands: Commands) {
commands.insert_resource(GatesRaised);
}
fn take_the_castle(gates_are_raised: Option<Res<GatesRaised>>) {
if gates_are_raised.is_some() {
info!("The gates are raised, we can take the castle!");
}
}
Because raise_the_gates queues commands (that is, it takes the Commands parameter), the schedule ends up looking like this:
raise_the_gates -> ApplyDeferred -> take_the_castle
The ApplyDeferred in the middle is what Bevy inserted automatically. So by the time take_the_castle runs, the gates have been raised.
Commands are also applied at the end of every schedule run, so even a system with no dependents still has its changes visible to the rest of the frame.
Delayed commands
If we want to queue commands that run sometime in the future, Bevy provides the DelayedCommandsExt trait (in bevy_time, exported through the prelude):
fn delayed_spawn_enemy(mut commands: Commands) {
commands.delayed().secs(1.0).spawn(Enemy);
}
Each delay gets its own CommandQueue, and when the DelayedCommands value is dropped those queues are stored as DelayedCommandQueue components. A system added by TimePlugin checks them against the default clock every PreUpdate, so delayed commands only work when TimePlugin is present (it is part of DefaultPlugins).
We can also use several delays together. Because spawn/spawn_empty allocate the entity immediately, we can queue more commands against it later. Here an entity is spawned after half a second and becomes an Enemy a second later:
fn delayed_spawn_then_insert_enemy(mut commands: Commands) {
let mut delayed = commands.delayed();
let entity = delayed.secs(0.5).spawn_empty().id();
delayed.secs(1.5).entity(entity).insert(Enemy);
}
There is currently no built-in way to cancel delayed commands.
Custom commands
We can implement our own custom commands to make our logic more understandable.
struct MyCommand;
impl Command for MyCommand {
type Out = ();
fn apply(self, world: &mut World) {
info!("Hello, world!");
}
}
The apply method is handed the World which lets you do anything you want. It also returns Self::Out (here ()), which is how a command can report success or failure.
#[derive(Component)]
#[require(Transform)]
struct Planet {
radius: f32,
}
// Our custom command
struct SpawnPlanet {
radius: f32,
position: Vec2,
}
fn planet(position: Vec3, radius: f32) -> impl Scene {
bsn! {
#Planet
Transform::from_translation(position)
Mesh2d(asset_value(Circle::new(radius)))
MeshMaterial2d<ColorMaterial>(asset_value(Color::srgb(0.0, 0.0, 1.0)))
}
}
impl Command for SpawnPlanet {
type Out = ();
fn apply(self, world: &mut World) {
world
.spawn_scene(planet(self.position.extend(0.), self.radius))
.unwrap();
}
}
Instead of our regular system parameters, apply just takes &mut World, so we reach into the world directly.
Here we spawn a scene with world.spawn_scene(...). Resources would be accessed with world.resource::<T>() or world.resource_mut::<T>(). The bsn! macro is Bevy's new scene notation, covered in BSN.
In the end we get a nice reusable command that we can queue up from any system:
fn spawn_planet(mut commands: Commands) {
commands.queue(SpawnPlanet {
radius: 100.,
position: Vec2::new(0., 0.),
});
}
When you queue a command, Commands::queue_handled and Commands::queue_silenced let you choose how its output is handled.
Read more about custom commands
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 SpawnPlanetExt {
fn spawn_planet(&mut self, position: Vec2, radius: f32) -> &mut Self;
}
impl SpawnPlanetExt for Commands<'_, '_> {
fn spawn_planet(&mut self, position: Vec2, radius: f32) -> &mut Self {
self.queue(SpawnPlanet { radius, position });
self
}
}
We could then use our custom API directly on the Commands system parameter:
fn spawn_planet_with_extension(mut commands: Commands) {
commands.spawn_planet(Vec2::ZERO, 100.0);
}
Testing commands
We can write tests for our custom commands and manually push them to a CommandQueue and finally trigger an apply:
use bevy::prelude::*;
struct MyCommand;
impl Command for MyCommand {
type Out = ();
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);
}
}