character_controller_collisions
As the character moves along its path, it will hit grounds and obstacles before sliding or stepping on them. Knowing what collider was hit on this path, and where the hit took place, can be valuable to apply various logic (custom forces, sound effects, etc.) This is why a set of character collision events are collected during the calculation of its trajectory.
The character collision events are given in chronological order. For example, if, during the resolution of the character motion, the character hits an obstacle A, then slides against it, and then hits another obstacle B. The collision with A will be reported first, and the collision with B will be reported second.
let character_controller = KinematicCharacterController::default();
// Use a closure to handle or collect the collisions while
// the character is being moved.
character_controller.move_shape(
dt,
&query_pipeline,
character_shape,
character_pos,
desired_translation,
|collision| { /* Handle or collect the collision in this closure. */ },
);
The character collisions are stored in the KinematicCharacterControllerOutput::collisions field
after each update of the character controller:
/* Read the character controller collisions stored in the character controller’s output. */
fn read_character_controller_collisions(
character_controller_outputs: Query<&KinematicCharacterControllerOutput>,
) {
for output in character_controller_outputs.iter() {
for collision in &output.collisions {
// Do something with that collision information.
println!(
"The character hit the entity {:?} after moving by {}.",
collision.entity, collision.translation_applied
);
}
}
}
The hit field of each collision has the same form as the result of a
shape-casting: its first witness point and normal are on the obstacle, in
world-space, whereas its second witness point and normal are on the character, in its local-space.
let characterController = world.createCharacterController(0.01);
characterController.computeColliderMovement(collider, desiredMovementVector);
// After the collider movement calculation is done, we can read the
// collision events.
for (let i = 0; i < characterController.numComputedCollisions(); i++) {
let collision = characterController.computedCollision(i);
// Do something with that collision information.
}
The character collisions are recorded by the character controller during each call to
r3KinematicCharacterController_MoveShape, and remain available until its next call. They are copied into a buffer of
R3CharacterCollision with r3KinematicCharacterController_Collisions (call it once with a NULL buffer and a zero
capacity to get the number of collisions, then a second time to copy them):
R2KinematicCharacterController *character_controller = r2NewKinematicCharacterController();
// The collisions are recorded by the controller while the character is being moved.
r2KinematicCharacterController_MoveShape(world, &options, character_controller, dt,
character_shape, character_pos,
desired_translation);
// Read them after the movement (they remain available until the next movement
// calculation of this controller).
size_t num_collisions = r2KinematicCharacterController_Collisions(character_controller, NULL, 0);
R2CharacterCollision *collisions = malloc(num_collisions * sizeof(R2CharacterCollision));
r2KinematicCharacterController_Collisions(character_controller, collisions, num_collisions);
for (size_t k = 0; k < num_collisions; k++) {
R2CharacterCollision collision = collisions[k];
/* Handle the collision with the collider `collision.collider`. */
(void)collision;
}
free(collisions);
Each collision gives the handle of the collider hit, the pose of the character at the time of the hit
(character_pos), as well as the parts of the desired translation already applied (translation_applied) and
remaining (translation_remaining) at that time. Its hit field has the same form as the result of a
shape-casting: its first witness point and normal are on the obstacle, in
world-space, whereas its second witness point and normal are on the character, in its local-space.
The character collisions are given to the optional events_callback argument of
KinematicCharacterController.move_shape: a callable invoked with each CharacterCollision while the movement is
being calculated (an exception raised by that callable is re-raised by move_shape):
character_controller = rp.KinematicCharacterController()
def on_collision(collision):
# Handle or collect the collision in this callback.
pass
# Give a callback to handle or collect the collisions while
# the character is being moved.
character_controller.move_shape(
dt,
None,
None,
world.query_pipeline,
character_shape,
character_pos,
desired_translation,
query_filter,
events_callback=on_collision,
)
Each collision gives the handle of the collider hit, the pose of the character at the time of the hit
(character_pos), as well as the parts of the desired translation already applied (translation_applied) and
remaining (translation_remaining) at that time. Its hit property is a ShapeCastHit, i.e., it has the same form
as the result of a shape-casting: its first witness point and normal are on the
obstacle, in world-space, whereas its second witness point and normal are on the character, in its local-space.
Unless dynamic bodies are filtered-out by the character controller’s filters, they may be hit during the resolution of the character movement. If that happens, these dynamic bodies will generally not react to (i.e. not be pushed by) the character because the character controller’s offset prevents actual contacts from happening.
In these situations forces need to be applied manually to this rigid-bodies. The character controller can apply these forces for you if needed:
// First, collect all the collisions.
let mut collisions = vec![];
character_controller.move_shape(
dt,
&query_pipeline,
character_shape,
character_pos,
desired_translation,
|collision| collisions.push(collision),
);
// Then, let the character controller solve (and apply) the collision impulses
// to the dynamic rigid-bodies hit along its path.
// Note that we need to init a QueryPipelineMut here (because the impulse
// application will modify rigid-bodies.
let mut query_pipeline_mut = world.broad_phase.as_query_pipeline_mut(
world.narrow_phase.query_dispatcher(),
&mut world.bodies,
&mut world.colliders,
filter,
);
character_controller.solve_character_collision_impulses(
dt,
&mut query_pipeline_mut,
character_shape,
character_mass,
&collisions,
);
/* Configure the character controller when the collider is created. */
commands
.spawn(Collider::ball(0.5))
.insert(KinematicCharacterController {
// Enable the automatic application of impulses to the dynamic bodies
// hit by the character along its path.
apply_impulse_to_dynamic_bodies: true,
..default()
});
/* Configure dynamic impulses inside of a system. */
fn modify_character_controller_impulses(
mut character_controllers: Query<&mut KinematicCharacterController>,
) {
for mut character_controller in character_controllers.iter_mut() {
// Enable the automatic application of impulses to the dynamic bodies
// hit by the character along its path.
character_controller.apply_impulse_to_dynamic_bodies = true;
}
}
The mass of the character taken into account for computing these impulses is the mass of the rigid-body it is
attached to, unless KinematicCharacterController::custom_mass is set.
let characterController = world.createCharacterController(0.01);
// Enable the automatic application of impulses to the dynamic bodies
// hit by the character along its path.
characterController.setApplyImpulsesToDynamicBodies(true);
// First, calculate the movement, which records all the collisions.
r2KinematicCharacterController_MoveShape(world, &options, character_controller, dt,
character_shape, character_pos,
desired_translation);
// Then, let the character controller solve (and apply) the collision impulses
// to the dynamic rigid-bodies hit along its path. Note that this must be given the
// same shape, timestep length, and query options as the movement calculation.
r2KinematicCharacterController_SolveCharacterCollisionImpulses(
character_controller, character_shape, dt, character_mass, &options);
The impulses are computed from the collisions recorded by the last r3KinematicCharacterController_MoveShape call
of the character controller, and applied to the rigid-bodies of the world given to that call. The mass of the
character is given explicitly: it can be, for example, the mass of the rigid-body it is attached to (as given by
r3RigidBody_Mass). The R3QueryOptions should be the same as the ones given to the movement calculation. Unlike
r3KinematicCharacterController_MoveShape, this calls the predicate of the options, if any, once for every collider
of the world before applying the impulses, while the world is locked for writing: it may only use the Read
functions of its R3ReadContext.
# First, collect all the collisions.
collisions = []
character_controller.move_shape(
dt,
None,
None,
world.query_pipeline,
character_shape,
character_pos,
desired_translation,
query_filter,
events_callback=collisions.append,
)
# Then, let the character controller solve (and apply) the collision impulses
# to the dynamic rigid-bodies hit along its path.
character_controller.solve_character_collision_impulses(
dt,
world.rigid_bodies,
world.colliders,
world.query_pipeline,
character_shape,
None, # Unused: each collision stores the pose of the character when it happened.
character_mass,
collisions,
query_filter,
)
The impulses are computed from the collisions collected during the move_shape call (each of them stores the pose
of the character when it happened, so the character_pos argument is unused and can be None), and applied to the
rigid-bodies of the given rigid-body set (which, like the collider set, must be the one of the query pipeline). The
shape and filter given to solve_character_collision_impulses should be the same as the ones given to move_shape.
Unlike move_shape, this calls the predicate of the filter, if any, once for every collider before applying the
impulses, while the sets are locked: it must not modify them. The mass of the character is given explicitly: it can
be, for example, the mass of the rigid-body it is attached to (as given by its RigidBody.mass property).