1
1
//! Movement and controls for playable characters.
2
2
3
3
use avian3d:: prelude:: * ;
4
- use bevy:: { ecs:: query:: Has , prelude:: * } ;
4
+ use bevy:: {
5
+ ecs:: { component:: ComponentId , query:: Has , world:: DeferredWorld } ,
6
+ prelude:: * ,
7
+ } ;
5
8
6
9
use avian3d:: math:: * ;
7
10
@@ -35,8 +38,61 @@ pub enum MovementAction {
35
38
36
39
/// A marker component indicating that an entity is using a character controller.
37
40
#[ derive( Component ) ]
41
+ #[ require(
42
+ Transform ,
43
+ Visibility ,
44
+ RigidBody ,
45
+ Collider ,
46
+ ShapeCaster ,
47
+ LockedAxes ( ||LockedAxes :: ROTATION_LOCKED ) ,
48
+ Friction ,
49
+ Restitution ,
50
+ GravityScale ,
51
+ JumpImpulse ,
52
+ MaxSlopeAngle ,
53
+ MovementAcceleration ,
54
+ MovementDampingFactor ,
55
+ ) ]
56
+ #[ component( on_add = setup_shapecaster) ]
38
57
pub struct CharacterController ;
39
58
59
+ /// Override the default shapecaster on spawn to be based on the collider.
60
+ fn setup_shapecaster ( mut world : DeferredWorld , entity : Entity , _id : ComponentId ) {
61
+ /// The relative size of the shape caster compared to the collider.
62
+ /// Setting this value to less than 1.0 prevents the shape caster from
63
+ /// bouncing on the ground.
64
+ const CAST_SCALE : Scalar = 0.99 ;
65
+
66
+ /// How far out from the collider the shape caster checks for ground.
67
+ /// Setting this value above 0 gives a bit of a buffer to help the controls
68
+ /// feel more responsive.
69
+ const CAST_RADIUS : Scalar = 0.2 ;
70
+
71
+ /// The resolution of the shape caster, in number of subdivisions.
72
+ ///
73
+ /// This is fundamentally a performance vs accuracy tuning knob.
74
+ const CAST_RESOLUTION : u32 = 10 ;
75
+
76
+ let collider = world
77
+ . get :: < Collider > ( entity)
78
+ . expect ( "Collider is a required component of CharacterController" ) ;
79
+
80
+ // Create shape caster as a slightly smaller version of the collider
81
+ let mut caster_shape = collider. clone ( ) ;
82
+ caster_shape. set_scale ( Vector :: ONE * CAST_SCALE , CAST_RESOLUTION ) ;
83
+
84
+ let ground_caster = ShapeCaster :: new (
85
+ caster_shape,
86
+ Vector :: ZERO ,
87
+ Quaternion :: default ( ) ,
88
+ Dir3 :: NEG_Y ,
89
+ )
90
+ . with_max_distance ( CAST_RADIUS ) ;
91
+
92
+ let mut shape_caster = world. get_mut :: < ShapeCaster > ( entity) . unwrap ( ) ;
93
+ * shape_caster = ground_caster;
94
+ }
95
+
40
96
/// A marker component indicating that an entity is on the ground.
41
97
#[ derive( Component ) ]
42
98
#[ component( storage = "SparseSet" ) ]
@@ -45,107 +101,41 @@ pub struct Grounded;
45
101
#[ derive( Component ) ]
46
102
pub struct MovementAcceleration ( Scalar ) ;
47
103
104
+ impl Default for MovementAcceleration {
105
+ fn default ( ) -> Self {
106
+ Self ( 30.0 )
107
+ }
108
+ }
109
+
48
110
/// The damping factor used for slowing down movement.
49
111
#[ derive( Component ) ]
50
112
pub struct MovementDampingFactor ( Scalar ) ;
51
113
114
+ impl Default for MovementDampingFactor {
115
+ fn default ( ) -> Self {
116
+ Self ( 0.9 )
117
+ }
118
+ }
119
+
52
120
/// The strength of a jump.
53
121
#[ derive( Component ) ]
54
122
pub struct JumpImpulse ( Scalar ) ;
55
123
56
- /// The maximum angle a slope can have for a character controller
124
+ impl Default for JumpImpulse {
125
+ fn default ( ) -> Self {
126
+ Self ( 7.0 )
127
+ }
128
+ }
129
+
130
+ /// The maximum angle in radians that a slope can have for a character controller
57
131
/// to be able to climb and jump. If the slope is steeper than this angle,
58
132
/// the character will slide down.
59
133
#[ derive( Component ) ]
60
134
pub struct MaxSlopeAngle ( Scalar ) ;
61
135
62
- /// A bundle that contains the components needed for a basic
63
- /// kinematic character controller.
64
- #[ derive( Bundle ) ]
65
- pub struct CharacterControllerBundle {
66
- /// The character controller marker component.
67
- character_controller : CharacterController ,
68
- /// The rigid body component.
69
- rigid_body : RigidBody ,
70
- /// The collider component.
71
- collider : Collider ,
72
- /// The shape caster used for detecting ground.
73
- ground_caster : ShapeCaster ,
74
- /// The locked axes for the character controller.
75
- locked_axes : LockedAxes ,
76
- /// The movement components for character
77
- movement : MovementBundle ,
78
- }
79
-
80
- /// A bundle that contains components for character movement.
81
- #[ derive( Bundle ) ]
82
- pub struct MovementBundle {
83
- /// The acceleration used for character movement.
84
- acceleration : MovementAcceleration ,
85
- /// The damping factor used for slowing down movement.
86
- damping : MovementDampingFactor ,
87
- /// The strength of a jump.
88
- jump_impulse : JumpImpulse ,
89
- /// The maximum angle that a character can jump on
90
- max_slope_angle : MaxSlopeAngle ,
91
- }
92
-
93
- impl MovementBundle {
94
- /// Create a new movement bundle with the given parameters.
95
- pub const fn new (
96
- acceleration : Scalar ,
97
- damping : Scalar ,
98
- jump_impulse : Scalar ,
99
- max_slope_angle : Scalar ,
100
- ) -> Self {
101
- Self {
102
- acceleration : MovementAcceleration ( acceleration) ,
103
- damping : MovementDampingFactor ( damping) ,
104
- jump_impulse : JumpImpulse ( jump_impulse) ,
105
- max_slope_angle : MaxSlopeAngle ( max_slope_angle) ,
106
- }
107
- }
108
- }
109
-
110
- impl Default for MovementBundle {
136
+ impl Default for MaxSlopeAngle {
111
137
fn default ( ) -> Self {
112
- Self :: new ( 30.0 , 0.9 , 7.0 , PI * 0.45 )
113
- }
114
- }
115
-
116
- impl CharacterControllerBundle {
117
- /// Create a new character controller from a [`Collider`].
118
- pub fn new ( collider : Collider ) -> Self {
119
- // Create shape caster as a slightly smaller version of collider
120
- let mut caster_shape = collider. clone ( ) ;
121
- caster_shape. set_scale ( Vector :: ONE * 0.99 , 10 ) ;
122
-
123
- Self {
124
- character_controller : CharacterController ,
125
- rigid_body : RigidBody :: Dynamic ,
126
- collider,
127
- ground_caster : ShapeCaster :: new (
128
- caster_shape,
129
- Vector :: ZERO ,
130
- Quaternion :: default ( ) ,
131
- Dir3 :: NEG_Y ,
132
- )
133
- . with_max_distance ( 0.2 ) ,
134
- locked_axes : LockedAxes :: ROTATION_LOCKED ,
135
- movement : MovementBundle :: default ( ) ,
136
- }
137
- }
138
-
139
- /// Set the movement parameters for the character controller.
140
- pub fn with_movement (
141
- mut self ,
142
- acceleration : Scalar ,
143
- damping : Scalar ,
144
- jump_impulse : Scalar ,
145
- max_slope_angle : Scalar ,
146
- ) -> Self {
147
- self . movement = MovementBundle :: new ( acceleration, damping, jump_impulse, max_slope_angle) ;
148
- self
138
+ Self ( PI * 0.45 )
149
139
}
150
140
}
151
141
0 commit comments