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