Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Example time api #10204

Merged
merged 15 commits into from
Oct 28, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 38 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,11 @@ bevy_asset = ["bevy_internal/bevy_asset"]
bevy_audio = ["bevy_internal/bevy_audio"]

# Provides cameras and other basic render pipeline features
bevy_core_pipeline = ["bevy_internal/bevy_core_pipeline", "bevy_asset", "bevy_render"]
bevy_core_pipeline = [
"bevy_internal/bevy_core_pipeline",
"bevy_asset",
"bevy_render",
]

# Plugin for dynamic loading (using [libloading](https://crates.io/crates/libloading))
bevy_dynamic_plugin = ["bevy_internal/bevy_dynamic_plugin"]
Expand All @@ -83,7 +87,12 @@ bevy_gilrs = ["bevy_internal/bevy_gilrs"]
bevy_gltf = ["bevy_internal/bevy_gltf", "bevy_asset", "bevy_scene", "bevy_pbr"]

# Adds PBR rendering
bevy_pbr = ["bevy_internal/bevy_pbr", "bevy_asset", "bevy_render", "bevy_core_pipeline"]
bevy_pbr = [
"bevy_internal/bevy_pbr",
"bevy_asset",
"bevy_render",
"bevy_core_pipeline",
]

# Provides rendering functionality
bevy_render = ["bevy_internal/bevy_render"]
Expand All @@ -98,7 +107,12 @@ bevy_sprite = ["bevy_internal/bevy_sprite", "bevy_render", "bevy_core_pipeline"]
bevy_text = ["bevy_internal/bevy_text", "bevy_asset", "bevy_sprite"]

# A custom ECS-driven UI framework
bevy_ui = ["bevy_internal/bevy_ui", "bevy_core_pipeline", "bevy_text", "bevy_sprite"]
bevy_ui = [
"bevy_internal/bevy_ui",
"bevy_core_pipeline",
"bevy_text",
"bevy_sprite",
]

# winit window and input backend
bevy_winit = ["bevy_internal/bevy_winit"]
Expand All @@ -113,7 +127,11 @@ trace_chrome = ["trace", "bevy_internal/trace_chrome"]
trace_tracy = ["trace", "bevy_internal/trace_tracy"]

# Tracing support, with memory profiling, exposing a port for Tracy
trace_tracy_memory = ["trace", "bevy_internal/trace_tracy", "bevy_internal/trace_tracy_memory"]
trace_tracy_memory = [
"trace",
"bevy_internal/trace_tracy",
"bevy_internal/trace_tracy_memory",
]

# Tracing support
trace = ["bevy_internal/trace"]
Expand Down Expand Up @@ -1398,27 +1416,39 @@ description = "Illustrates creating custom system parameters with `SystemParam`"
category = "ECS (Entity Component System)"
wasm = false

# Time
[[example]]
name = "time"
path = "examples/ecs/time.rs"
path = "examples/time/time.rs"

[package.metadata.example.time]
name = "Time handling"
description = "Explains how Time is handled in ECS"
category = "ECS (Entity Component System)"
category = "Time"
wasm = false

[[example]]
name = "virtual_time"
path = "examples/time/virtual_time.rs"

[package.metadata.example.virtual_time]
name = "Virtual time"
description = "Shows how `Time<Virtual>` can be used to pause, resume, slow down and speed up a game."
category = "Time"
wasm = false

[[example]]
name = "timers"
path = "examples/ecs/timers.rs"
path = "examples/time/timers.rs"
doc-scrape-examples = true

[package.metadata.example.timers]
name = "Timers"
description = "Illustrates ticking `Timer` resources inside systems and handling their state"
category = "ECS (Entity Component System)"
category = "Time"
wasm = false


# Games
[[example]]
name = "alien_cake_addict"
Expand Down
11 changes: 9 additions & 2 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ git checkout v0.4.0
- [Scene](#scene)
- [Shaders](#shaders)
- [Stress Tests](#stress-tests)
- [Time](#time)
- [Tools](#tools)
- [Transforms](#transforms)
- [UI (User Interface)](#ui-user-interface)
Expand Down Expand Up @@ -233,8 +234,6 @@ Example | Description
[System Closure](../examples/ecs/system_closure.rs) | Show how to use closures as systems, and how to configure `Local` variables by capturing external state
[System Parameter](../examples/ecs/system_param.rs) | Illustrates creating custom system parameters with `SystemParam`
[System Piping](../examples/ecs/system_piping.rs) | Pipe the output of one system into a second, allowing you to handle any errors gracefully
[Time handling](../examples/ecs/time.rs) | Explains how Time is handled in ECS
[Timers](../examples/ecs/timers.rs) | Illustrates ticking `Timer` resources inside systems and handling their state

## Games

Expand Down Expand Up @@ -326,6 +325,14 @@ Example | Description
[Text Pipeline](../examples/stress_tests/text_pipeline.rs) | Text Pipeline benchmark
[Transform Hierarchy](../examples/stress_tests/transform_hierarchy.rs) | Various test cases for hierarchy and transform propagation performance

## Time

Example | Description
--- | ---
[Time handling](../examples/time/time.rs) | Explains how Time is handled in ECS
[Timers](../examples/time/timers.rs) | Illustrates ticking `Timer` resources inside systems and handling their state
[Virtual time](../examples/time/virtual_time.rs) | Shows how `Time<Virtual>` can be used to pause, resume, slow down and speed up a game.

## Tools

Example | Description
Expand Down
File renamed without changes.
File renamed without changes.
219 changes: 219 additions & 0 deletions examples/time/virtual_time.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
//! Shows how `Time<Virtual>` can be used to pause, resume, slow down
//! and speed up a game.

use std::time::Duration;

use bevy::{
input::common_conditions::input_just_pressed, prelude::*,
time::common_conditions::on_real_timer,
};

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(
Update,
(
move_virtual_time_sprites,
move_real_time_sprites,
toggle_pause.run_if(input_just_pressed(KeyCode::Space)),
change_time_speed::<1>.run_if(input_just_pressed(KeyCode::Up)),
change_time_speed::<-1>.run_if(input_just_pressed(KeyCode::Down)),
(update_virtual_time_info_text, update_real_time_info_text)
// update the texts on a timer to make them more readable
// `on_timer` run condition uses `Virtual` time meaning it's scaled
// and would result in the UI updating at different intervals based
// on `Time<Virtual>::relative_speed` and `Time<Virtual>::is_paused()`
.run_if(on_real_timer(Duration::from_millis(250))),
),
)
.run();
}

/// `Real` time related marker
#[derive(Component)]
struct RealTime;

/// `Virtual` time related marker
#[derive(Component)]
struct VirtualTime;

/// Setup the example
fn setup(mut commands: Commands, asset_server: Res<AssetServer>, mut time: ResMut<Time<Virtual>>) {
// start with double `Virtual` time resulting in one of the sprites moving at twice the speed
// of the other sprite which moves based on `Real` (unscaled) time
time.set_relative_speed(2.);

commands.spawn(Camera2dBundle::default());

let virtual_color = Color::GOLD;
let sprite_scale = Vec2::splat(0.5).extend(1.);
let texture_handle = asset_server.load("branding/icon.png");

// the sprite moving based on real time
commands.spawn((
SpriteBundle {
texture: texture_handle.clone(),
transform: Transform::from_scale(sprite_scale),
..default()
},
RealTime,
));

// the sprite moving based on virtual time
commands.spawn((
SpriteBundle {
texture: texture_handle,
sprite: Sprite {
color: virtual_color,
..default()
},
transform: Transform {
scale: sprite_scale,
translation: Vec3::new(0., -160., 0.),
..default()
},
..default()
},
VirtualTime,
));

// info UI
let font_size = 40.;

commands
.spawn(NodeBundle {
style: Style {
display: Display::Flex,
justify_content: JustifyContent::SpaceBetween,
width: Val::Percent(100.),
position_type: PositionType::Absolute,
top: Val::Px(0.),
padding: UiRect::all(Val::Px(20.0)),
..default()
},
..default()
})
.with_children(|builder| {
// real time info
builder.spawn((
TextBundle::from_section(
"",
TextStyle {
font_size,
..default()
},
),
RealTime,
));

// keybindings
builder.spawn(
TextBundle::from_section(
"CONTROLS\nUn/Pause: Space\nSpeed+: +/Up\nSpeed-: -/Down",
SecretPocketCat marked this conversation as resolved.
Show resolved Hide resolved
TextStyle {
font_size,
color: Color::rgb(0.85, 0.85, 0.85),
..default()
},
)
.with_text_alignment(TextAlignment::Center),
);

// virtual time info
builder.spawn((
TextBundle::from_section(
"",
TextStyle {
font_size,
color: virtual_color,
..default()
},
)
.with_text_alignment(TextAlignment::Right),
VirtualTime,
));
});
}

/// Move sprites using `Real` (unscaled) time
fn move_real_time_sprites(
mut sprite_query: Query<&mut Transform, (With<Sprite>, With<RealTime>)>,
// `Real` time which is not scaled or paused
time: Res<Time<Real>>,
) {
for mut transform in sprite_query.iter_mut() {
// move roughly half the screen in a `Real` second
// when the time is scaled the speed is going to change
// and the sprite will stay still the the time is paused
transform.translation.x = get_sprite_translation_x(time.elapsed_seconds());
}
}

/// Move sprites using `Virtual` (scaled) time
fn move_virtual_time_sprites(
mut sprite_query: Query<&mut Transform, (With<Sprite>, With<VirtualTime>)>,
// the default `Time` is either `Time<Virtual>` in regular systems
// or `Time<Fixed>` in fixed timestep systems so `Time::delta()`,
// `Time::elapsed()` will return the appropriate values either way
time: Res<Time>,
) {
for mut transform in sprite_query.iter_mut() {
// move roughly half the screen in a `Virtual` second
// when time is scaled using `Time<Virtual>::set_relative_speed` it's going
// to move at diffent pace and the sprite will stay still when time is
SecretPocketCat marked this conversation as resolved.
Show resolved Hide resolved
// `Time<Virtual>::is_paused()`
transform.translation.x = get_sprite_translation_x(time.elapsed_seconds());
}
}

fn get_sprite_translation_x(elapsed: f32) -> f32 {
// move 500px in a second (either real or virtual seconds based on where elapsed comes from)
SecretPocketCat marked this conversation as resolved.
Show resolved Hide resolved
elapsed.sin() * 500.
}

/// Update the speed of `Time<Virtual>.` by `DELTA`
fn change_time_speed<const DELTA: i8>(mut time: ResMut<Time<Virtual>>) {
let time_speed = (time.relative_speed() + DELTA as f32)
.round()
.clamp(0.25, 5.);

// set the speed of the virtual time to speed it up or slow it down
time.set_relative_speed(time_speed);
}

/// pause or resume `Relative` time
fn toggle_pause(mut time: ResMut<Time<Virtual>>) {
if time.is_paused() {
time.unpause();
} else {
time.pause();
}
}

/// Update the `Real` time info text
fn update_real_time_info_text(time: Res<Time<Real>>, mut query: Query<&mut Text, With<RealTime>>) {
for mut text in &mut query {
text.sections[0].value = format!(
"REAL TIME\nElapsed: {:.1}\nDelta: {:.5}\n",
time.elapsed_seconds(),
time.delta_seconds(),
);
}
}

/// Update the `Virtual` time info text
fn update_virtual_time_info_text(
time: Res<Time<Virtual>>,
mut query: Query<&mut Text, With<VirtualTime>>,
) {
for mut text in &mut query {
text.sections[0].value = format!(
"VIRTUAL TIME\nElapsed: {:.1}\nDelta: {:.5}\nSpeed: {:.2}",
time.elapsed_seconds(),
time.delta_seconds(),
time.relative_speed()
);
}
}
Loading