bevy_mod_wanderlust/components/
settings.rs

1use bevy::ecs::reflect::ReflectComponent;
2use bevy::math::vec3;
3use bevy::prelude::{default, warn, Component, Entity, Vec3};
4use bevy::reflect::Reflect;
5use bevy::utils::HashSet;
6use bevy_rapier3d::prelude::Collider;
7
8use crate::Spring;
9
10/// The settings of a character controller. See each individual field for more description.
11///
12/// The [`Default::default()`] of this type is not well configured; it is not a good reference for any character controller, and will not do much.
13/// See bundles like [`CharacterControllerBundle`](super::bundles::CharacterControllerBundle) for well-config
14#[derive(Component, Reflect)]
15#[reflect(Component)]
16pub struct ControllerSettings {
17    /// How quickly to interpolate from `last_goal_velocity` to the new `input_goal_velocity`.
18    /// In other words, how quickly to go from "not moving" to "moving at max speed".
19    pub acceleration: f32,
20    /// The length of the calculated `input_goal_velocity`.
21    /// In other words, the speed to attempt to reach if a movement input (such as forwards) is fully saturated.
22    ///
23    /// Keys are generally either not saturated or fully saturated, while analog controls like a joystick can be partially saturated (half tilt).
24    pub max_speed: f32,
25    /// The maximum amount of force that can be applied to fulfill [`acceleration`](ControllerSettings::acceleration).
26    pub max_acceleration_force: f32,
27    /// The direction to jump, which is also the direction that gravity is opposite to.
28    pub up_vector: Vec3,
29    /// The direction to face towards, or `None` to not rotate to face any direction. Must be perpendicular to the up vector and normalized.
30    pub forward_vector: Option<Vec3>,
31    /// The strength of gravity.
32    pub gravity: f32,
33    /// The maximum angle that the ground can be, in radians, before it is no longer considered suitable for being "grounded" on.
34    ///
35    /// For example, if this is set to `π/4` (45 degrees), then a player standing on a slope steeper than 45 degrees will slip and fall, and will not have
36    /// their jump refreshed by landing on that surface.
37    pub max_ground_angle: f32,
38    /// While floating, the character can be floating at a different exact distance than [`float_distance`] depending on other forces acting on them.
39    /// This field controls how much lower than [`float_distance`] they can be and still be considered grounded.
40    ///
41    /// This helps keep jumps more consistent when the ground cast length is longer than the float distance.
42    pub min_float_offset: f32,
43    /// While floating, the character can be floating at a different exact distance than [`float_distance`] depending on other forces acting on them.
44    /// This field controls how much higher than [`float_distance`] they can be and still be considered grounded.
45    ///
46    /// This helps keep jumps more consistent when the ground cast length is longer than the float distance.
47    pub max_float_offset: f32,
48    /// The amount of force to apply on the first frame when a jump begins.
49    pub jump_initial_force: f32,
50    /// The amount of force to continuously apply every second during a jump.
51    pub jump_force: f32,
52    /// The amount of force to apply downwards when the jump control is released prior to a jump expiring.
53    /// This allows analog jumping by cutting the jump short when the control is released.
54    pub jump_stop_force: f32,
55    /// How long a jump can last.
56    pub jump_time: f32,
57    /// A function taking the current progress of a jump, from 0.0 to 1.0, with 0.0 indicating a jump has just begun and 1.0 indicating the jump has ended,
58    /// which returns a modifier (usually from 0.0 to 1.0, but not necessarily) to multiply [`jump_force`](ControllerSettings::jump_force) by.
59    #[reflect(ignore)]
60    pub jump_decay_function: Option<fn(f32) -> f32>,
61    /// How long to skip ground checks after jumping. Usually this should be set just high enough that the character is out of range of the ground
62    /// just before the timer elapses.
63    pub jump_skip_ground_check_duration: f32,
64    /// Override skip ground check. If true, never checks for the ground.
65    pub skip_ground_check_override: bool,
66    /// How many extra times the character can jump after leaving the ground. 0 is normal, 1 corresponds to double jump, etc.
67    pub extra_jumps: u32,
68    /// How long should the character still be able to jump after leaving the ground, in seconds.
69    /// For example, if this is set to 0.5, the player can fall off a ledge and then jump if they do so within 0.5 seconds of leaving the ledge.
70    pub coyote_time_duration: f32,
71    /// If the jump input is pressed before landing, how long will the jump be buffered for?
72    /// In other words, if this is 0.5, the character can input jump up to 0.5 seconds before landing and the jump will occur when they land.
73    pub jump_buffer_duration: f32,
74    /// Scales movement force. This is useful to ensure movement does not affect vertical velocity (by setting it to e.g. `Vec3(1.0, 0.0, 1.0)`).
75    pub force_scale: Vec3,
76    /// How long of a ray to cast to detect the ground. Setting this unnecessarily high will permanently count the player as grounded,
77    /// and too low will allow the player to slip and become disconnected from the ground easily.
78    pub float_cast_length: f32,
79    /// An offset to start the ground check from, relative to the character's origin.
80    pub float_cast_origin: Vec3,
81    /// What shape of ray to cast. See [`Collider`] and [`RapierContext::cast_shape`](RapierContext).
82    #[reflect(ignore)]
83    pub float_cast_collider: Collider,
84    /// How far to attempt to float away from the ground.
85    pub float_distance: f32,
86    /// How strongly to float away from the ground.
87    pub float_spring: Spring,
88    /// How strongly to force the character upright/avoid overshooting. Alternatively, see [`LockedAxes`] to lock rotation entirely.
89    pub upright_spring: Spring,
90    /// Set of entities that should be ignored when ground casting.
91    pub exclude_from_ground: HashSet<Entity>,
92    /// Scaling factor for the impulse applied to the ground to keep the character moving/off the ground.
93    pub opposing_impulse_scale: f32,
94    /// Scaling factor for the movement impulse applied to the ground.
95    /// Setting this to 0.0 would make it so things don't "slip" out from the characters feet.
96    pub opposing_movement_impulse_scale: f32,
97}
98
99impl Default for ControllerSettings {
100    fn default() -> Self {
101        Self {
102            acceleration: default(),
103            max_speed: default(),
104            max_acceleration_force: default(),
105            up_vector: default(),
106            forward_vector: default(),
107            gravity: default(),
108            max_ground_angle: default(),
109            min_float_offset: default(),
110            max_float_offset: default(),
111            jump_initial_force: default(),
112            jump_force: default(),
113            jump_stop_force: default(),
114            jump_time: 1.0,
115            jump_decay_function: None,
116            jump_skip_ground_check_duration: default(),
117            skip_ground_check_override: default(),
118            extra_jumps: default(),
119            coyote_time_duration: default(),
120            jump_buffer_duration: default(),
121            force_scale: default(),
122            float_cast_length: default(),
123            float_cast_origin: default(),
124            float_cast_collider: Collider::ball(1.0),
125            float_distance: default(),
126            float_spring: default(),
127            upright_spring: default(),
128            exclude_from_ground: default(),
129            opposing_impulse_scale: 1.0,
130            opposing_movement_impulse_scale: 1.0,
131        }
132    }
133}
134
135impl ControllerSettings {
136    /// A basic preset for a standard, walking character controller. Works for most first and third person games.
137    pub fn character() -> Self {
138        ControllerSettings {
139            acceleration: 50.0,
140            max_speed: 10.0,
141            max_acceleration_force: 10.0,
142            up_vector: Vec3::Y,
143            //gravity: -9.8,
144            gravity: -20.0,
145            max_ground_angle: 45.0 * (std::f32::consts::PI / 180.0),
146            min_float_offset: -0.3,
147            max_float_offset: 0.05,
148            jump_time: 0.5,
149            jump_initial_force: 15.0,
150            jump_stop_force: 0.3,
151            jump_decay_function: Some(|x| (1.0 - x).sqrt()),
152            jump_skip_ground_check_duration: 0.5,
153            coyote_time_duration: 0.16,
154            jump_buffer_duration: 0.16,
155            force_scale: vec3(1.0, 0.0, 1.0),
156            float_cast_length: 1.0,
157            float_cast_collider: Collider::ball(0.45),
158            float_distance: 0.55,
159            float_spring: Spring {
160                strength: 100.0,
161                damping: 0.8,
162            },
163            upright_spring: Spring {
164                strength: 10.0,
165                damping: 0.5,
166            },
167            ..default()
168        }
169    }
170
171    /// A sample controller preset for a spaceship which can fly in any direction.
172    pub fn starship() -> Self {
173        ControllerSettings {
174            acceleration: 0.3,
175            max_speed: 100.0,
176            max_acceleration_force: 10.0,
177            up_vector: Vec3::Y,
178            force_scale: vec3(1.0, 1.0, 1.0),
179            upright_spring: Spring {
180                strength: 0.0,
181                damping: 0.0,
182            },
183            ..default()
184        }
185    }
186
187    /// Validate that assumptions made in the settings are correct.
188    pub fn valid(&self) -> bool {
189        let mut valid = true;
190        if !self.up_vector.is_normalized() {
191            warn!("controller up vector is not normalized");
192            valid = false;
193        }
194
195        valid
196    }
197}