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