Files
bevy/examples/ecs/delayed_commands.rs
Runi-c 1fdf4267ae Add a DelayedCommands helper to support arbitrary delayed commands (#23090)
# Objective

- A generalized mechanism for "doing something later" is desirable for
many games, especially when it comes to gameplay logic and VFX.
- Fixes https://github.com/bevyengine/bevy/issues/15129
- Closes #20155

## Solution

- Build off the work in
https://github.com/bevyengine/bevy/pull/20155#issuecomment-3702483127,
especially @laundmo's comment.
- Add a `DelayedCommands` helper obtainable via `commands.delayed()`
that owns `CommandQueue`s and hands out new `Commands` bound to them.
- When the `DelayedCommands` helper is dropped, push spawn commands onto
the host `Commands` to spawn the queues as `DelayedCommandQueue`
entities.
- The entities are ticked by a new system added by `TimePlugin`. When
the timer fires, the queue is submitted onto that system's `Commands`.

## Testing

- Added a new test in `bevy_time` and it seems to work.
- I'm not very familiar with doing hacky things like using `Drop` like
this and would therefore appreciate careful review and guidance if
changes are requested.

---

## Showcase

```rust
fn my_cool_system(mut commands: Commands) {
    // fairly unobtrusive one-line delayed spawn
    commands.delayed().secs(0.1).spawn(DummyComponent);

    // the DelayedCommands can be stored to reuse more tersely
    let mut delayed = commands.delayed();
    // allocation happens immediately so you can even queue
    // further operations on entities that aren't spawned yet
    let entity = delayed.secs(0.5).spawn_empty().id();
    delayed.secs(0.7).entity(entity).insert(DummyComponent);

    // `delayed.secs` and `delayed.duration` both simply return a
    // `Commands` rebound to the stored `CommandQueue`, so you can additionally
    // just store that and reuse it to queue multiple commands with the same delay
    let mut in_1_sec = delayed.duration(Duration::from_secs_f32(1.0));
    in_1_sec.spawn(DummyComponent);
    in_1_sec.spawn(DummyComponent);
    in_1_sec.spawn(DummyComponent);
}
```

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2026-02-24 23:29:32 +00:00

61 lines
1.8 KiB
Rust

//! This example demonstrates how to send commands which will take effect after a period of time.
//!
//! We've chosen to demonstrate this effect through the creation of a grid of clickable,
//! with "ripples" created when you click.
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, spawn)
.add_observer(click)
.run();
}
#[derive(Component)]
struct BlinkySquare;
const SQUARE_SIZE: Vec2 = Vec2::splat(45.0);
fn spawn(mut commands: Commands) {
commands.spawn(Camera2d);
for x in -5..=5 {
for y in -5..=5 {
commands.spawn((
BlinkySquare,
Transform::from_xyz(x as f32 * 50.0, y as f32 * 50.0, 0.0),
Sprite::from_color(Color::BLACK, SQUARE_SIZE),
));
}
}
}
fn click(
click: On<Pointer<Click>>,
mut commands: Commands,
squares: Query<(Entity, &Transform), With<BlinkySquare>>,
cameras: Query<(&Camera, &GlobalTransform)>,
) {
let (camera, camera_transform) = cameras.single().unwrap();
let mut delayed = commands.delayed();
for (entity, transform) in squares.iter() {
// convert the pointer position to world position
let mouse_world_pos = camera
.viewport_to_world_2d(camera_transform, click.pointer_location.position)
.unwrap();
// delay the blinkiness by distance to cursor
let dist = mouse_world_pos.distance(transform.translation.truncate());
let delay = dist / 1000.0;
delayed
.secs(delay)
.entity(entity)
.insert(Sprite::from_color(Color::WHITE, SQUARE_SIZE));
delayed
.secs(delay + 0.1)
.entity(entity)
.insert(Sprite::from_color(Color::BLACK, SQUARE_SIZE));
}
}