Skip to main content

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).

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
);
}
}

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:

/// 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(())
}
warning

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.

info

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.

warning

The built-in character controller does not support rotational movement. It only supports translations.