bevy_transform_interpolation/
interpolation.rs

1//! [`Transform`] interpolation, making movement in [`FixedUpdate`] appear smooth
2//! by easing between the old and current [`Transform`] in between fixed timesteps.
3//!
4//! See the [`TransformInterpolationPlugin`] for more information.
5
6#![allow(clippy::type_complexity)]
7
8use crate::{
9    prelude::*, RotationEasingState, ScaleEasingState, TransformEasingSet, TranslationEasingState,
10};
11use bevy::prelude::*;
12
13/// A plugin for [`Transform`] interpolation, making movement in [`FixedUpdate`] appear smooth.
14///
15/// Transform interpolation applies easing between the old and current [`Transform`]
16/// in between fixed timesteps. This results in movement that looks smooth and accurate,
17/// at the cost of rendered positions being slightly behind the "true" gameplay positions.
18///
19/// This plugin requires the [`TransformEasingPlugin`] to function. It is automatically added
20/// if not already present in the app.
21///
22/// # Usage
23///
24/// Transform interpolation can be enabled for a given entity by adding the [`TransformInterpolation`] component.
25///
26/// ```
27/// use bevy::prelude::*;
28/// use bevy_transform_interpolation::prelude::*;
29///
30/// fn setup(mut commands: Commands) {
31///     // Interpolate the entire transform: translation, rotation, and scale.
32///     commands.spawn((
33///         Transform::default(),
34///         TransformInterpolation,
35///     ));
36/// }
37/// ```
38///
39/// Now, any changes made to the [`Transform`] of the entity in [`FixedPreUpdate`], [`FixedUpdate`],
40/// or [`FixedPostUpdate`] will automatically be smoothed in between fixed timesteps.
41///
42/// Transform properties can also be interpolated individually by adding the [`TranslationInterpolation`],
43/// [`RotationInterpolation`], and [`ScaleInterpolation`] components.
44///
45/// ```
46/// # use bevy::prelude::*;
47/// # use bevy_transform_interpolation::prelude::*;
48/// #
49/// fn setup(mut commands: Commands) {
50///     // Only interpolate translation.
51///     commands.spawn((Transform::default(), TranslationInterpolation));
52///     
53///     // Only interpolate rotation.
54///     commands.spawn((Transform::default(), RotationInterpolation));
55///     
56///     // Only interpolate scale.
57///     commands.spawn((Transform::default(), ScaleInterpolation));
58///     
59///     // Interpolate translation and rotation, but not scale.
60///     commands.spawn((
61///         Transform::default(),
62///         TranslationInterpolation,
63///         RotationInterpolation,
64///     ));
65/// }
66/// ```
67///
68/// If you want *all* entities with a [`Transform`] to be interpolated by default, you can use
69/// [`TransformInterpolationPlugin::interpolate_all()`], or set the [`interpolate_translation_all`],
70/// [`interpolate_rotation_all`], and [`interpolate_scale_all`] fields.
71///
72/// ```no_run
73/// # use bevy::prelude::*;
74/// # use bevy_transform_interpolation::prelude::*;
75/// #
76/// fn main() {
77///    App::new()
78///       .add_plugins(TransformInterpolationPlugin {
79///           // Interpolate translation and rotation by default, but not scale.
80///           interpolate_translation_all: true,
81///           interpolate_rotation_all: true,
82///           interpolate_scale_all: false,
83///       })
84///       // ...
85///       .run();
86/// }
87/// ```
88///
89/// When interpolation is enabled for all entities by default, you can still opt out of it for individual entities
90/// by adding the [`NoTransformEasing`] component, or the individual [`NoTranslationEasing`], [`NoRotationEasing`],
91/// and [`NoScaleEasing`] components.
92///
93/// Note that changing [`Transform`] manually in any schedule that *doesn't* use a fixed timestep is also supported,
94/// but it is equivalent to teleporting, and disables interpolation for the entity for the remainder of that fixed timestep.
95///
96/// [`interpolate_translation_all`]: TransformInterpolationPlugin::interpolate_translation_all
97/// [`interpolate_rotation_all`]: TransformInterpolationPlugin::interpolate_rotation_all
98/// [`interpolate_scale_all`]: TransformInterpolationPlugin::interpolate_scale_all
99///
100/// # Alternatives
101///
102/// For games where low latency is crucial for gameplay, such as in some first-person shooters
103/// or racing games, the small delay introduced by interpolation may be undesirable. In those cases,
104/// one option is to use the [`TransformExtrapolationPlugin`] instea.
105///
106/// Transform extrapolation predicts future positions based on velocity, and applies easing between
107/// the current and predicted [`Transform`]. This results in movement that looks smooth and feels responsive,
108/// but can stutter when the prediction is incorrect, such as when velocity changes abruptly.
109///
110/// # Easing Backends
111///
112/// By default, transform interpolation uses linear interpolation (`lerp`) for easing translation and scale,
113/// and spherical linear interpolation (`slerp`) for easing rotation.
114///
115/// If the previous and current velocities are also available, it is possible to use [Hermite interpolation]
116/// with the [`TransformHermiteEasingPlugin`] to get smoother and more accurate easing. To enable Hermite interpolation,
117/// add the [`TransformHermiteEasing`] component to the entity in addition to the core interpolation components.
118#[derive(Debug, Default)]
119pub struct TransformInterpolationPlugin {
120    /// If `true`, translation will be interpolated for all entities with the [`Transform`] component by default.
121    ///
122    /// This can be overridden for individual entities by adding the [`NoTranslationEasing`] or [`NoTransformEasing`] component.
123    pub interpolate_translation_all: bool,
124    /// If `true`, rotation will be interpolated for all entities with the [`Transform`] component by default.
125    ///
126    /// This can be overridden for individual entities by adding the [`NoRotationEasing`] or [`NoTransformEasing`] component.
127    pub interpolate_rotation_all: bool,
128    /// If `true`, scale will be interpolated for all entities with the [`Transform`] component by default.
129    ///
130    /// This can be overridden for individual entities by adding the [`NoScaleEasing`] or [`NoTransformEasing`] component.
131    pub interpolate_scale_all: bool,
132}
133
134impl TransformInterpolationPlugin {
135    /// Enables interpolation for translation, rotation, and scale for all entities with the [`Transform`] component.
136    ///
137    /// This can be overridden for individual entities by adding the [`NoTransformEasing`] component,
138    /// or the individual [`NoTranslationEasing`], [`NoRotationEasing`], and [`NoScaleEasing`] components.
139    pub const fn interpolate_all() -> Self {
140        Self {
141            interpolate_translation_all: true,
142            interpolate_rotation_all: true,
143            interpolate_scale_all: true,
144        }
145    }
146}
147
148impl Plugin for TransformInterpolationPlugin {
149    fn build(&self, app: &mut App) {
150        // Register components.
151        app.register_type::<(
152            TranslationInterpolation,
153            RotationInterpolation,
154            ScaleInterpolation,
155        )>();
156
157        app.add_systems(
158            FixedFirst,
159            (
160                complete_translation_easing,
161                complete_rotation_easing,
162                complete_scale_easing,
163            )
164                .chain()
165                .before(TransformEasingSet::Reset),
166        );
167
168        // Update the start state of the interpolation at the start of the fixed timestep.
169        app.add_systems(
170            FixedFirst,
171            (
172                update_translation_interpolation_start,
173                update_rotation_interpolation_start,
174                update_scale_interpolation_start,
175            )
176                .chain()
177                .in_set(TransformEasingSet::UpdateStart),
178        );
179
180        // Update the end state of the interpolation at the end of the fixed timestep.
181        app.add_systems(
182            FixedLast,
183            (
184                update_translation_interpolation_end,
185                update_rotation_interpolation_end,
186                update_scale_interpolation_end,
187            )
188                .chain()
189                .in_set(TransformEasingSet::UpdateEnd),
190        );
191
192        // Insert interpolation components automatically for all entities with a `Transform`
193        // if the corresponding global interpolation is enabled.
194        if self.interpolate_translation_all {
195            let _ = app.try_register_required_components::<Transform, TranslationInterpolation>();
196        }
197        if self.interpolate_rotation_all {
198            let _ = app.try_register_required_components::<Transform, RotationInterpolation>();
199        }
200        if self.interpolate_scale_all {
201            let _ = app.try_register_required_components::<Transform, ScaleInterpolation>();
202        }
203    }
204
205    fn finish(&self, app: &mut App) {
206        // Add the `TransformEasingPlugin` if it hasn't been added yet.
207        if !app.is_plugin_added::<TransformEasingPlugin>() {
208            app.add_plugins(TransformEasingPlugin);
209        }
210    }
211}
212
213/// Enables full [`Transform`] interpolation for an entity, making changes to translation,
214/// rotation, and scale in [`FixedUpdate`] appear smooth.
215///
216/// See the [`TransformInterpolationPlugin`] for more information.
217#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
218#[reflect(Component, Debug, Default)]
219#[require(TranslationInterpolation, RotationInterpolation, ScaleInterpolation)]
220pub struct TransformInterpolation;
221
222/// Enables translation interpolation for an entity, making changes to translation
223/// in [`FixedUpdate`] appear smooth.
224///
225/// See the [`TransformInterpolationPlugin`] for more information.
226#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
227#[reflect(Component, Debug, Default)]
228#[require(TranslationEasingState)]
229pub struct TranslationInterpolation;
230
231/// Enables rotation interpolation for an entity, making changes to rotation
232/// in [`FixedUpdate`] appear smooth.
233///
234/// See the [`TransformInterpolationPlugin`] for more information.
235#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
236#[reflect(Component, Debug, Default)]
237#[require(RotationEasingState)]
238pub struct RotationInterpolation;
239
240/// Enables scale interpolation for an entity, making changes to scale
241/// in [`FixedUpdate`] appear smooth.
242///
243/// See the [`TransformInterpolationPlugin`] for more information.
244#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
245#[reflect(Component, Debug, Default)]
246#[require(ScaleEasingState)]
247pub struct ScaleInterpolation;
248
249/// Makes sure the previous translation easing is fully applied before the next easing starts.
250fn complete_translation_easing(
251    mut query: Query<
252        (&mut Transform, &TranslationEasingState),
253        (With<TranslationInterpolation>, Without<NoTranslationEasing>),
254    >,
255) {
256    for (mut transform, easing) in &mut query {
257        // Make sure the previous easing is fully applied.
258        if let Some(end) = easing.end {
259            transform.translation = end;
260        }
261    }
262}
263
264/// Makes sure the previous rotation easing is fully applied before the next easing starts.
265fn complete_rotation_easing(
266    mut query: Query<
267        (&mut Transform, &RotationEasingState),
268        (With<RotationInterpolation>, Without<NoRotationEasing>),
269    >,
270) {
271    for (mut transform, easing) in &mut query {
272        // Make sure the previous easing is fully applied.
273        if let Some(end) = easing.end {
274            transform.rotation = end;
275        }
276    }
277}
278
279/// Makes sure the previous scale easing is fully applied before the next easing starts.
280fn complete_scale_easing(
281    mut query: Query<
282        (&mut Transform, &ScaleEasingState),
283        (With<ScaleInterpolation>, Without<NoScaleEasing>),
284    >,
285) {
286    for (mut transform, easing) in &mut query {
287        // Make sure the previous easing is fully applied.
288        if let Some(end) = easing.end {
289            transform.scale = end;
290        }
291    }
292}
293
294fn update_translation_interpolation_start(
295    mut query: Query<
296        (&Transform, &mut TranslationEasingState),
297        (With<TranslationInterpolation>, Without<NoTranslationEasing>),
298    >,
299) {
300    for (transform, mut easing) in &mut query {
301        easing.start = Some(transform.translation);
302    }
303}
304
305fn update_translation_interpolation_end(
306    mut query: Query<
307        (&Transform, &mut TranslationEasingState),
308        (With<TranslationInterpolation>, Without<NoTranslationEasing>),
309    >,
310) {
311    for (transform, mut easing) in &mut query {
312        easing.end = Some(transform.translation);
313    }
314}
315
316fn update_rotation_interpolation_start(
317    mut query: Query<
318        (&Transform, &mut RotationEasingState),
319        (With<RotationInterpolation>, Without<NoRotationEasing>),
320    >,
321) {
322    for (transform, mut easing) in &mut query {
323        easing.start = Some(transform.rotation);
324    }
325}
326
327fn update_rotation_interpolation_end(
328    mut query: Query<
329        (&Transform, &mut RotationEasingState),
330        (With<RotationInterpolation>, Without<NoRotationEasing>),
331    >,
332) {
333    for (transform, mut easing) in &mut query {
334        easing.end = Some(transform.rotation);
335    }
336}
337
338fn update_scale_interpolation_start(
339    mut query: Query<
340        (&Transform, &mut ScaleEasingState),
341        (With<ScaleInterpolation>, Without<NoScaleEasing>),
342    >,
343) {
344    for (transform, mut easing) in &mut query {
345        easing.start = Some(transform.scale);
346    }
347}
348
349fn update_scale_interpolation_end(
350    mut query: Query<
351        (&Transform, &mut ScaleEasingState),
352        (With<ScaleInterpolation>, Without<NoScaleEasing>),
353    >,
354) {
355    for (transform, mut easing) in &mut query {
356        easing.end = Some(transform.scale);
357    }
358}