mirror of
https://github.com/bevyengine/bevy
synced 2024-11-25 14:10:19 +00:00
Allow ordering variable timesteps around fixed timesteps (#14881)
# Objective - Fixes #14873, see that issue for a whole lot of context ## Solution - Add a blessed system set for this stuff. See [this Discord discussion](https://discord.com/channels/691052431525675048/749335865876021248/1276262931327094908). Note that the gizmo systems, [LWIM](https://github.com/Leafwing-Studios/leafwing-input-manager/pull/522/files#diff-9b59ee4899ad0a5d008889ea89a124a7291316532e42f9f3d6ae842b906fb095R154) and now a new plugin I'm working on are all already ordering against `run_fixed_main_schedule`, so having a dedicated system set should be more robust and hopefully also more discoverable. --- ## ~~Showcase~~ ~~I can add a little video of a smooth camera later if this gets merged :)~~ Apparently a release note is not needed, so I'll leave it out. See the changes in the fixed timestep example for usage showcase and the video in #14873 for a more or less accurate video of the effect (it does not use the same solution though, so it is not quite the same) ## Migration Guide [run_fixed_main_schedule](https://docs.rs/bevy/latest/bevy/time/fn.run_fixed_main_schedule.html) is no longer public. If you used to order against it, use the new dedicated `RunFixedMainLoopSystem` system set instead. You can replace your usage of `run_fixed_main_schedule` one for one by `RunFixedMainLoopSystem::FixedMainLoop`, but it is now more idiomatic to place your systems in either `RunFixedMainLoopSystem::BeforeFixedMainLoop` or `RunFixedMainLoopSystem::AfterFixedMainLoop` Old: ```rust app.add_systems( RunFixedMainLoop, some_system.before(run_fixed_main_schedule) ); ``` New: ```rust app.add_systems( RunFixedMainLoop, some_system.in_set(RunFixedMainLoopSystem::BeforeFixedMainLoop) ); ``` --------- Co-authored-by: Tau Gärtli <git@tau.garden>
This commit is contained in:
parent
f1f07bec09
commit
c92ee31779
6 changed files with 185 additions and 30 deletions
|
@ -34,7 +34,8 @@ pub mod prelude {
|
||||||
app::{App, AppExit},
|
app::{App, AppExit},
|
||||||
main_schedule::{
|
main_schedule::{
|
||||||
First, FixedFirst, FixedLast, FixedPostUpdate, FixedPreUpdate, FixedUpdate, Last, Main,
|
First, FixedFirst, FixedLast, FixedPostUpdate, FixedPreUpdate, FixedUpdate, Last, Main,
|
||||||
PostStartup, PostUpdate, PreStartup, PreUpdate, SpawnScene, Startup, Update,
|
PostStartup, PostUpdate, PreStartup, PreUpdate, RunFixedMainLoop,
|
||||||
|
RunFixedMainLoopSystem, SpawnScene, Startup, Update,
|
||||||
},
|
},
|
||||||
sub_app::SubApp,
|
sub_app::SubApp,
|
||||||
Plugin, PluginGroup,
|
Plugin, PluginGroup,
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
use crate::{App, Plugin};
|
use crate::{App, Plugin};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
schedule::{ExecutorKind, InternedScheduleLabel, Schedule, ScheduleLabel},
|
schedule::{
|
||||||
|
ExecutorKind, InternedScheduleLabel, IntoSystemSetConfigs, Schedule, ScheduleLabel,
|
||||||
|
SystemSet,
|
||||||
|
},
|
||||||
system::{Local, Resource},
|
system::{Local, Resource},
|
||||||
world::{Mut, World},
|
world::{Mut, World},
|
||||||
};
|
};
|
||||||
|
@ -75,6 +78,11 @@ pub struct First;
|
||||||
pub struct PreUpdate;
|
pub struct PreUpdate;
|
||||||
|
|
||||||
/// Runs the [`FixedMain`] schedule in a loop according until all relevant elapsed time has been "consumed".
|
/// Runs the [`FixedMain`] schedule in a loop according until all relevant elapsed time has been "consumed".
|
||||||
|
/// If you need to order your variable timestep systems
|
||||||
|
/// before or after the fixed update logic, use the [`RunFixedMainLoopSystem`] system set.
|
||||||
|
///
|
||||||
|
/// Note that in contrast to most other Bevy schedules, systems added directly to
|
||||||
|
/// [`RunFixedMainLoop`] will *not* be parallelized between each other.
|
||||||
///
|
///
|
||||||
/// See the [`Main`] schedule for some details about how schedules are run.
|
/// See the [`Main`] schedule for some details about how schedules are run.
|
||||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
@ -126,8 +134,8 @@ pub struct FixedLast;
|
||||||
|
|
||||||
/// The schedule that contains systems which only run after a fixed period of time has elapsed.
|
/// The schedule that contains systems which only run after a fixed period of time has elapsed.
|
||||||
///
|
///
|
||||||
/// The exclusive `run_fixed_main_schedule` system runs this schedule.
|
/// This is run by the [`RunFixedMainLoop`] schedule. If you need to order your variable timestep systems
|
||||||
/// This is run by the [`RunFixedMainLoop`] schedule.
|
/// before or after the fixed update logic, use the [`RunFixedMainLoopSystem`] system set.
|
||||||
///
|
///
|
||||||
/// Frequency of execution is configured by inserting `Time<Fixed>` resource, 64 Hz by default.
|
/// Frequency of execution is configured by inserting `Time<Fixed>` resource, 64 Hz by default.
|
||||||
/// See [this example](https://github.com/bevyengine/bevy/blob/latest/examples/time/time.rs).
|
/// See [this example](https://github.com/bevyengine/bevy/blob/latest/examples/time/time.rs).
|
||||||
|
@ -288,7 +296,16 @@ impl Plugin for MainSchedulePlugin {
|
||||||
.init_resource::<MainScheduleOrder>()
|
.init_resource::<MainScheduleOrder>()
|
||||||
.init_resource::<FixedMainScheduleOrder>()
|
.init_resource::<FixedMainScheduleOrder>()
|
||||||
.add_systems(Main, Main::run_main)
|
.add_systems(Main, Main::run_main)
|
||||||
.add_systems(FixedMain, FixedMain::run_fixed_main);
|
.add_systems(FixedMain, FixedMain::run_fixed_main)
|
||||||
|
.configure_sets(
|
||||||
|
RunFixedMainLoop,
|
||||||
|
(
|
||||||
|
RunFixedMainLoopSystem::BeforeFixedMainLoop,
|
||||||
|
RunFixedMainLoopSystem::FixedMainLoop,
|
||||||
|
RunFixedMainLoopSystem::AfterFixedMainLoop,
|
||||||
|
)
|
||||||
|
.chain(),
|
||||||
|
);
|
||||||
|
|
||||||
#[cfg(feature = "bevy_debug_stepping")]
|
#[cfg(feature = "bevy_debug_stepping")]
|
||||||
{
|
{
|
||||||
|
@ -352,3 +369,96 @@ impl FixedMain {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set enum for the systems that want to run inside [`RunFixedMainLoop`],
|
||||||
|
/// but before or after the fixed update logic. Systems in this set
|
||||||
|
/// will run exactly once per frame, regardless of the number of fixed updates.
|
||||||
|
/// They will also run under a variable timestep.
|
||||||
|
///
|
||||||
|
/// This is useful for handling things that need to run every frame, but
|
||||||
|
/// also need to be read by the fixed update logic. See the individual variants
|
||||||
|
/// for examples of what kind of systems should be placed in each.
|
||||||
|
///
|
||||||
|
/// Note that in contrast to most other Bevy schedules, systems added directly to
|
||||||
|
/// [`RunFixedMainLoop`] will *not* be parallelized between each other.
|
||||||
|
#[derive(Debug, Hash, PartialEq, Eq, Copy, Clone, SystemSet)]
|
||||||
|
pub enum RunFixedMainLoopSystem {
|
||||||
|
/// Runs before the fixed update logic.
|
||||||
|
///
|
||||||
|
/// A good example of a system that fits here
|
||||||
|
/// is camera movement, which needs to be updated in a variable timestep,
|
||||||
|
/// as you want the camera to move with as much precision and updates as
|
||||||
|
/// the frame rate allows. A physics system that needs to read the camera
|
||||||
|
/// position and orientation, however, should run in the fixed update logic,
|
||||||
|
/// as it needs to be deterministic and run at a fixed rate for better stability.
|
||||||
|
/// Note that we are not placing the camera movement system in `Update`, as that
|
||||||
|
/// would mean that the physics system already ran at that point.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # use bevy_app::prelude::*;
|
||||||
|
/// # use bevy_ecs::prelude::*;
|
||||||
|
/// App::new()
|
||||||
|
/// .add_systems(
|
||||||
|
/// RunFixedMainLoop,
|
||||||
|
/// update_camera_rotation.in_set(RunFixedMainLoopSystem::BeforeFixedMainLoop))
|
||||||
|
/// .add_systems(FixedUpdate, update_physics);
|
||||||
|
///
|
||||||
|
/// # fn update_camera_rotation() {}
|
||||||
|
/// # fn update_physics() {}
|
||||||
|
/// ```
|
||||||
|
BeforeFixedMainLoop,
|
||||||
|
/// Contains the fixed update logic.
|
||||||
|
/// Runs [`FixedMain`] zero or more times based on delta of
|
||||||
|
/// [`Time<Virtual>`] and [`Time::overstep`].
|
||||||
|
///
|
||||||
|
/// Don't place systems here, use [`FixedUpdate`] and friends instead.
|
||||||
|
/// Use this system instead to order your systems to run specifically inbetween the fixed update logic and all
|
||||||
|
/// other systems that run in [`RunFixedMainLoopSystem::BeforeFixedMainLoop`] or [`RunFixedMainLoopSystem::AfterFixedMainLoop`].
|
||||||
|
///
|
||||||
|
/// [`Time<Virtual>`]: https://docs.rs/bevy/latest/bevy/prelude/struct.Virtual.html
|
||||||
|
/// [`Time::overstep`]: https://docs.rs/bevy/latest/bevy/time/struct.Time.html#method.overstep
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # use bevy_app::prelude::*;
|
||||||
|
/// # use bevy_ecs::prelude::*;
|
||||||
|
/// App::new()
|
||||||
|
/// .add_systems(FixedUpdate, update_physics)
|
||||||
|
/// .add_systems(
|
||||||
|
/// RunFixedMainLoop,
|
||||||
|
/// (
|
||||||
|
/// // This system will be called before all interpolation systems
|
||||||
|
/// // that third-party plugins might add.
|
||||||
|
/// prepare_for_interpolation
|
||||||
|
/// .after(RunFixedMainLoopSystem::FixedMainLoop)
|
||||||
|
/// .before(RunFixedMainLoopSystem::AfterFixedMainLoop),
|
||||||
|
/// )
|
||||||
|
/// );
|
||||||
|
///
|
||||||
|
/// # fn prepare_for_interpolation() {}
|
||||||
|
/// # fn update_physics() {}
|
||||||
|
/// ```
|
||||||
|
FixedMainLoop,
|
||||||
|
/// Runs after the fixed update logic.
|
||||||
|
///
|
||||||
|
/// A good example of a system that fits here
|
||||||
|
/// is a system that interpolates the transform of an entity between the last and current fixed update.
|
||||||
|
/// See the [fixed timestep example] for more details.
|
||||||
|
///
|
||||||
|
/// [fixed timestep example]: https://github.com/bevyengine/bevy/blob/main/examples/movement/physics_in_fixed_timestep.rs
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # use bevy_app::prelude::*;
|
||||||
|
/// # use bevy_ecs::prelude::*;
|
||||||
|
/// App::new()
|
||||||
|
/// .add_systems(FixedUpdate, update_physics)
|
||||||
|
/// .add_systems(
|
||||||
|
/// RunFixedMainLoop,
|
||||||
|
/// interpolate_transforms.in_set(RunFixedMainLoopSystem::AfterFixedMainLoop));
|
||||||
|
///
|
||||||
|
/// # fn interpolate_transforms() {}
|
||||||
|
/// # fn update_physics() {}
|
||||||
|
/// ```
|
||||||
|
AfterFixedMainLoop,
|
||||||
|
}
|
||||||
|
|
|
@ -246,13 +246,15 @@ impl AppGizmoBuilder for App {
|
||||||
.init_resource::<GizmoStorage<Config, Swap<Fixed>>>()
|
.init_resource::<GizmoStorage<Config, Swap<Fixed>>>()
|
||||||
.add_systems(
|
.add_systems(
|
||||||
RunFixedMainLoop,
|
RunFixedMainLoop,
|
||||||
start_gizmo_context::<Config, Fixed>.before(bevy_time::run_fixed_main_schedule),
|
start_gizmo_context::<Config, Fixed>
|
||||||
|
.in_set(bevy_app::RunFixedMainLoopSystem::BeforeFixedMainLoop),
|
||||||
)
|
)
|
||||||
.add_systems(FixedFirst, clear_gizmo_context::<Config, Fixed>)
|
.add_systems(FixedFirst, clear_gizmo_context::<Config, Fixed>)
|
||||||
.add_systems(FixedLast, collect_requested_gizmos::<Config, Fixed>)
|
.add_systems(FixedLast, collect_requested_gizmos::<Config, Fixed>)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
RunFixedMainLoop,
|
RunFixedMainLoop,
|
||||||
end_gizmo_context::<Config, Fixed>.after(bevy_time::run_fixed_main_schedule),
|
end_gizmo_context::<Config, Fixed>
|
||||||
|
.in_set(bevy_app::RunFixedMainLoopSystem::AfterFixedMainLoop),
|
||||||
)
|
)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Last,
|
Last,
|
||||||
|
|
|
@ -233,8 +233,10 @@ impl Default for Fixed {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs [`FixedMain`] zero or more times based on delta of
|
/// Runs [`FixedMain`] zero or more times based on delta of
|
||||||
/// [`Time<Virtual>`](Virtual) and [`Time::overstep`]
|
/// [`Time<Virtual>`](Virtual) and [`Time::overstep`].
|
||||||
pub fn run_fixed_main_schedule(world: &mut World) {
|
/// You can order your systems relative to this by using
|
||||||
|
/// [`RunFixedMainLoopSystem`](bevy_app::prelude::RunFixedMainLoopSystem).
|
||||||
|
pub(super) fn run_fixed_main_schedule(world: &mut World) {
|
||||||
let delta = world.resource::<Time<Virtual>>().delta();
|
let delta = world.resource::<Time<Virtual>>().delta();
|
||||||
world.resource_mut::<Time<Fixed>>().accumulate(delta);
|
world.resource_mut::<Time<Fixed>>().accumulate(delta);
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,10 @@ impl Plugin for TimePlugin {
|
||||||
.in_set(TimeSystem)
|
.in_set(TimeSystem)
|
||||||
.ambiguous_with(event_update_system),
|
.ambiguous_with(event_update_system),
|
||||||
)
|
)
|
||||||
.add_systems(RunFixedMainLoop, run_fixed_main_schedule);
|
.add_systems(
|
||||||
|
RunFixedMainLoop,
|
||||||
|
run_fixed_main_schedule.in_set(RunFixedMainLoopSystem::FixedMainLoop),
|
||||||
|
);
|
||||||
|
|
||||||
// Ensure the events are not dropped until `FixedMain` systems can observe them
|
// Ensure the events are not dropped until `FixedMain` systems can observe them
|
||||||
app.add_systems(FixedPostUpdate, signal_event_update_system);
|
app.add_systems(FixedPostUpdate, signal_event_update_system);
|
||||||
|
|
|
@ -53,16 +53,22 @@
|
||||||
//!
|
//!
|
||||||
//! ## Implementation
|
//! ## Implementation
|
||||||
//!
|
//!
|
||||||
|
//! - The player's inputs since the last physics update are stored in the `AccumulatedInput` component.
|
||||||
//! - The player's velocity is stored in a `Velocity` component. This is the speed in units per second.
|
//! - The player's velocity is stored in a `Velocity` component. This is the speed in units per second.
|
||||||
//! - The player's current position in the physics simulation is stored in a `PhysicalTranslation` component.
|
//! - The player's current position in the physics simulation is stored in a `PhysicalTranslation` component.
|
||||||
//! - The player's previous position in the physics simulation is stored in a `PreviousPhysicalTranslation` component.
|
//! - The player's previous position in the physics simulation is stored in a `PreviousPhysicalTranslation` component.
|
||||||
//! - The player's visual representation is stored in Bevy's regular `Transform` component.
|
//! - The player's visual representation is stored in Bevy's regular `Transform` component.
|
||||||
//! - Every frame, we go through the following steps:
|
//! - Every frame, we go through the following steps:
|
||||||
|
//! - Accumulate the player's input and set the current speed in the `handle_input` system.
|
||||||
|
//! This is run in the `RunFixedMainLoop` schedule, ordered in `RunFixedMainLoopSystem::BeforeFixedMainLoop`,
|
||||||
|
//! which runs before the fixed timestep loop. This is run every frame.
|
||||||
//! - Advance the physics simulation by one fixed timestep in the `advance_physics` system.
|
//! - Advance the physics simulation by one fixed timestep in the `advance_physics` system.
|
||||||
//! This is run in the `FixedUpdate` schedule, which runs before the `Update` schedule.
|
//! Accumulated input is consumed here.
|
||||||
//! - Update the player's visual representation in the `update_rendered_transform` system.
|
//! This is run in the `FixedUpdate` schedule, which runs zero or multiple times per frame.
|
||||||
|
//! - Update the player's visual representation in the `interpolate_rendered_transform` system.
|
||||||
//! This interpolates between the player's previous and current position in the physics simulation.
|
//! This interpolates between the player's previous and current position in the physics simulation.
|
||||||
//! - Update the player's velocity based on the player's input in the `handle_input` system.
|
//! It is run in the `RunFixedMainLoop` schedule, ordered in `RunFixedMainLoopSystem::AfterFixedMainLoop`,
|
||||||
|
//! which runs after the fixed timestep loop. This is run every frame.
|
||||||
//!
|
//!
|
||||||
//!
|
//!
|
||||||
//! ## Controls
|
//! ## Controls
|
||||||
|
@ -80,13 +86,31 @@ fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins(DefaultPlugins)
|
.add_plugins(DefaultPlugins)
|
||||||
.add_systems(Startup, (spawn_text, spawn_player))
|
.add_systems(Startup, (spawn_text, spawn_player))
|
||||||
// `FixedUpdate` runs before `Update`, so the physics simulation is advanced before the player's visual representation is updated.
|
// Advance the physics simulation using a fixed timestep.
|
||||||
.add_systems(FixedUpdate, advance_physics)
|
.add_systems(FixedUpdate, advance_physics)
|
||||||
.add_systems(Update, (update_rendered_transform, handle_input).chain())
|
.add_systems(
|
||||||
|
// The `RunFixedMainLoop` schedule allows us to schedule systems to run before and after the fixed timestep loop.
|
||||||
|
RunFixedMainLoop,
|
||||||
|
(
|
||||||
|
// The physics simulation needs to know the player's input, so we run this before the fixed timestep loop.
|
||||||
|
// Note that if we ran it in `Update`, it would be too late, as the physics simulation would already have been advanced.
|
||||||
|
// If we ran this in `FixedUpdate`, it would sometimes not register player input, as that schedule may run zero times per frame.
|
||||||
|
handle_input.in_set(RunFixedMainLoopSystem::BeforeFixedMainLoop),
|
||||||
|
// The player's visual representation needs to be updated after the physics simulation has been advanced.
|
||||||
|
// This could be run in `Update`, but if we run it here instead, the systems in `Update`
|
||||||
|
// will be working with the `Transform` that will actually be shown on screen.
|
||||||
|
interpolate_rendered_transform.in_set(RunFixedMainLoopSystem::AfterFixedMainLoop),
|
||||||
|
),
|
||||||
|
)
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// How many units per second the player should move.
|
/// A vector representing the player's input, accumulated over all frames that ran
|
||||||
|
/// since the last time the physics simulation was advanced.
|
||||||
|
#[derive(Debug, Component, Clone, Copy, PartialEq, Default, Deref, DerefMut)]
|
||||||
|
struct AccumulatedInput(Vec2);
|
||||||
|
|
||||||
|
/// A vector representing the player's velocity in the physics simulation.
|
||||||
#[derive(Debug, Component, Clone, Copy, PartialEq, Default, Deref, DerefMut)]
|
#[derive(Debug, Component, Clone, Copy, PartialEq, Default, Deref, DerefMut)]
|
||||||
struct Velocity(Vec3);
|
struct Velocity(Vec3);
|
||||||
|
|
||||||
|
@ -100,7 +124,7 @@ struct Velocity(Vec3);
|
||||||
struct PhysicalTranslation(Vec3);
|
struct PhysicalTranslation(Vec3);
|
||||||
|
|
||||||
/// The value [`PhysicalTranslation`] had in the last fixed timestep.
|
/// The value [`PhysicalTranslation`] had in the last fixed timestep.
|
||||||
/// Used for interpolation in the `update_rendered_transform` system.
|
/// Used for interpolation in the `interpolate_rendered_transform` system.
|
||||||
#[derive(Debug, Component, Clone, Copy, PartialEq, Default, Deref, DerefMut)]
|
#[derive(Debug, Component, Clone, Copy, PartialEq, Default, Deref, DerefMut)]
|
||||||
struct PreviousPhysicalTranslation(Vec3);
|
struct PreviousPhysicalTranslation(Vec3);
|
||||||
|
|
||||||
|
@ -114,6 +138,7 @@ fn spawn_player(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
transform: Transform::from_scale(Vec3::splat(0.3)),
|
transform: Transform::from_scale(Vec3::splat(0.3)),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
|
AccumulatedInput::default(),
|
||||||
Velocity::default(),
|
Velocity::default(),
|
||||||
PhysicalTranslation::default(),
|
PhysicalTranslation::default(),
|
||||||
PreviousPhysicalTranslation::default(),
|
PreviousPhysicalTranslation::default(),
|
||||||
|
@ -143,31 +168,35 @@ fn spawn_text(mut commands: Commands) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle keyboard input to move the player.
|
/// Handle keyboard input and accumulate it in the `AccumulatedInput` component.
|
||||||
fn handle_input(keyboard_input: Res<ButtonInput<KeyCode>>, mut query: Query<&mut Velocity>) {
|
/// There are many strategies for how to handle all the input that happened since the last fixed timestep.
|
||||||
|
/// This is a very simple one: we just accumulate the input and average it out by normalizing it.
|
||||||
|
fn handle_input(
|
||||||
|
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||||
|
mut query: Query<(&mut AccumulatedInput, &mut Velocity)>,
|
||||||
|
) {
|
||||||
/// Since Bevy's default 2D camera setup is scaled such that
|
/// Since Bevy's default 2D camera setup is scaled such that
|
||||||
/// one unit is one pixel, you can think of this as
|
/// one unit is one pixel, you can think of this as
|
||||||
/// "How many pixels per second should the player move?"
|
/// "How many pixels per second should the player move?"
|
||||||
const SPEED: f32 = 210.0;
|
const SPEED: f32 = 210.0;
|
||||||
for mut velocity in query.iter_mut() {
|
for (mut input, mut velocity) in query.iter_mut() {
|
||||||
velocity.0 = Vec3::ZERO;
|
|
||||||
|
|
||||||
if keyboard_input.pressed(KeyCode::KeyW) {
|
if keyboard_input.pressed(KeyCode::KeyW) {
|
||||||
velocity.y += 1.0;
|
input.y += 1.0;
|
||||||
}
|
}
|
||||||
if keyboard_input.pressed(KeyCode::KeyS) {
|
if keyboard_input.pressed(KeyCode::KeyS) {
|
||||||
velocity.y -= 1.0;
|
input.y -= 1.0;
|
||||||
}
|
}
|
||||||
if keyboard_input.pressed(KeyCode::KeyA) {
|
if keyboard_input.pressed(KeyCode::KeyA) {
|
||||||
velocity.x -= 1.0;
|
input.x -= 1.0;
|
||||||
}
|
}
|
||||||
if keyboard_input.pressed(KeyCode::KeyD) {
|
if keyboard_input.pressed(KeyCode::KeyD) {
|
||||||
velocity.x += 1.0;
|
input.x += 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need to normalize and scale because otherwise
|
// Need to normalize and scale because otherwise
|
||||||
// diagonal movement would be faster than horizontal or vertical movement.
|
// diagonal movement would be faster than horizontal or vertical movement.
|
||||||
velocity.0 = velocity.normalize_or_zero() * SPEED;
|
// This effectively averages the accumulated input.
|
||||||
|
velocity.0 = input.extend(0.0).normalize_or_zero() * SPEED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,18 +209,26 @@ fn advance_physics(
|
||||||
mut query: Query<(
|
mut query: Query<(
|
||||||
&mut PhysicalTranslation,
|
&mut PhysicalTranslation,
|
||||||
&mut PreviousPhysicalTranslation,
|
&mut PreviousPhysicalTranslation,
|
||||||
|
&mut AccumulatedInput,
|
||||||
&Velocity,
|
&Velocity,
|
||||||
)>,
|
)>,
|
||||||
) {
|
) {
|
||||||
for (mut current_physical_translation, mut previous_physical_translation, velocity) in
|
for (
|
||||||
query.iter_mut()
|
mut current_physical_translation,
|
||||||
|
mut previous_physical_translation,
|
||||||
|
mut input,
|
||||||
|
velocity,
|
||||||
|
) in query.iter_mut()
|
||||||
{
|
{
|
||||||
previous_physical_translation.0 = current_physical_translation.0;
|
previous_physical_translation.0 = current_physical_translation.0;
|
||||||
current_physical_translation.0 += velocity.0 * fixed_time.delta_seconds();
|
current_physical_translation.0 += velocity.0 * fixed_time.delta_seconds();
|
||||||
|
|
||||||
|
// Reset the input accumulator, as we are currently consuming all input that happened since the last fixed timestep.
|
||||||
|
input.0 = Vec2::ZERO;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_rendered_transform(
|
fn interpolate_rendered_transform(
|
||||||
fixed_time: Res<Time<Fixed>>,
|
fixed_time: Res<Time<Fixed>>,
|
||||||
mut query: Query<(
|
mut query: Query<(
|
||||||
&mut Transform,
|
&mut Transform,
|
||||||
|
|
Loading…
Reference in a new issue