A general-purpose transfom interpolation plugin for fixed timesteps for the Bevy game engine.

Overview

bevy_transform_interpolation

A general-purpose Transform interpolation plugin 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);

// Runs in `Update` or `PostUpdate`.
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.

Usage

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

[dependencies]
bevy_transform_interpolation = { git = "https://github.com/Jondolf/bevy_transform_interpolation" }

Next, add the TransformInterpolationPlugin:

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

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

Transform interpolation can be enabled very granularly in bevy_transform_interpolation. You can choose to interpolate transform, rotation, or scale individually, or use any combination of them:

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

fn setup(mut commands: Commands) {
    // Only interpolate translation.
    commands.spawn((TransformBundle::default(), TranslationInterpolation));
    
    // Only interpolate rotation.
    commands.spawn((TransformBundle::default(), RotationInterpolation));
    
    // Only interpolate scale.
    commands.spawn((TransformBundle::default(), ScaleInterpolation));
    
    // Interpolate translation and rotation, but not scale.
    commands.spawn((
        TransformBundle::default(),
        TranslationInterpolation,
        RotationInterpolation,
    ));
    
    // Interpolate the entire transform: translation, rotation, and scale.
    // The components can be added individually, or using the `TransformInterpolationBundle`.
    commands.spawn((
        TransformBundle::default(),
        TransformInterpolationBundle::default(),
    ));
}

You can also enable transform interpolation globally for all entities that have a Transform by configuring the TransformInterpolationPlugin:

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

fn main() {
    App::new()
        .add_plugins((
            DefaultPlugins,
            // Interpolate translation and rotation, but not scale.
            TransformInterpolationPlugin {
                global_translation_interpolation: true,
                global_rotation_interpolation: true,
                global_scale_interpolation: false,
            },
        ))
        // ...other plugins, resources, and systems
        .run();
}

If interpolation is enabled globally, it can still be disabled for individual entities using the NoTranslationInterpolation, NoRotationInterpolation, and NoScaleInterpolation components.

Now, any changes made to Transform in FixedPreUpdate, FixedUpdate, or FixedPostUpdate will automatically be smoothed in between the fixed timesteps for entities that have transform interpolation enabled.

Changing Transform manually in any schedule that doesn't use a fixed timestep is also supported, but it is equivalent to teleporting, and disables interpolation for the entity for the remainder of that fixed timestep.

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>,
}
  • At the start of the FixedFirst schedule, the states are reset to None.
  • In FixedFirst, for every entity with the TranslationInterpolation component, start is set to the current Transform.
  • In FixedLast, for every entity with the TranslationInterpolation component, end is set to the current Transform.

This way, start represents the "old" state, while end represents the "new" state after changes have been made to Transform in between FixedFirst and FixedLast. Rotation and scale are handled similarly.

The easing is then performed in PostUpdate, before Bevy's transform propagation systems. If the Transform is detected to have changed since the last easing run but outside of the fixed timestep schedules, the easing is reset to None to prevent overwriting the change.

Note that the core easing logic and components are intentionally not tied to interpolation directly. A physics engine could implement transform extrapolation using velocity and the same easing functionality, supplying its own TranslationExtrapolation and RotationExtrapolation components.

Caveats

  • In cases where the previous or current gameplay transform are already stored separately from Transform, storing them in the easing states as well may be redundant. Although it is still useful for allowing Transform to be modified directly and for wider compatibility with the ecosystem.
  • Transform extrapolation is currently not supported as a built-in feature, as it typically requires a velocity for the prediction of the next state. However, it could be supported by external libraries such as physics engines in a similar way to src/interpolation.rs, and simply updating the start and end states differently.
  • Large angular velocities may cause visual artifacts, as the interpolation follows the shortest path. A physics engine could handle this properly.

License

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

at your option.

You might also like...
2d Endless Runner Game made with Bevy Game Engine
2d Endless Runner Game made with Bevy Game Engine

Cute-runner A 2d Endless Runner Game made with Bevy Game Engine. Table of contents Project Infos Usage Screenshots Disclaimer Project Infos Date: Sept

A game of snake written in Rust using the Bevy game engine, targeting WebGL2

Snake using the Bevy Game Engine Prerequisites cargo install cargo-make Build and serve WASM version Set your local ip address in Makefile.toml (loca

A game made in one week for the Bevy engine's first game jam

¿Quien es el MechaBurro? An entry for the first Bevy game jam following the theme of "Unfair Advantage." It was made in one week using the wonderful B

A Bevy Engine plugin for making 2D paths, smooth animations with Bezier curves
A Bevy Engine plugin for making 2D paths, smooth animations with Bezier curves

bevy_pen_tool A Bevy Engine plugin for making 2D paths and smooth animations with Bezier curves TODO: Mesh-making functionality for building 2D shapes

Bevy engine + miniquad render plugin

Bevy engine + miniquad renderer This is a plugin for Bevy engine that replaces default windowing and rendering plugins with miniquad based one. Usage

A Bevy plugin to use Kira for game audio

Bevy Kira audio This bevy plugin is intended to try integrating Kira into Bevy. The end goal would be to replace or update bevy_audio, if Kira turns o

Basic first-person fly camera for the Bevy game engine

bevy_flycam A basic first-person fly camera for Bevy 0.4 Controls WASD to move horizontally SPACE to ascend LSHIFT to descend ESC to grab/release curs

Concise Reference Book for the Bevy Game Engine

Unofficial Bevy Cheat Book Click here to read the book! Concise reference to programming in the Bevy game engine. Covers useful syntax, features, prog

Proof-of-concept of getting OpenXR rendering support for Bevy game engine using gfx-rs abstractions
Proof-of-concept of getting OpenXR rendering support for Bevy game engine using gfx-rs abstractions

Introduction Proof-of-concept of getting OpenXR rendering support for Bevy game engine using gfx-rs abstractions. (hand interaction with boxes missing

Comments
  • Run interpolation before `Update`

    Run interpolation before `Update`

    This way, systems running in Update already operate on the final Transform and we don't need to check for changes in Update. Per discussions on Discord we recently had in #ecs-dev, this schedule was determined to be the one most useful for interpolation. See also this companion PR in Bevy: https://github.com/bevyengine/bevy/pull/14881

    opened by janhohenheim 3
  • Run interpolation before `Update`

    Run interpolation before `Update`

    See https://github.com/bevyengine/bevy/pull/14881 for the right schedule. See https://github.com/Jondolf/bevy_transform_interpolation/pull/1 for a buggy attempt

    opened by janhohenheim 0
Owner
Joona Aalto
Student interested in all things science. Working on physics for the Bevy game engine ✨
Joona Aalto
Bevy Simple Portals is a Bevy game engine plugin aimed to create portals.

Portals for Bevy Bevy Simple Portals is a Bevy game engine plugin aimed to create portals. Those portals are (for now) purely visual and can be used t

Sélène Amanita 11 May 28, 2023
A Client/Server game networking plugin using QUIC, for the Bevy game engine.

Bevy Quinnet A Client/Server game networking plugin using QUIC, for the Bevy game engine. Bevy Quinnet QUIC as a game networking protocol Features Roa

Gilles Henaux 65 Feb 20, 2023
Minecraft-esque voxel engine prototype made with the bevy game engine. Pending bevy 0.6 release to undergo a full rewrite.

vx_bevy A voxel engine prototype made using the Bevy game engine. Goals and features Very basic worldgen Animated chunk loading (ala cube world) Optim

Lucas Arriesse 125 Dec 31, 2022
General purpose client/server networking library written in Rust, built on top of the QUIC protocol which is implemented by quinn

Overview "This library stinks!" ... "Unless you like durian" durian is a client-server networking library built on top of the QUIC protocol which is i

Michael 92 Dec 31, 2022
Inspector plugin for the bevy game engine

bevy-inspector-egui This crate provides the ability to annotate structs with a #[derive(Inspectable)], which opens a debug interface using egui where

Jakob Hellermann 517 Dec 31, 2022
Crossterm plugin for the bevy game engine

What is bevy_crossterm? bevy_crossterm is a Bevy plugin that uses crossterm as a renderer. It provides custom components and events which allow users

null 79 Nov 2, 2022
Hanabi — a particle system plugin for the Bevy game engine.

Hanabi — a particle system plugin for the Bevy game engine

Jerome Humbert 256 Dec 30, 2022
Tweening animation plugin for the Bevy game engine.

?? Bevy Tweening Tweening animation plugin for the Bevy game engine. Features Animate any field of any component or asset, including custom ones. Run

Jerome Humbert 135 Dec 23, 2022
A plugin to enable random number generation for the Bevy game engine.

bevy_turborand A plugin to enable random number generation for the Bevy game engine, built upon turborand. Implements ideas from Bevy's Deterministic

Gonçalo Rica Pais da Silva 15 Dec 23, 2022
A spectator camera plugin for the Bevy game engine

bevy_spectator A spectator camera plugin for the Bevy game engine. Controls Action Key Forward W Left A Backward S Right D Up Space Down LControl Alt.

Jonah Henriksson 5 Jan 2, 2023