character_controller_setup
The character controller is used through the KinematicCharacterController component. This
component contains the character controller’s behavior settings, as well as the translation to apply to the character.
The KinematicCharacterController component must be added to the same entity as a Transform component. If the field
KinematicCharacterController::custom_shape isn’t set, then the entity it is attached to must also contain a Collider component.
That collider can optionally be attached to a rigid-body. At each frame, the KinematicCharacterController::translation
field can be set to the desired translation for that character.
During the next physics update step, that translation will be resolved against obstacles, and the resulting movement will be automatically applied to the entity’s transform, or the transform of the entity containing the rigid-body the collider to move is attached to.
The applied character motion, and the information of whether the character
is touching the ground at its final position, can be read with the KinematicCharacterControllerOutput component
(inserted automatically to the same entity as the KinematicCharacterController component).
- Example 2D
- Example 3D
fn setup_physics(mut commands: Commands) {
commands
.spawn(RigidBody::KinematicPositionBased)
.insert(Collider::ball(0.5))
.insert(KinematicCharacterController::default());
}
fn update_system(mut controllers: Query<&mut KinematicCharacterController>) {
for mut controller in controllers.iter_mut() {
controller.translation = Some(Vec2::new(1.0, -0.5));
}
}
fn read_result_system(controllers: Query<(Entity, &KinematicCharacterControllerOutput)>) {
for (entity, output) in controllers.iter() {
println!(
"Entity {:?} moved by {:?} and touches the ground: {:?}",
entity, output.effective_translation, output.grounded
);
}
}
fn setup_physics(mut commands: Commands) {
commands
.spawn(RigidBody::KinematicPositionBased)
.insert(Collider::ball(0.5))
.insert(Transform::default())
.insert(KinematicCharacterController {
..KinematicCharacterController::default()
});
}
fn update_system(time: Res<Time>, mut controllers: Query<&mut KinematicCharacterController>) {
for mut controller in controllers.iter_mut() {
controller.translation = Some(Vec3::new(1.0, -5.0, -1.0) * time.delta_secs());
}
}
fn read_result_system(controllers: Query<(Entity, &KinematicCharacterControllerOutput)>) {
for (entity, output) in controllers.iter() {
println!(
"Entity {:?} moved by {:?} and touches the ground: {:?}",
entity, output.effective_translation, output.grounded
);
}
}
The character controller can also be used without the KinematicCharacterController component, by calling
RapierContextMut::move_shape (from the WriteRapierContext system parameter) with the desired translation, the shape
of the character (a Collider can be given directly), and its current position. This calculates the possible movement
right away, based on the positions of the colliders at the end of the last timestep, but doesn't apply it: it is up to
you to apply the resulting MoveShapeOutput::effective_translation to the character. The behavior of the controller is
configured by the MoveShapeOptions argument (which has the same settings as the KinematicCharacterController
component), and its obstacles by the QueryFilter argument detailed in the
scene query filters section. The collisions are given to the last argument, a
closure called on each obstacle hit along the path:
- Example 2D
- Example 3D
/// Marks a character moved without the `KinematicCharacterController` component.
#[derive(Component)]
struct ManualCharacter;
fn move_character_manually(
mut context: WriteRapierContext,
mut characters: Query<(Entity, &Collider, &mut Transform), With<ManualCharacter>>,
) -> Result {
let mut context = context.single_mut()?;
for (entity, collider, mut transform) in characters.iter_mut() {
// The translation we would like to apply if there were no obstacles.
let desired_translation = Vec2::new(1.0, -0.5);
// Configure the controller like with the `KinematicCharacterController` component.
let options = MoveShapeOptions {
snap_to_ground: Some(CharacterLength::Absolute(0.5)),
..default()
};
// Make sure the character we are trying to move isn’t considered an obstacle.
let filter = QueryFilter::default().exclude_collider(entity);
// Calculate the possible movement.
let output = context.move_shape(
desired_translation,
collider, // The character’s shape.
transform.translation.truncate(), // The character’s initial position.
transform.rotation.to_euler(EulerRot::ZYX).0, // The character’s rotation.
1.0, // The character’s mass, for the impulses applied to dynamic bodies.
&options,
filter,
|collision| println!("The character hit the entity {:?}.", collision.entity),
);
// The movement isn’t applied automatically.
transform.translation += output.effective_translation.extend(0.0);
}
Ok(())
}
/// Marks a character moved without the `KinematicCharacterController` component.
#[derive(Component)]
struct ManualCharacter;
fn move_character_manually(
time: Res<Time>,
mut context: WriteRapierContext,
mut characters: Query<(Entity, &Collider, &mut Transform), With<ManualCharacter>>,
) -> Result {
let mut context = context.single_mut()?;
for (entity, collider, mut transform) in characters.iter_mut() {
// The translation we would like to apply if there were no obstacles.
let desired_translation = Vec3::new(1.0, -5.0, -1.0) * time.delta_secs();
// Configure the controller like with the `KinematicCharacterController` component.
let options = MoveShapeOptions {
snap_to_ground: Some(CharacterLength::Absolute(0.5)),
..default()
};
// Make sure the character we are trying to move isn’t considered an obstacle.
let filter = QueryFilter::default().exclude_collider(entity);
// Calculate the possible movement.
let output = context.move_shape(
desired_translation,
collider, // The character’s shape.
transform.translation, // The character’s initial position.
transform.rotation, // The character’s rotation.
1.0, // The character’s mass, for the impulses applied to dynamic bodies.
&options,
filter,
|collision| println!("The character hit the entity {:?}.", collision.entity),
);
// The movement isn’t applied automatically.
transform.translation += output.effective_translation;
}
Ok(())
}
Unlike the KinematicCharacterController component, move_shape doesn't know which collider is the character. If the
character is a collider (or is attached to a rigid-body) present in the physics scene, the filter must exclude that
collider (and that rigid-body) from the set of obstacles (with QueryFilter::exclude_collider and
QueryFilter::exclude_rigid_body) to prevent the character from colliding with itself.
The character’s shape may be any shape supported by Rapier. However, it is recommended to either use a cuboid, a ball, or a capsule since they involve less computations and less numerical approximations.
The built-in character controller does not support rotational movement. It only supports translations.