bevy_tnua/
basis_action_traits.rs

1use bevy::prelude::*;
2use bevy::time::Stopwatch;
3use bevy_tnua_physics_integration_layer::math::{Float, Vector3};
4
5use std::{any::Any, time::Duration};
6
7use crate::{TnuaMotor, TnuaProximitySensor, TnuaRigidBodyTracker};
8
9/// Various data passed to [`TnuaBasis::apply`].
10pub struct TnuaBasisContext<'a> {
11    /// The duration of the current frame.
12    pub frame_duration: Float,
13
14    /// A sensor that collects data about the rigid body from the physics backend.
15    pub tracker: &'a TnuaRigidBodyTracker,
16
17    /// A sensor that tracks the distance of the character's center from the ground.
18    pub proximity_sensor: &'a TnuaProximitySensor,
19
20    /// The direction considered as "up".
21    pub up_direction: Dir3,
22}
23
24/// The main movement command of a character.
25///
26/// A basis handles the character's motion when the user is not feeding it any input, or when it
27/// just moves around without doing anything special. A simple game would only need once basis -
28/// [`TnuaBuiltinWalk`](crate::builtins::TnuaBuiltinWalk) - but more complex games can have bases
29/// for things like swimming or driving.
30///
31/// The type that implements this trait is called the basis _input_, and is expected to be
32/// overwritten each frame by the controller system of the game code. Configuration is considered
33/// as part of the input. If the basis needs to persist data between frames it must keep it in its
34/// [state](Self::State).
35pub trait TnuaBasis: 'static + Send + Sync {
36    /// The default name of the basis.
37    ///
38    /// [Once `type_name` becomes `const`](https://github.com/rust-lang/rust/issues/63084), this
39    /// will default to it. For now, just set it to the name of the type.
40    const NAME: &'static str;
41
42    /// Data that the basis can persist between frames.
43    ///
44    /// The basis will typically update this in its [`apply`](Self::apply). It has three purposes:
45    ///
46    /// 1. Store data that cannot be calculated on the spot. For example - a timer for tracking
47    ///    coyote time.
48    ///
49    /// 2. Pass data from the basis to the action (or to Tnua's internal mechanisms)
50    ///
51    /// 3. Inspect the basis from game code systems, like an animation controlling system that
52    ///    needs to know which animation to play based on the basis' current state.
53    type State: Default + Send + Sync;
54
55    /// This is where the basis affects the character's motion.
56    ///
57    /// This method gets called each frame to let the basis control the [`TnuaMotor`] that will
58    /// later move the character.
59    ///
60    /// Note that after the motor is set in this method, if there is an action going on, the
61    /// action's [`apply`](TnuaAction::apply) will also run and typically change some of the things
62    /// the basis did to the motor.
63    ///                                                              
64    /// It can also update the state.
65    fn apply(&self, state: &mut Self::State, ctx: TnuaBasisContext, motor: &mut TnuaMotor);
66
67    /// A value to configure the range of the ground proximity sensor according to the basis'
68    /// needs.
69    fn proximity_sensor_cast_range(&self, state: &Self::State) -> Float;
70
71    /// The displacement of the character from where the basis wants it to be.
72    ///
73    /// This is a query method, used by the action to determine what the basis thinks.
74    fn displacement(&self, state: &Self::State) -> Option<Vector3>;
75
76    /// The velocity of the character, relative the what the basis considers its frame of
77    /// reference.
78    ///
79    /// This is a query method, used by the action to determine what the basis thinks.
80    fn effective_velocity(&self, state: &Self::State) -> Vector3;
81
82    /// The vertical velocity the character requires to stay the same height if it wants to move in
83    /// [`effective_velocity`](Self::effective_velocity).
84    fn vertical_velocity(&self, state: &Self::State) -> Float;
85
86    /// Nullify the fields of the basis that represent user input.
87    fn neutralize(&mut self);
88
89    /// Can be queried by an action to determine if the character should be considered "in the air".
90    ///
91    /// This is a query method, used by the action to determine what the basis thinks.
92    fn is_airborne(&self, state: &Self::State) -> bool;
93
94    /// If the basis is at coyote time - finish the coyote time.
95    ///
96    /// This will be called automatically by Tnua, if the controller runs an action that  [violated
97    /// coyote time](TnuaAction::VIOLATES_COYOTE_TIME), so that a long coyote time will not allow,
98    /// for example, unaccounted air jumps.
99    ///
100    /// If the character is fully grounded, this method must not change that.
101    fn violate_coyote_time(&self, state: &mut Self::State);
102}
103
104/// Helper trait for accessing a basis and its trait with dynamic dispatch.
105pub trait DynamicBasis: Send + Sync + Any + 'static {
106    #[doc(hidden)]
107    fn as_any(&self) -> &dyn Any;
108
109    #[doc(hidden)]
110    fn as_mut_any(&mut self) -> &mut dyn Any;
111
112    #[doc(hidden)]
113    fn apply(&mut self, ctx: TnuaBasisContext, motor: &mut TnuaMotor);
114
115    /// Dynamically invokes [`TnuaBasis::proximity_sensor_cast_range`].
116    fn proximity_sensor_cast_range(&self) -> Float;
117
118    /// Dynamically invokes [`TnuaBasis::displacement`].
119    fn displacement(&self) -> Option<Vector3>;
120
121    /// Dynamically invokes [`TnuaBasis::effective_velocity`].
122    fn effective_velocity(&self) -> Vector3;
123
124    /// Dynamically invokes [`TnuaBasis::vertical_velocity`].
125    fn vertical_velocity(&self) -> Float;
126
127    /// Dynamically invokes [`TnuaBasis::neutralize`].
128    fn neutralize(&mut self);
129
130    /// Dynamically invokes [`TnuaBasis::is_airborne`].
131    fn is_airborne(&self) -> bool;
132
133    #[doc(hidden)]
134    fn violate_coyote_time(&mut self);
135}
136
137pub(crate) struct BoxableBasis<B: TnuaBasis> {
138    pub(crate) input: B,
139    pub(crate) state: B::State,
140}
141
142impl<B: TnuaBasis> BoxableBasis<B> {
143    pub(crate) fn new(basis: B) -> Self {
144        Self {
145            input: basis,
146            state: Default::default(),
147        }
148    }
149}
150
151impl<B: TnuaBasis> DynamicBasis for BoxableBasis<B> {
152    fn as_any(&self) -> &dyn Any {
153        self
154    }
155
156    fn as_mut_any(&mut self) -> &mut dyn Any {
157        self
158    }
159
160    fn apply(&mut self, ctx: TnuaBasisContext, motor: &mut TnuaMotor) {
161        self.input.apply(&mut self.state, ctx, motor);
162    }
163
164    fn proximity_sensor_cast_range(&self) -> Float {
165        self.input.proximity_sensor_cast_range(&self.state)
166    }
167
168    fn displacement(&self) -> Option<Vector3> {
169        self.input.displacement(&self.state)
170    }
171
172    fn effective_velocity(&self) -> Vector3 {
173        self.input.effective_velocity(&self.state)
174    }
175
176    fn vertical_velocity(&self) -> Float {
177        self.input.vertical_velocity(&self.state)
178    }
179
180    fn neutralize(&mut self) {
181        self.input.neutralize();
182    }
183
184    fn is_airborne(&self) -> bool {
185        self.input.is_airborne(&self.state)
186    }
187
188    fn violate_coyote_time(&mut self) {
189        self.input.violate_coyote_time(&mut self.state)
190    }
191}
192
193/// Various data passed to [`TnuaAction::apply`].
194#[derive(Clone)]
195pub struct TnuaActionContext<'a> {
196    /// The duration of the current frame.
197    pub frame_duration: Float,
198
199    /// A sensor that collects data about the rigid body from the physics backend.
200    pub tracker: &'a TnuaRigidBodyTracker,
201
202    /// A sensor that tracks the distance of the character's center from the ground.
203    pub proximity_sensor: &'a TnuaProximitySensor,
204
205    /// The direction considered as "up".
206    pub up_direction: Dir3,
207
208    /// An accessor to the currently active basis.
209    pub basis: &'a dyn DynamicBasis,
210}
211
212impl<'a> TnuaActionContext<'a> {
213    /// Can be used to get the concrete basis.
214    ///
215    /// Use with care - actions that use it will only be usable with one basis.
216    pub fn concrete_basis<B: TnuaBasis>(&self) -> Option<(&B, &B::State)> {
217        let boxable_basis: &BoxableBasis<B> = self.basis.as_any().downcast_ref()?;
218        Some((&boxable_basis.input, &boxable_basis.state))
219    }
220
221    /// "Downgrade" to a basis context.
222    ///
223    /// This is useful for some helper methods of [the concrete basis and its
224    /// state](Self::concrete_basis) that require a basis context.
225    pub fn as_basis_context(&self) -> TnuaBasisContext<'a> {
226        TnuaBasisContext {
227            frame_duration: self.frame_duration,
228            tracker: self.tracker,
229            proximity_sensor: self.proximity_sensor,
230            up_direction: self.up_direction,
231        }
232    }
233
234    pub fn frame_duration_as_duration(&self) -> Duration {
235        Duration::from_secs_f64(self.frame_duration.into())
236    }
237}
238
239/// Input for [`TnuaAction::apply`] that informs it about the long-term feeding of the input.
240#[derive(PartialEq, Eq, Debug, Clone, Copy)]
241pub enum TnuaActionLifecycleStatus {
242    /// There was no action in the previous frame
243    Initiated,
244    /// There was a different action in the previous frame
245    CancelledFrom,
246    /// This action was already active in the previous frame, and it keeps getting fed
247    StillFed,
248    /// This action was fed up until the previous frame, and now no action is fed
249    NoLongerFed,
250    /// This action was fed up until the previous frame, and now a different action tries to override it
251    CancelledInto,
252}
253
254impl TnuaActionLifecycleStatus {
255    /// Continue if the action is still fed, finish if its not fed or if some other action gets
256    /// fed.
257    pub fn directive_simple(&self) -> TnuaActionLifecycleDirective {
258        match self {
259            TnuaActionLifecycleStatus::Initiated => TnuaActionLifecycleDirective::StillActive,
260            TnuaActionLifecycleStatus::CancelledFrom => TnuaActionLifecycleDirective::StillActive,
261            TnuaActionLifecycleStatus::StillFed => TnuaActionLifecycleDirective::StillActive,
262            TnuaActionLifecycleStatus::NoLongerFed => TnuaActionLifecycleDirective::Finished,
263            TnuaActionLifecycleStatus::CancelledInto => TnuaActionLifecycleDirective::Finished,
264        }
265    }
266
267    /// Similar to [`directive_simple`](Self::directive_simple), but if some other action gets fed
268    /// and this action is still being fed, reschedule this action once the other action finishes,
269    /// as long as more time than `after_seconds` has passed.
270    pub fn directive_simple_reschedule(
271        &self,
272        after_seconds: Float,
273    ) -> TnuaActionLifecycleDirective {
274        match self {
275            TnuaActionLifecycleStatus::Initiated => TnuaActionLifecycleDirective::StillActive,
276            TnuaActionLifecycleStatus::CancelledFrom => TnuaActionLifecycleDirective::StillActive,
277            TnuaActionLifecycleStatus::StillFed => TnuaActionLifecycleDirective::StillActive,
278            TnuaActionLifecycleStatus::NoLongerFed => {
279                // The rescheduling will probably go away, but in case things happen too fast and
280                // it doesn't - pass it anyway.
281                TnuaActionLifecycleDirective::Reschedule { after_seconds }
282            }
283            TnuaActionLifecycleStatus::CancelledInto => {
284                TnuaActionLifecycleDirective::Reschedule { after_seconds }
285            }
286        }
287    }
288
289    /// Continue - unless the action is cancelled into another action.
290    pub fn directive_linger(&self) -> TnuaActionLifecycleDirective {
291        match self {
292            TnuaActionLifecycleStatus::Initiated => TnuaActionLifecycleDirective::StillActive,
293            TnuaActionLifecycleStatus::CancelledFrom => TnuaActionLifecycleDirective::StillActive,
294            TnuaActionLifecycleStatus::StillFed => TnuaActionLifecycleDirective::StillActive,
295            TnuaActionLifecycleStatus::NoLongerFed => TnuaActionLifecycleDirective::StillActive,
296            TnuaActionLifecycleStatus::CancelledInto => TnuaActionLifecycleDirective::Finished,
297        }
298    }
299
300    /// Determine if the action just started, whether from no action or to replace another action.
301    pub fn just_started(&self) -> bool {
302        match self {
303            TnuaActionLifecycleStatus::Initiated => true,
304            TnuaActionLifecycleStatus::CancelledFrom => true,
305            TnuaActionLifecycleStatus::StillFed => false,
306            TnuaActionLifecycleStatus::NoLongerFed => false,
307            TnuaActionLifecycleStatus::CancelledInto => false,
308        }
309    }
310
311    /// Determine if the action is currently active - still fed and not replaced by another.
312    pub fn is_active(&self) -> bool {
313        match self {
314            TnuaActionLifecycleStatus::Initiated => true,
315            TnuaActionLifecycleStatus::CancelledFrom => true,
316            TnuaActionLifecycleStatus::StillFed => true,
317            TnuaActionLifecycleStatus::NoLongerFed => false,
318            TnuaActionLifecycleStatus::CancelledInto => false,
319        }
320    }
321}
322
323/// A decision by [`TnuaAction::apply`] that determines if the action should be continued or not.
324///
325/// Note that an action may continue (probably with different state) after no longer being fed, or
326/// stopped while still being fed. It's up to the action, and it should be responsible with it.
327#[derive(PartialEq, Debug, Clone, Copy)]
328pub enum TnuaActionLifecycleDirective {
329    /// The action should continue in the next frame.
330    StillActive,
331
332    /// The action should not continue in the next frame.
333    ///
334    /// If another action is pending, it will run in this frame. This means that two actions can
335    /// run in the same frame, as long as the first is finished (or
336    /// [rescheduled](Self::Reschedule))
337    ///
338    /// If [`TnuaAction::apply`] returns this but the action is still being fed, it will not run
339    /// again unless it stops being fed for one frame and then gets fed again. If this is not the
340    /// desired behavior, [`TnuaActionLifecycleDirective::Reschedule`] should be used instead.
341    Finished,
342
343    /// The action should not continue in the next frame, but if its still being fed it run again
344    /// later. The rescheduled action will be considered a new action.
345    ///
346    /// If another action is pending, it will run in this frame. This means that two actions can
347    /// run in the same frame, as long as the first is rescheduled (or [finished](Self::Finished))
348    Reschedule {
349        /// Only reschedule the action after this much time has passed.
350        after_seconds: Float,
351    },
352}
353
354/// A decision by [`TnuaAction::initiation_decision`] that determines if the action can start.
355#[derive(PartialEq, Eq, Debug, Clone, Copy)]
356pub enum TnuaActionInitiationDirective {
357    /// The action will not start as long as the input is still fed. In order to start it, the
358    /// input must be released for at least one frame and then start being fed again.
359    Reject,
360
361    /// The action will not start this frame, but if the input is still fed next frame
362    /// [`TnuaAction::initiation_decision`] will be checked again.
363    Delay,
364
365    /// The action can start this frame.
366    Allow,
367}
368
369/// A character movement command for performing special actions.
370///
371/// "Special" does not necessarily mean **that** special - even
372/// [jumping](crate::builtins::TnuaBuiltinJump) or [crouching](crate::builtins::TnuaBuiltinCrouch)
373/// are considered [`TnuaAction`]s. Unlike basis - which is something constant - an action is
374/// usually something more momentarily that has a flow.
375///
376/// The type that implements this trait is called the action _input_, and is expected to be
377/// overwritten each frame by the controller system of the game code - although unlike basis the
378/// input will probably be the exact same. Configuration is considered as part of the input. If the
379/// action needs to persist data between frames it must keep it in its [state](Self::State).
380pub trait TnuaAction: 'static + Send + Sync {
381    /// The default name of the action.
382    ///
383    /// [Once `type_name` becomes `const`](https://github.com/rust-lang/rust/issues/63084), this
384    /// will default to it. For now, just set it to the name of the type.
385    const NAME: &'static str;
386
387    /// Data that the action can persist between frames.
388    ///
389    /// The action will typically update this in its [`apply`](Self::apply). It has three purposes:
390    ///
391    /// 1. Store data that cannot be calculated on the spot. For example - the part of the jump
392    ///    the character is currently at.
393    ///
394    /// 2. Pass data from the action to Tnua's internal mechanisms.
395    ///
396    /// 3. Inspect the action from game code systems, like an animation controlling system that
397    ///    needs to know which animation to play based on the action's current state.
398    type State: Default + Send + Sync;
399
400    /// Set this to true for actions that may launch the character into the air.
401    const VIOLATES_COYOTE_TIME: bool;
402
403    /// This is where the action affects the character's motion.
404    ///
405    /// This method gets called each frame to let the action control the [`TnuaMotor`] that will
406    /// later move the character. Note that this happens the motor was set by the basis'
407    /// [`apply`](TnuaBasis::apply). Here the action can modify some aspects of or even completely
408    /// overwrite what the basis did.
409    ///                                                              
410    /// It can also update the state.
411    ///
412    /// The returned value of this action determines whether or not the action will continue in the
413    /// next frame.
414    fn apply(
415        &self,
416        state: &mut Self::State,
417        ctx: TnuaActionContext,
418        lifecycle_status: TnuaActionLifecycleStatus,
419        motor: &mut TnuaMotor,
420    ) -> TnuaActionLifecycleDirective;
421
422    /// A value to configure the range of the ground proximity sensor according to the action's
423    /// needs.
424    fn proximity_sensor_cast_range(&self) -> Float {
425        0.0
426    }
427
428    /// Decides whether the action can start.
429    ///
430    /// The difference between rejecting the action here with
431    /// [`TnuaActionInitiationDirective::Reject`] or [`TnuaActionInitiationDirective::Delay`] and
432    /// approving it with [`TnuaActionInitiationDirective::Allow`] only to do nothing in it and
433    /// terminate with [`TnuaActionLifecycleDirective::Finished`] on the first frame, is that if
434    /// some other action is currently running, in the former that action will continue to be
435    /// active, while in the latter it'll be cancelled into this new action - which, having being
436    /// immediately finished, will leave the controller with no active action, or with some third
437    /// action if there is one.
438    fn initiation_decision(
439        &self,
440        ctx: TnuaActionContext,
441        being_fed_for: &Stopwatch,
442    ) -> TnuaActionInitiationDirective;
443
444    /// If the action targets an entity, return that entity
445    fn target_entity(&self, _state: &Self::State) -> Option<Entity> {
446        None
447    }
448}
449
450pub trait DynamicAction: Send + Sync + Any + 'static {
451    fn as_any(&self) -> &dyn Any;
452    fn as_mut_any(&mut self) -> &mut dyn Any;
453    fn apply(
454        &mut self,
455        ctx: TnuaActionContext,
456        lifecycle_status: TnuaActionLifecycleStatus,
457        motor: &mut TnuaMotor,
458    ) -> TnuaActionLifecycleDirective;
459    fn proximity_sensor_cast_range(&self) -> Float;
460    fn initiation_decision(
461        &self,
462        ctx: TnuaActionContext,
463        being_fed_for: &Stopwatch,
464    ) -> TnuaActionInitiationDirective;
465    fn violates_coyote_time(&self) -> bool;
466    fn target_entity(&self) -> Option<Entity>;
467}
468
469pub(crate) struct BoxableAction<A: TnuaAction> {
470    pub(crate) input: A,
471    pub(crate) state: A::State,
472}
473
474impl<A: TnuaAction> BoxableAction<A> {
475    pub(crate) fn new(basis: A) -> Self {
476        Self {
477            input: basis,
478            state: Default::default(),
479        }
480    }
481}
482
483impl<A: TnuaAction> DynamicAction for BoxableAction<A> {
484    fn as_any(&self) -> &dyn Any {
485        self
486    }
487
488    fn as_mut_any(&mut self) -> &mut dyn Any {
489        self
490    }
491
492    fn apply(
493        &mut self,
494        ctx: TnuaActionContext,
495        lifecycle_status: TnuaActionLifecycleStatus,
496        motor: &mut TnuaMotor,
497    ) -> TnuaActionLifecycleDirective {
498        self.input
499            .apply(&mut self.state, ctx, lifecycle_status, motor)
500    }
501
502    fn proximity_sensor_cast_range(&self) -> Float {
503        self.input.proximity_sensor_cast_range()
504    }
505
506    fn initiation_decision(
507        &self,
508        ctx: TnuaActionContext,
509        being_fed_for: &Stopwatch,
510    ) -> TnuaActionInitiationDirective {
511        self.input.initiation_decision(ctx, being_fed_for)
512    }
513
514    fn violates_coyote_time(&self) -> bool {
515        A::VIOLATES_COYOTE_TIME
516    }
517
518    fn target_entity(&self) -> Option<Entity> {
519        self.input.target_entity(&self.state)
520    }
521}