character_controller_setup
The character controller implementation is exposed as the KinematicCharacterController structure. This structure only
contains information about the character controller’s behavior. It does not contain any collider-specific or
rigid-body-specific information like handles, velocities, positions, etc. Therefore, the same instance of KinematicCharacterController
can be used to control multiple rigid-bodies/colliders if they rely on the same set of parameters.
The KinematicCharacterController exposes only two methods:
move_shapeis responsible for calculating the possible movement of a character based on the desired movement, obstacles, and character controller options.solve_character_collision_impulsesis detailed in the collisions section.
- Example 2D
- Example 3D
// The translation we would like to apply if there were no obstacles.
let desired_translation = Vector::new(1.0, -2.0);
// Create the character controller, here with the default configuration.
let character_controller = KinematicCharacterController::default();
// Init the query pipeline.
let filter = QueryFilter::default()
// Make sure the character we are trying to move isn’t considered an obstacle.
.exclude_rigid_body(rigid_body_handle);
let query_pipeline = world.query_pipeline_with_filter(filter);
// Calculate the possible movement.
let corrected_movement = character_controller.move_shape(
dt, // The timestep length (can be set to SimulationSettings::dt).
&query_pipeline, // The query pipeline.
character_shape, // The character’s shape.
character_pos, // The character’s initial position.
desired_translation,
|_| {}, // We don’t care about events in this example.
);
// TODO: apply the `corrected_movement.translation` to the rigid-body or collider based on the rules described below.
// The translation we would like to apply if there were no obstacles.
let desired_translation = Vector::new(1.0, -2.0, 3.0);
// Create the character controller, here with the default configuration.
let character_controller = KinematicCharacterController::default();
// Init the query pipeline.
let filter = QueryFilter::default()
// Make sure the character we are trying to move isn’t considered an obstacle.
.exclude_rigid_body(rigid_body_handle);
let query_pipeline = world.query_pipeline_with_filter(filter);
// Calculate the possible movement.
let corrected_movement = character_controller.move_shape(
dt, // The timestep length (can be set to SimulationSettings::dt).
&query_pipeline, // The query pipeline.
character_shape, // The character’s shape.
character_pos, // The character’s initial position.
desired_translation,
|_| {}, // We don’t care about events in this example.
);
// TODO: apply the `corrected_movement.translation` to the rigid-body or collider based on the rules described below.
The recommended way to update the character’s position depends on its representation:
- A collider not attached to any rigid-body: set the collider’s position directly to the corrected movement added to its current position.
- A velocity-based kinematic rigid-body: set its velocity to the computed movement divided by the timestep length.
- A position-based kinematic rigid-body: set its next kinematic position to the corrected movement added to its current position.
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.
A new character controller can be created and removed by the physics World:
// The gap the controller will leave between the character and its environment.
let offset = 0.01;
// Create the controller.
let characterController = world.createCharacterController(offset);
// Remove the controller once we are done with it.
world.removeCharacterController(characterController);
Note that the character controller does not store a reference to the rigid-body and collider it controls. Therefore, the
same instance of the CharacterController class can be used to control different colliders. This can be useful if
you want to apply the same kind of character control settings to multiple characters.
The created character controller can then be used to control the movement of a collider taking into account obstacles on its path. This is done in two steps:
- Given a desired translation, compute the actual translation that we can apply to the collider based on the obstacles.
- Read the result and apply it to the rigid-body or collider (if it isn’t attached to a rigid-body) by setting its position, kinematic velocity, or next kinematic position, depending on the situation.
let characterController = world.createCharacterController(offset);
characterController.computeColliderMovement(
collider, // The collider we would like to move.
desiredTranslation, // The movement we would like to apply if there wasn’t any obstacle.
);
// Read the result.
let correctedMovement = characterController.computedMovement();
// TODO: apply this corrected movement by following the rules described below.
The recommended way to update the character’s position depends on its representation:
- A collider not attached to any rigid-body: set the collider’s position directly (with
collider.setTranslation) to the corrected movement added to its current position. - A velocity-based kinematic rigid-body: set its velocity (with
rigidBody.setLinvel) to the computed movement divided by the timestep length. - A position-based kinematic rigid-body: set its next kinematic position (with
rigidBody.setNextKinematicTranslation) to the corrected movement added to its current position.
The character controller implementation is exposed as the R3KinematicCharacterController object, created with its
default configuration by r3NewKinematicCharacterController, and freed by r3FreeKinematicCharacterController. This
object only contains information about the character controller’s behavior (as well as the collisions recorded by its
last movement calculation, see the collisions section). It does not contain
any collider-specific or rigid-body-specific information like handles, velocities, positions, etc. Therefore, the same
R3KinematicCharacterController can be used to control multiple rigid-bodies/colliders if they rely on the same set
of parameters. Its behavior is configured by its setters (detailed in the next sections), and it is used through the
following functions:
r3KinematicCharacterController_MoveShapeis responsible for calculating the possible movement of a character based on the desired movement, obstacles, and character controller options. It is given the world containing the obstacles, theR3QueryOptionsselecting these obstacles (see the filtering section), the timestep length, the shape of the character, and its current pose. The character’s shape is anR3SharedShape, e.g., created withr3BallSharedShapeor cloned from a collider withr3Collider_CloneShape(and freed withr3FreeSharedShapeonce it is no longer needed).r3KinematicCharacterController_Collisionsandr3KinematicCharacterController_SolveCharacterCollisionImpulsesare detailed in the collisions section.
- Example 2D
- Example 3D
// The translation we would like to apply if there were no obstacles.
R2Vector desired_translation = r2Vector(1.0, -2.0);
// Create the character controller, here with the default configuration.
R2KinematicCharacterController *character_controller = r2NewKinematicCharacterController();
// Init the query options.
R2QueryOptions options = r2DefaultQueryOptions();
// Make sure the character we are trying to move isn't considered an obstacle.
options.filter.exclude_rigid_body = rigid_body_handle;
// Calculate the possible movement.
R2CharacterMovement corrected_movement = r2KinematicCharacterController_MoveShape(
world, // The world containing the obstacles.
&options, // The query options (NULL for the default ones).
character_controller, // The character controller.
dt, // The timestep length (can be set to r2TimeStep(world)).
character_shape, // The character's shape.
character_pos, // The character's initial position.
desired_translation);
// TODO: apply the `corrected_movement.translation` to the rigid-body or collider based on the rules described below.
// Free the character controller once it is no longer needed.
r2FreeKinematicCharacterController(character_controller);
// The translation we would like to apply if there were no obstacles.
R3Vector desired_translation = r3Vector(1.0, -2.0, 3.0);
// Create the character controller, here with the default configuration.
R3KinematicCharacterController *character_controller = r3NewKinematicCharacterController();
// Init the query options.
R3QueryOptions options = r3DefaultQueryOptions();
// Make sure the character we are trying to move isn't considered an obstacle.
options.filter.exclude_rigid_body = rigid_body_handle;
// Calculate the possible movement.
R3CharacterMovement corrected_movement = r3KinematicCharacterController_MoveShape(
world, // The world containing the obstacles.
&options, // The query options (NULL for the default ones).
character_controller, // The character controller.
dt, // The timestep length (can be set to r3TimeStep(world)).
character_shape, // The character's shape.
character_pos, // The character's initial position.
desired_translation);
// TODO: apply the `corrected_movement.translation` to the rigid-body or collider based on the rules described below.
// Free the character controller once it is no longer needed.
r3FreeKinematicCharacterController(character_controller);
The obstacles are taken at the positions they had at the end of the last r3Step (or r3DetectCollisions). The
returned R3CharacterMovement contains the corrected movement (its translation field), as well as whether the
character touches the ground at its final position (its grounded field), and whether it is sliding down a slope that
is too steep to climb (its is_sliding_down_slope field). The corrected movement isn’t applied automatically: the
recommended way to update the character’s position depends on its representation:
- A collider not attached to any rigid-body: set the collider’s position directly (with
r3Collider_SetTranslation) to the corrected movement added to its current position. - A velocity-based kinematic rigid-body: set its velocity (with
r3RigidBody_SetLinvel) to the computed movement divided by the timestep length. - A position-based kinematic rigid-body: set its next kinematic position (with
r3RigidBody_SetNextKinematicTranslation) to the corrected movement added to its current position.
The character controller implementation is exposed as the KinematicCharacterController class. This class only
contains information about the character controller’s behavior. It does not contain any collider-specific or
rigid-body-specific information like handles, velocities, positions, etc. Therefore, the same instance of
KinematicCharacterController can be used to control multiple rigid-bodies/colliders if they rely on the same set of
parameters. Its settings (detailed in the next sections) are properties that can also be given as keyword arguments
to its constructor (e.g. KinematicCharacterController(slide=False, max_slope_climb_angle=0.5)). The
KinematicCharacterController exposes only two methods:
move_shapeis responsible for calculating the possible movement of a character based on the desired movement, obstacles, and character controller options. It is given the timestep length, the rigid-body set and the collider set (both unused and kept for backward compatibility: they can beNone, or must be the sets of the query pipeline), the query pipeline of the world containing the obstacles (e.g.world.query_pipeline), the shape of the character (aSharedShape, e.g. theshapeof itsCollider), its current pose, the desired translation, and optionally theQueryFilterselecting the obstacles (see the filtering section) and a callback receiving the collisions (see the collisions section).solve_character_collision_impulsesis detailed in the collisions section.
# The translation we would like to apply if there were no obstacles.
desired_translation = (1.0, -2.0, 3.0)
# Create the character controller, here with the default configuration.
character_controller = rp.KinematicCharacterController()
# Make sure the character we are trying to move isn’t considered an obstacle.
query_filter = rp.QueryFilter().exclude_rigid_body(rigid_body_handle)
# Calculate the possible movement.
corrected_movement = character_controller.move_shape(
dt, # The timestep length (can be set to world.integration_parameters.dt).
None, # The rigid-body set, unused: the one of the query pipeline is used.
None, # The collider set, unused: the one of the query pipeline is used.
world.query_pipeline, # The query pipeline containing the obstacles.
character_shape, # The character’s shape.
character_pos, # The character’s initial position.
desired_translation,
query_filter, # The obstacles to consider.
)
# TODO: apply the `corrected_movement.translation` to the rigid-body or collider based on the rules described below.
The obstacles are taken at the positions they had at the end of the last PhysicsWorld.step (or the last
PhysicsWorld.update_query_pipeline). The returned EffectiveCharacterMovement contains the corrected movement (its
translation property), as well as whether the character touches the ground at its final position (its grounded
property), and whether it is sliding down a slope that is too steep to climb (its is_sliding_down_slope property).
The corrected movement isn’t applied automatically: the recommended way to update the character’s position depends on
its representation:
- A collider not attached to any rigid-body: set the collider’s position directly (with its
translationproperty) to the corrected movement added to its current position. - A velocity-based kinematic rigid-body: set its velocity (with its
linvelproperty) to the computed movement divided by the timestep length. - A position-based kinematic rigid-body: set its next kinematic position (with
RigidBody.set_next_kinematic_translation) to the corrected movement added to its current position.
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.