pub struct MoveAndSlide<'w, 's> {
pub spatial_query: SpatialQuery<'w, 's>,
pub colliders: Query<'w, 's, (&'static Collider, &'static Position, &'static Rotation, Option<&'static CollisionLayers>), (With<ColliderOf>, Without<Sensor>)>,
pub length_unit: Res<'w, PhysicsLengthUnit>,
}Expand description
A SystemParam for the move and slide algorithm, also known as collide and slide or step slide.
Move and slide is the core movement and collision algorithm used by most kinematic character controllers. It attempts to move a shape along a desired velocity vector, sliding along any colliders that are hit on the way.
§Algorithm
At a high level, the algorithm works as follows:
- Sweep the shape along the desired velocity vector.
- If no collision is detected, move the full distance.
- If a collision is detected:
- Move up to the point of collision.
- Project the remaining velocity onto the contact surfaces to obtain a new sliding velocity.
- Repeat with the new sliding velocity until movement is complete.
The algorithm also includes depenetration passes before and after movement to improve stability and ensure that the shape does not intersect any colliders.
§Configuration
MoveAndSlideConfig allows configuring various aspects of the algorithm.
See its documentation for more information.
Additionally, move_and_slide can be given a callback that is called
for each contact surface that is detected during movement. This allows for custom handling of collisions,
such as triggering events, or modifying movement based on specific colliders.
§Other Utilities
In addition to the main move_and_slide method, this system parameter also provides utilities for:
- Performing shape casts optimized for movement via
cast_move. - Depenetrating shapes that are intersecting colliders via
depenetrate. - Performing intersection tests via
intersections. - Projecting velocities to slide along contact planes via
project_velocity.
These methods are used internally by the move and slide algorithm, but can also be used independently for custom movement and collision handling.
§Resources
Some useful resources for learning more about the move and slide algorithm include:
- Collide And Slide - *Actually Decent* Character Collision From Scratch by Poke Dev (video)
PM_SlideMovein Quake III Arena (source code)
Note that while the high-level concepts are similar across different implementations, details may vary.
Fields§
§spatial_query: SpatialQuery<'w, 's>The SpatialQuery system parameter used to perform shape casts and other geometric queries.
colliders: Query<'w, 's, (&'static Collider, &'static Position, &'static Rotation, Option<&'static CollisionLayers>), (With<ColliderOf>, Without<Sensor>)>The Query used to query for colliders.
length_unit: Res<'w, PhysicsLengthUnit>A units-per-meter scaling factor that adjusts some thresholds and tolerances to the scale of the world for better behavior.
Implementations§
Source§impl<'w, 's> MoveAndSlide<'w, 's>
impl<'w, 's> MoveAndSlide<'w, 's>
Sourcepub fn move_and_slide(
&self,
shape: &Collider,
shape_position: Vector,
shape_rotation: Quaternion,
velocity: Vector,
delta_time: Duration,
config: &MoveAndSlideConfig,
filter: &SpatialQueryFilter,
on_hit: impl FnMut(MoveAndSlideHitData<'_>) -> MoveAndSlideHitResponse,
) -> MoveAndSlideOutput
pub fn move_and_slide( &self, shape: &Collider, shape_position: Vector, shape_rotation: Quaternion, velocity: Vector, delta_time: Duration, config: &MoveAndSlideConfig, filter: &SpatialQueryFilter, on_hit: impl FnMut(MoveAndSlideHitData<'_>) -> MoveAndSlideHitResponse, ) -> MoveAndSlideOutput
Moves a shape along a given velocity vector, sliding along any colliders that are hit on the way.
See MoveAndSlide for an overview of the algorithm.
§Arguments
shape: The shape being cast represented as aCollider.shape_position: Where the shape is cast from.shape_rotation: The rotation of the shape being cast.velocity: The initial velocity vector along which to move the shape. This will be modified to reflect sliding along surfaces.delta_time: The duration over which to move the shape.velocity * delta_timegives the total desired movement vector.config: AMoveAndSlideConfigthat determines the behavior of the move and slide.MoveAndSlideConfig::default()should be a good start for most cases.filter: ASpatialQueryFilterthat determines which colliders are taken into account in the query. It is highly recommended to exclude the entity holding the collider itself, otherwise the character will collide with itself.on_hit: A callback that is called when a collider is hit as part of the move and slide iterations. The returnedMoveAndSlideHitResponsedetermines how to handle the hit. If you don’t have any special handling per collision, you can pass|_| MoveAndSlideHitResponse::Accept.
§Example
use bevy::prelude::*;
use std::collections::HashSet;
use avian3d::{prelude::*, math::{Vector, AdjustPrecision as _, AsF32 as _}};
#[derive(Component)]
struct CharacterController {
velocity: Vector,
}
fn perform_move_and_slide(
player: Single<(Entity, &Collider, &mut CharacterController, &mut Transform)>,
move_and_slide: MoveAndSlide,
time: Res<Time>
) {
let (entity, collider, mut controller, mut transform) = player.into_inner();
let velocity = controller.velocity + Vector::X * 10.0;
let filter = SpatialQueryFilter::from_excluded_entities([entity]);
let mut collisions = HashSet::new();
let out = move_and_slide.move_and_slide(
collider,
transform.translation.adjust_precision(),
transform.rotation.adjust_precision(),
velocity,
time.delta(),
&MoveAndSlideConfig::default(),
&filter,
|hit| {
collisions.insert(hit.entity);
MoveAndSlideHitResponse::Accept
},
);
transform.translation = out.position.f32();
controller.velocity = out.projected_velocity;
info!("Colliding with entities: {:?}", collisions);
}Sourcepub fn cast_move(
&self,
shape: &Collider,
shape_position: Vector,
shape_rotation: Quaternion,
movement: Vector,
skin_width: Scalar,
filter: &SpatialQueryFilter,
) -> Option<MoveHitData>
pub fn cast_move( &self, shape: &Collider, shape_position: Vector, shape_rotation: Quaternion, movement: Vector, skin_width: Scalar, filter: &SpatialQueryFilter, ) -> Option<MoveHitData>
A shape cast optimized for movement. Use this if you want to move a collider
with a given velocity and stop so that it keeps a distance of skin_width from the first collider on its path.
This operation is most useful when you ensure that the character is not intersecting any colliders before moving.
To do this, call MoveAndSlide::depenetrate and add the resulting offset vector to the character’s position
before calling this method. See the example below.
It is often useful to clip the velocity afterwards so that it no longer points into the contact plane using Self::project_velocity.
§Arguments
shape: The shape being cast represented as aCollider.shape_position: Where the shape is cast from.shape_rotation: The rotation of the shape being cast.movement: The direction and magnitude of the movement. If this isVector::ZERO, this method can still returnSome(MoveHitData)if the shape started off intersecting a collider.skin_width: AShapeCastConfigthat determines the behavior of the cast.filter: ASpatialQueryFilterthat determines which colliders are taken into account in the query. It is highly recommended to exclude the entity holding the collider itself, otherwise the character will collide with itself.
§Returns
Some(MoveHitData)if the shape hit a collider on the way, or started off intersecting a collider.Noneif the shape is able to move the full distance without hitting a collider.
§Example
use bevy::prelude::*;
use avian3d::{prelude::*, math::{Vector, Dir, AdjustPrecision as _, AsF32 as _}};
#[derive(Component)]
struct CharacterController {
velocity: Vector,
}
fn perform_cast_move(
player: Single<(Entity, &Collider, &mut CharacterController, &mut Transform)>,
move_and_slide: MoveAndSlide,
time: Res<Time>
) {
let (entity, collider, mut controller, mut transform) = player.into_inner();
let filter = SpatialQueryFilter::from_excluded_entities([entity]);
let config = MoveAndSlideConfig::default();
// Ensure that the character is not intersecting with any colliders.
let offset = move_and_slide.depenetrate(
collider,
transform.translation.adjust_precision(),
transform.rotation.adjust_precision(),
&((&config).into()),
&filter,
);
transform.translation += offset.f32();
let velocity = controller.velocity;
let hit = move_and_slide.cast_move(
collider,
transform.translation.adjust_precision(),
transform.rotation.adjust_precision(),
velocity * time.delta_secs().adjust_precision(),
config.skin_width,
&filter,
);
if let Some(hit) = hit {
// We collided with something on the way. Advance as much as possible.
transform.translation += (velocity.normalize_or_zero() * hit.distance).f32();
// Then project the velocity to make sure it no longer points towards the contact plane.
controller.velocity =
MoveAndSlide::project_velocity(velocity, &[Dir::new_unchecked(hit.normal1.f32())])
} else {
// We traveled the full distance without colliding.
transform.translation += velocity.f32();
}
}§Related methods
Sourcepub fn depenetrate(
&self,
shape: &Collider,
shape_position: Vector,
shape_rotation: Quaternion,
config: &DepenetrationConfig,
filter: &SpatialQueryFilter,
) -> Vector
pub fn depenetrate( &self, shape: &Collider, shape_position: Vector, shape_rotation: Quaternion, config: &DepenetrationConfig, filter: &SpatialQueryFilter, ) -> Vector
Moves a collider so that it no longer intersects any other collider and keeps a minimum distance
of DepenetrationConfig::skin_width scaled by the PhysicsLengthUnit.
Depenetration is an iterative process that solves penetrations for all planes, until we either reached
MoveAndSlideConfig::move_and_slide_iterations or the accumulated error is less than MoveAndSlideConfig::max_depenetration_error.
If the maximum number of iterations was reached before the error is below the threshold, the current best attempt is returned,
in which case the collider may still be intersecting with other colliders.
This method is equivalent to calling Self::depenetrate_intersections with the results of Self::intersections.
§Arguments
shape: The shape that intersections are tested against represented as aCollider.shape_position: The position of the shape.shape_rotation: The rotation of the shape.config: ADepenetrationConfigthat determines the behavior of the depenetration.DepenetrationConfig::default()should be a good start for most cases.filter: ASpatialQueryFilterthat determines which colliders are taken into account in the query.
§Example
use bevy::prelude::*;
use avian3d::{prelude::*, character_controller::move_and_slide::DepenetrationConfig, math::{AdjustPrecision as _, AsF32 as _}};
fn depenetrate_player(
player: Single<(Entity, &Collider, &mut Transform)>,
move_and_slide: MoveAndSlide,
time: Res<Time>
) {
let (entity, collider, mut transform) = player.into_inner();
let filter = SpatialQueryFilter::from_excluded_entities([entity]);
let offset = move_and_slide.depenetrate(
collider,
transform.translation.adjust_precision(),
transform.rotation.adjust_precision(),
&DepenetrationConfig::default(),
&filter,
);
transform.translation += offset.f32();
}See also MoveAndSlide::cast_move for a typical usage scenario.
§Related methods
Sourcepub fn depenetrate_intersections(
&self,
config: &DepenetrationConfig,
intersections: &[(Dir, Scalar)],
) -> Vector
pub fn depenetrate_intersections( &self, config: &DepenetrationConfig, intersections: &[(Dir, Scalar)], ) -> Vector
Manual version of MoveAndSlide::depenetrate.
Moves a collider so that it no longer intersects any other collider and keeps a minimum distance
of DepenetrationConfig::skin_width scaled by the PhysicsLengthUnit. The intersections
should be provided as a list of contact plane normals and penetration distances, which can be obtained
via MoveAndSlide::intersections.
Depenetration is an iterative process that solves penetrations for all planes, until we either reached
MoveAndSlideConfig::move_and_slide_iterations or the accumulated error is less than MoveAndSlideConfig::max_depenetration_error.
If the maximum number of iterations was reached before the error is below the threshold, the current best attempt is returned,
in which case the collider may still be intersecting with other colliders.
§Arguments
config: ADepenetrationConfigthat determines the behavior of the depenetration.DepenetrationConfig::default()should be a good start for most cases.intersections: A list of contact plane normals and penetration distances representing the intersections to resolve.
§Returns
A displacement vector that can be added to the shape_position to resolve the intersections,
or the best attempt if the max iterations were reached before a solution was found.
§Example
use bevy::prelude::*;
use avian3d::{prelude::*, character_controller::move_and_slide::DepenetrationConfig, math::{AdjustPrecision as _, AsF32 as _}};
fn depenetrate_player_manually(
player: Single<(Entity, &Collider, &mut Transform)>,
move_and_slide: MoveAndSlide,
time: Res<Time>
) {
let (entity, collider, mut transform) = player.into_inner();
let filter = SpatialQueryFilter::from_excluded_entities([entity]);
let config = DepenetrationConfig::default();
let mut intersections = Vec::new();
move_and_slide.intersections(
collider,
transform.translation.adjust_precision(),
transform.rotation.adjust_precision(),
config.skin_width,
&filter,
|contact_point, normal| {
intersections.push((normal, contact_point.penetration + config.skin_width));
true
},
);
let offset = move_and_slide.depenetrate_intersections(&config, &intersections);
transform.translation += offset.f32();
}§Related methods
Sourcepub fn intersections(
&self,
shape: &Collider,
shape_position: Vector,
shape_rotation: Quaternion,
prediction_distance: Scalar,
filter: &SpatialQueryFilter,
callback: impl FnMut(&ContactPoint, Dir) -> bool,
)
pub fn intersections( &self, shape: &Collider, shape_position: Vector, shape_rotation: Quaternion, prediction_distance: Scalar, filter: &SpatialQueryFilter, callback: impl FnMut(&ContactPoint, Dir) -> bool, )
An intersection test that calls a callback for each Collider found
that is closer to the given shape with a given position and rotation than prediction_distance.
§Arguments
shape: The shape that intersections are tested against represented as aCollider.shape_position: The position of the shape.shape_rotation: The rotation of the shape.filter: ASpatialQueryFilterthat determines which colliders are taken into account in the query.prediction_distance: An extra margin applied to theCollider.callback: A callback that is called for each intersection found. The callback receives the deepest contact point and the contact normal. Returningfalsewill stop further processing of intersections.
§Example
See MoveAndSlide::depenetrate_intersections for a typical usage scenario.
§Related methods
Sourcepub fn project_velocity(v: Vector, normals: &[Dir]) -> Vector
pub fn project_velocity(v: Vector, normals: &[Dir]) -> Vector
Projects input velocity v onto the planes defined by the given normals.
This ensures that velocity does not point into any of the planes, but along them.
This is often used after MoveAndSlide::cast_move to ensure a character moved that way
does not try to continue moving into colliding geometry.
Trait Implementations§
Source§impl SystemParam for MoveAndSlide<'_, '_>
impl SystemParam for MoveAndSlide<'_, '_>
Source§type Item<'w, 's> = MoveAndSlide<'w, 's>
type Item<'w, 's> = MoveAndSlide<'w, 's>
Self, instantiated with new lifetimes. Read moreSource§fn init_access(
state: &Self::State,
system_meta: &mut SystemMeta,
component_access_set: &mut FilteredAccessSet,
world: &mut World,
)
fn init_access( state: &Self::State, system_meta: &mut SystemMeta, component_access_set: &mut FilteredAccessSet, world: &mut World, )
World access used by this SystemParamSource§fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World)
fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World)
SystemParam’s state.
This is used to apply Commands during ApplyDeferred.Source§fn queue(
state: &mut Self::State,
system_meta: &SystemMeta,
world: DeferredWorld<'_>,
)
fn queue( state: &mut Self::State, system_meta: &SystemMeta, world: DeferredWorld<'_>, )
ApplyDeferred.Source§unsafe fn validate_param<'w, 's>(
state: &'s mut Self::State,
_system_meta: &SystemMeta,
_world: UnsafeWorldCell<'w>,
) -> Result<(), SystemParamValidationError>
unsafe fn validate_param<'w, 's>( state: &'s mut Self::State, _system_meta: &SystemMeta, _world: UnsafeWorldCell<'w>, ) -> Result<(), SystemParamValidationError>
Source§unsafe fn get_param<'w, 's>(
state: &'s mut Self::State,
system_meta: &SystemMeta,
world: UnsafeWorldCell<'w>,
change_tick: Tick,
) -> Self::Item<'w, 's>
unsafe fn get_param<'w, 's>( state: &'s mut Self::State, system_meta: &SystemMeta, world: UnsafeWorldCell<'w>, change_tick: Tick, ) -> Self::Item<'w, 's>
SystemParamFunction. Read moreimpl<'w, 's> ReadOnlySystemParam for MoveAndSlide<'w, 's>where
SpatialQuery<'w, 's>: ReadOnlySystemParam,
Query<'w, 's, (&'static Collider, &'static Position, &'static Rotation, Option<&'static CollisionLayers>), (With<ColliderOf>, Without<Sensor>)>: ReadOnlySystemParam,
Res<'w, PhysicsLengthUnit>: ReadOnlySystemParam,
Auto Trait Implementations§
impl<'w, 's> Freeze for MoveAndSlide<'w, 's>
impl<'w, 's> !RefUnwindSafe for MoveAndSlide<'w, 's>
impl<'w, 's> Send for MoveAndSlide<'w, 's>
impl<'w, 's> Sync for MoveAndSlide<'w, 's>
impl<'w, 's> Unpin for MoveAndSlide<'w, 's>
impl<'w, 's> !UnwindSafe for MoveAndSlide<'w, 's>
Blanket Implementations§
Source§impl<T, U> AsBindGroupShaderType<U> for T
impl<T, U> AsBindGroupShaderType<U> for T
Source§fn as_bind_group_shader_type(&self, _images: &RenderAssets<GpuImage>) -> U
fn as_bind_group_shader_type(&self, _images: &RenderAssets<GpuImage>) -> U
T ShaderType for self. When used in AsBindGroup
derives, it is safe to assume that all images in self exist.Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>, which can then be
downcast into Box<dyn ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>, which can then be further
downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.Source§impl<T> DowncastSend for T
impl<T> DowncastSend for T
Source§impl<T> DowncastSync for T
impl<T> DowncastSync for T
Source§impl<T, W> HasTypeWitness<W> for Twhere
W: MakeTypeWitness<Arg = T>,
T: ?Sized,
impl<T, W> HasTypeWitness<W> for Twhere
W: MakeTypeWitness<Arg = T>,
T: ?Sized,
Source§impl<T> Identity for Twhere
T: ?Sized,
impl<T> Identity for Twhere
T: ?Sized,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> IntoResult<T> for T
impl<T> IntoResult<T> for T
Source§fn into_result(self) -> Result<T, RunSystemError>
fn into_result(self) -> Result<T, RunSystemError>
Source§impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
Source§fn to_subset(&self) -> Option<SS>
fn to_subset(&self) -> Option<SS>
self from the equivalent element of its
superset. Read moreSource§fn is_in_subset(&self) -> bool
fn is_in_subset(&self) -> bool
self is actually part of its subset T (and can be converted to it).Source§fn to_subset_unchecked(&self) -> SS
fn to_subset_unchecked(&self) -> SS
self.to_subset but without any property checks. Always succeeds.Source§fn from_subset(element: &SS) -> SP
fn from_subset(element: &SS) -> SP
self to the equivalent element of its superset.