bevy_transform_interpolation

Crates.iobevy_transform_interpolation
lib.rsbevy_transform_interpolation
version
sourcesrc
created_at2024-12-05 22:59:34.437485
updated_at2024-12-05 22:59:34.437485
descriptionTransform interpolation for fixed timesteps for the Bevy game engine
homepage
repositoryhttps://github.com/Jondolf/bevy_transform_interpolation
max_upload_size
id1473708
Cargo.toml error:TOML parse error at line 18, column 1 | 18 | autolib = false | ^^^^^^^ unknown field `autolib`, expected one of `name`, `version`, `edition`, `authors`, `description`, `readme`, `license`, `repository`, `homepage`, `documentation`, `build`, `resolver`, `links`, `default-run`, `default_dash_run`, `rust-version`, `rust_dash_version`, `rust_version`, `license-file`, `license_dash_file`, `license_file`, `licenseFile`, `license_capital_file`, `forced-target`, `forced_dash_target`, `autobins`, `autotests`, `autoexamples`, `autobenches`, `publish`, `metadata`, `keywords`, `categories`, `exclude`, `include`
size0
Joona Aalto (Jondolf)

documentation

https://docs.rs/bevy_transform_interpolation

README

bevy_transform_interpolation

MIT/Apache 2.0 ci crates.io docs.rs

A drop-in Transform interpolation solution for fixed timesteps for the Bevy game engine.

What Is This For?

A lot of gameplay logic and movement systems typically use a fixed timestep to produce consistent and stable behavior regardless of the frame rate. Notable examples include physics simulation and character movement.

However, this can make movement appear choppy, especially on displays with a high refresh rate. To achieve visually smooth movement while using a fixed timestep, the visual transform must be smoothed independently of the "true" gameplay transform.

The most common way to do this is to use transform interpolation, which interpolates movement from the previous state to the current state. This could be done by storing the current and old gameplay positions in their own components and interpolating Transform using them:

use bevy::prelude::*;

#[derive(Component, Deref, DerefMut)]
struct Position(Vec3);

#[derive(Component, Deref, DerefMut)]
struct OldPosition(Vec3);

fn interpolate_transforms(
    mut query: Query<(&mut Transform, &Position, &OldPosition)>,
    fixed_time: Res<Time<Fixed>>
) {
    // How much of a "partial timestep" has accumulated since the last fixed timestep run.
    // Between `0.0` and `1.0`.
    let overstep = fixed_time.overstep_fraction();

    for (mut transform, position, old_position) in &mut query {
        // Linearly interpolate the translation from the old position to the current one.
        transform.translation = old_position.lerp(position.0, overstep_fraction);
    }
}

In fact, you could simply plug the above implementation into your own application if you wanted to!

However, it requires you to use Position for gameplay logic, and to manage OldPosition somewhere. This can be annoying, and is incompatibile with third party libraries that expect to be able to modify the transform directly.

bevy_transform_interpolation aims to be a drop-in solution that allows easy and efficient transform interpolation, while still allowing the usage of Transform for gameplay logic. It should be automatically compatible with physics engines such as Avian and bevy_rapier, as long as the simulation is run in FixedUpdate or FixedPostUpdate.

Getting Started

First, add bevy_transform_interpolation to your dependencies in Cargo.toml:

[dependencies]
bevy_transform_interpolation = "0.1"

To enable Transform interpolation, add the TransformInterpolationPlugin to your app:

use bevy::prelude::*;
use bevy_transform_interpolation::prelude::*;

fn main() {
    App::new()
        .add_plugins((DefaultPlugins, TransformInterpolationPlugin::default()))
        // ...other plugins, resources, and systems
        .run();
}

By default, interpolation is only performed for entities with the TransformInterpolation component:

fn setup(mut commands: Commands) {
    // Interpolate the entire transform: translation, rotation, and scale.
    commands.spawn((
        Transform::default(),
        TransformInterpolation,
    ));
}

Now, any changes made to the Transform of the entity in FixedPreUpdate, FixedUpdate, or FixedPostUpdate will automatically be interpolated in between fixed timesteps.

If you want all entities with a Transform to be interpolated by default, you can use TransformInterpolationPlugin::interpolate_all():

fn main() {
    App::new()
        .add_plugins(TransformInterpolationPlugin::interpolate_all())
        // ...
        .run();
}

Advanced Usage

For a lot of applications, the functionality shown in the Getting Started guide might be all you need! However, bevy_transform_interpolation has a lot more to offer:

  • Granularly ease individual properties of the transform with TranslationInterpolation, RotationInterpolation, and ScaleInterpolation.
  • Opt out of transform easing for individual entities with NoTranslationEasing, NoRotationEasing, and NoScaleEasing.
  • Use extrapolation instead of interpolation with the TransformExtrapolationPlugin and its related components.
  • Use Hermite interpolation for more natural and accurate movement with the TransformHermiteEasingPlugin.
  • Implement custom easing backends for your specific needs.

How Does It Work?

Internally, bevy_transform_interpolation simply maintains components that store the start and end of the interpolation. For example, translation uses the following component for easing the movement:

pub struct TranslationEasingState {
    pub start: Option<Vec3>,
    pub end: Option<Vec3>,
}

The states are updated by the TransformInterpolationPlugin or TransformExtrapolationPlugin depending on whether the entity has TransformInterpolation or TransformExtrapolation components.

If interpolation is used:

  • In FixedFirst, start is set to the current Transform.
  • In FixedLast, end is set to the current Transform.

If extrapolation is used:

  • In FixedLast, start is set to the current Transform, and end is set to the Transform predicted based on velocity.

At the start of the FixedFirst schedule, the states are reset to None. If the Transform is detected to have changed since the last easing run but outside of the fixed timestep schedules, the easing is also reset to None to prevent overwriting the change.

The actual easing is performed in RunFixedMainLoop, right after FixedMain, before Update. By default, linear interpolation (lerp) is used for translation and scale, and spherical linear interpolation (slerp) is used for rotation.

However, thanks to the modular and flexible architecture, other easing methods can also be used. The TransformHermiteEasingPlugin provides an easing backend using Hermite interpolation, overwriting the linear interpolation for specific entities with the NonlinearTranslationEasing and NonlinearRotationEasing marker components. Custom easing solutions can be implemented using the same pattern.

Supported Bevy Versions

bevy bevy_transform_interpolation
0.15 0.1

License

bevy_transform_interpolation is free and open source. All code in this repository is dual-licensed under either:

at your option.

Commit count: 25

cargo fmt