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}