bevy_tnua/builtins/
climb.rs1use bevy::prelude::*;
2use bevy_tnua_physics_integration_layer::math::{AdjustPrecision, AsF32, Float};
3
4use crate::util::MotionHelper;
5use crate::TnuaActionContext;
6use crate::{
7 math::Vector3, TnuaAction, TnuaActionInitiationDirective, TnuaActionLifecycleDirective,
8 TnuaActionLifecycleStatus, TnuaMotor,
9};
10
11#[derive(Clone)]
13pub struct TnuaBuiltinClimb {
14 pub climbable_entity: Option<Entity>,
16
17 pub anchor: Vector3,
22
23 pub desired_vec_to_anchor: Vector3,
27
28 pub anchor_speed: Float,
30
31 pub anchor_acceleration: Float,
33
34 pub desired_climb_velocity: Vector3,
36
37 pub climb_acceleration: Float,
39
40 pub coyote_time: Float,
42
43 pub desired_forward: Option<Dir3>,
45
46 pub hard_stop_up: Option<Vector3>,
52
53 pub hard_stop_down: Option<Vector3>,
59
60 pub initiation_direction: Vector3,
66}
67
68impl Default for TnuaBuiltinClimb {
69 fn default() -> Self {
70 Self {
71 climbable_entity: None,
72 anchor: Vector3::NAN,
73 desired_vec_to_anchor: Vector3::ZERO,
74 anchor_speed: 150.0,
75 anchor_acceleration: 500.0,
76 desired_climb_velocity: Vector3::ZERO,
77 climb_acceleration: 30.0,
78 coyote_time: 0.15,
79 desired_forward: None,
80 hard_stop_up: None,
81 hard_stop_down: None,
82 initiation_direction: Vector3::ZERO,
83 }
84 }
85}
86
87impl TnuaAction for TnuaBuiltinClimb {
88 const NAME: &'static str = "TnuaBuiltinClimb";
89
90 type State = TnuaBuiltinClimbState;
91
92 const VIOLATES_COYOTE_TIME: bool = true;
93
94 fn apply(
95 &self,
96 state: &mut Self::State,
97 ctx: TnuaActionContext,
98 lifecycle_status: TnuaActionLifecycleStatus,
99 motor: &mut TnuaMotor,
100 ) -> TnuaActionLifecycleDirective {
101 for _ in 0..2 {
104 return match state {
105 TnuaBuiltinClimbState::Climbing { climbing_velocity } => {
106 if matches!(lifecycle_status, TnuaActionLifecycleStatus::NoLongerFed) {
107 *state = TnuaBuiltinClimbState::Coyote(Timer::from_seconds(
108 self.coyote_time.f32(),
109 TimerMode::Once,
110 ));
111 continue;
112 }
113
114 *climbing_velocity = ctx
117 .tracker
118 .velocity
119 .project_onto(ctx.up_direction.adjust_precision());
120
121 motor
122 .lin
123 .cancel_on_axis(ctx.up_direction.adjust_precision());
124 motor.lin += ctx.negate_gravity();
125 motor.lin += ctx.adjust_vertical_velocity(
126 self.desired_climb_velocity
127 .dot(ctx.up_direction.adjust_precision()),
128 self.climb_acceleration,
129 );
130
131 if let Some(stop_at) = self.hard_stop_up {
132 motor.lin += ctx.hard_stop(ctx.up_direction, stop_at, &motor.lin);
133 }
134 if let Some(stop_at) = self.hard_stop_down {
135 motor.lin += ctx.hard_stop(-ctx.up_direction, stop_at, &motor.lin);
136 }
137
138 let vec_to_anchor = (self.anchor - ctx.tracker.translation)
139 .reject_from(ctx.up_direction().adjust_precision());
140 let horizontal_displacement = self.desired_vec_to_anchor - vec_to_anchor;
141
142 let desired_horizontal_velocity = -horizontal_displacement / ctx.frame_duration;
143
144 motor.lin += ctx.adjust_horizontal_velocity(
145 desired_horizontal_velocity.clamp_length_max(self.anchor_speed),
146 self.anchor_acceleration,
147 );
148
149 if let Some(desired_forward) = self.desired_forward {
150 motor
151 .ang
152 .cancel_on_axis(ctx.up_direction.adjust_precision());
153 motor.ang += ctx.turn_to_direction(desired_forward, ctx.up_direction);
154 }
155
156 lifecycle_status.directive_simple()
157 }
158 TnuaBuiltinClimbState::Coyote(timer) => {
159 if timer.tick(ctx.frame_duration_as_duration()).finished() {
160 TnuaActionLifecycleDirective::Finished
161 } else {
162 lifecycle_status.directive_linger()
163 }
164 }
165 };
166 }
167 error!("Tnua could not decide on climb state");
168 TnuaActionLifecycleDirective::Finished
169 }
170
171 fn initiation_decision(
172 &self,
173 _ctx: TnuaActionContext,
174 _being_fed_for: &bevy::time::Stopwatch,
175 ) -> TnuaActionInitiationDirective {
176 TnuaActionInitiationDirective::Allow
177 }
178
179 fn target_entity(&self, _state: &Self::State) -> Option<Entity> {
180 self.climbable_entity
181 }
182}
183
184#[derive(Debug)]
185pub enum TnuaBuiltinClimbState {
186 Climbing { climbing_velocity: Vector3 },
187 Coyote(Timer),
188}
189
190impl Default for TnuaBuiltinClimbState {
191 fn default() -> Self {
192 Self::Climbing {
193 climbing_velocity: Vector3::ZERO,
194 }
195 }
196}