Skip to content

Commit e2d34b2

Browse files
Migrate from required components to bundles
1 parent fc211dd commit e2d34b2

File tree

2 files changed

+82
-96
lines changed

2 files changed

+82
-96
lines changed

crates/game/src/characters/character_controller.rs

+78-88
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
//! Movement and controls for playable characters.
22
33
use avian3d::prelude::*;
4-
use bevy::{ecs::query::Has, prelude::*};
4+
use bevy::{
5+
ecs::{component::ComponentId, query::Has, world::DeferredWorld},
6+
prelude::*,
7+
};
58

69
use avian3d::math::*;
710

@@ -35,8 +38,61 @@ pub enum MovementAction {
3538

3639
/// A marker component indicating that an entity is using a character controller.
3740
#[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)]
3857
pub struct CharacterController;
3958

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+
4096
/// A marker component indicating that an entity is on the ground.
4197
#[derive(Component)]
4298
#[component(storage = "SparseSet")]
@@ -45,107 +101,41 @@ pub struct Grounded;
45101
#[derive(Component)]
46102
pub struct MovementAcceleration(Scalar);
47103

104+
impl Default for MovementAcceleration {
105+
fn default() -> Self {
106+
Self(30.0)
107+
}
108+
}
109+
48110
/// The damping factor used for slowing down movement.
49111
#[derive(Component)]
50112
pub struct MovementDampingFactor(Scalar);
51113

114+
impl Default for MovementDampingFactor {
115+
fn default() -> Self {
116+
Self(0.9)
117+
}
118+
}
119+
52120
/// The strength of a jump.
53121
#[derive(Component)]
54122
pub struct JumpImpulse(Scalar);
55123

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
57131
/// to be able to climb and jump. If the slope is steeper than this angle,
58132
/// the character will slide down.
59133
#[derive(Component)]
60134
pub struct MaxSlopeAngle(Scalar);
61135

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 {
111137
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)
149139
}
150140
}
151141

crates/game/src/main.rs

+4-8
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
mod characters;
44
mod pausing;
55

6-
use avian3d::{math::*, prelude::*};
6+
use avian3d::prelude::*;
77
use bevy::prelude::*;
8-
use characters::character_controller::{CharacterControllerBundle, CharacterControllerPlugin};
8+
use characters::character_controller::{CharacterController, CharacterControllerPlugin};
99
use pausing::PausePlugin;
1010

1111
fn main() {
@@ -29,15 +29,11 @@ fn setup(
2929
) {
3030
// Player
3131
commands.spawn((
32+
CharacterController,
3233
Mesh3d(meshes.add(Capsule3d::new(0.4, 1.0))),
3334
MeshMaterial3d(materials.add(Color::srgb(0.8, 0.7, 0.6))),
3435
Transform::from_xyz(0.0, 1.5, 0.0),
35-
CharacterControllerBundle::new(Collider::capsule(0.4, 1.0)).with_movement(
36-
30.0,
37-
0.92,
38-
7.0,
39-
(30.0 as Scalar).to_radians(),
40-
),
36+
Collider::capsule(0.4, 1.0),
4137
Friction::ZERO.with_combine_rule(CoefficientCombine::Min),
4238
Restitution::ZERO.with_combine_rule(CoefficientCombine::Min),
4339
GravityScale(2.0),

0 commit comments

Comments
 (0)