Character Movement

Description

The CharacterMovement component is a robust and feature-rich fully kinematic character controller, also known as a motor.

It has been developed as a compelling alternative to Unity's character controller, maintaining the familiar workflow through functions like Move / SimpleMove, while surpassing it with a myriad of enhanced features and advantages.

What is a character controller ?

A Character Controller facilitates straightforward movement constrained by collisions, eliminating the need to grapple with a dynamic Rigidbody.

Unlike a dynamic Rigidbody, a Character Controller remains unaffected by forces and will only initiate movement upon calling the Move function. Subsequently, it executes the intended movement while adhering to collision constraints.

In the earlier days of gaming, titles didn't rely on a 'true' physics engine like the PhysX SDK (Unity's underlying physics engine). However, they still employed a character controller to navigate players within a level. Games like Quake or Doom utilized a bespoke piece of code for collision detection and response, often constituting the sole physics aspect in the entire game. While these implementations lacked extensive physics, they heavily leaned on meticulously adjusted values to deliver an immersive player experience. The specific algorithm governing their behavior is commonly known as the 'collide and slide' algorithm, refined over more than a decade for optimal performance.

The CharacterMovement component is an implementation of such an algorithm, delivering a reliable and widely recognized behavior for character control.

The Character Movement Component

Details

The CharacterMovement component, in addition to its primary function of constrained movement by collision, offers a broad range of specifically designed functions to leverage its capabilities to the fullest.

Properties

/// <summary>
/// Cached character's transform.
/// </summary>

public new Transform transform

/// <summary>
/// The Character's rigidbody.
/// </summary>

public new Rigidbody rigidbody

/// <summary>
/// The Rigidbody interpolation setting.
/// </summary>

public RigidbodyInterpolation interpolation

/// <summary>
/// The Character's collider.
/// </summary>

public new Collider collider

/// <summary>
/// The root bone in the avatar.
/// </summary>

public Transform rootTransform

/// <summary>
/// The root transform will be positioned at this offset.
/// </summary>

public Vector3 rootTransformOffset

/// <summary>
/// The character's current position.
/// </summary>

public Vector3 position

/// <summary>
/// The character's current rotation.
/// </summary>

public Quaternion rotation

/// <summary>
/// The character's center in world space.
/// </summary>

public Vector3 worldCenter

/// <summary>
/// The character's updated position.
/// </summary>

public Vector3 updatedPosition


/// <summary>
/// The character's updated rotation.
/// </summary>

public Quaternion updatedRotation

/// <summary>
/// The current relative velocity of the Character.
/// The velocity is relative because it won't track movements to the transform that happen outside of this,
/// e.g. character parented under another moving Transform, such as a moving vehicle.
/// </summary>

public ref Vector3 velocity

/// <summary>
/// The character's speed.
/// </summary>

public float speed

/// <summary>
/// The character's speed along its forward vector (e.g: in local space).
/// </summary>

public float forwardSpeed

/// <summary>
/// The character's speed along its right vector (e.g: in local space).
/// </summary>

public float sidewaysSpeed

/// <summary>
/// The Character's capsule collider radius.
/// </summary>

public float radius

/// <summary>
/// The Character's capsule collider height.
/// </summary>

public float height

/// <summary>
/// The maximum angle (in degrees) for a walkable slope.
/// </summary>

public float slopeLimit

/// <summary>
/// The maximum height (in meters) for a valid step.
/// </summary>

public float stepOffset

/// <summary>
/// Allow a Character to perch on the edge of a surface if the horizontal distance from the Character's position to the edge is closer than this.
/// Note that we still enforce stepOffset to start the step up, this just allows the Character to hang off the edge or step slightly higher off the ground.
/// </summary>

public float perchOffset

/// <summary>
/// When perching on a ledge, add this additional distance to stepOffset when determining how high above a walkable ground we can perch.
/// </summary>

public float perchAdditionalHeight

/// <summary>
/// Should allow external slope limit override ?
/// </summary>

public bool slopeLimitOverride

/// <summary>
/// When enabled, will treat head collisions as if the character is using a shape with a flat top.
/// </summary>

public bool useFlatTop

/// <summary>
/// Performs ground checks as if the character is using a shape with a flat base.
/// This avoids the situation where characters slowly lower off the side of a ledge (as their capsule 'balances' on the edge).
/// </summary>

public bool useFlatBaseForGroundChecks

/// <summary>
/// Layers to be considered during collision detection.
/// </summary>

public LayerMask collisionLayers

/// <summary>
/// Determines how the Character should interact with triggers.
/// </summary>

public QueryTriggerInteraction triggerInteraction

/// <summary>
/// Should perform collision detection ?
/// </summary>

public bool detectCollisions

/// <summary>
/// What part of the capsule collided with the environment during the last Move call.
/// </summary>

public CollisionFlags collisionFlags

/// <summary>
/// Is the Character's movement constrained to a plane ?
/// </summary>

public bool isConstrainedToPlane

/// <summary>
/// Should movement be constrained to ground when on walkable ground ?
/// Toggles ground constraint. 
/// </summary>

public bool constrainToGround

/// <summary>
/// Is the Character constrained to walkable ground ?
/// </summary>

public bool isConstrainedToGround

/// <summary>
/// Is the ground constraint temporary disabled?
/// </summary>

public bool isGroundConstraintPaused

/// <summary>
/// If isGroundConstraintPaused is true, this represent the pause remaining time.
/// </summary>

public float unconstrainedTimer

/// <summary>
/// Was the character on ground last Move call ?
/// </summary>

public bool wasOnGround

/// <summary>
/// Is the character on ground ?
/// </summary>

public bool isOnGround

/// <summary>
/// Was the character on walkable ground last Move call ?
/// </summary>

public bool wasOnWalkableGround

/// <summary>
/// Is the character on walkable ground ?
/// </summary>

public bool isOnWalkableGround

/// <summary>
/// Was the character on walkable ground AND constrained to ground last Move call ?
/// </summary>

public bool wasGrounded

/// <summary>
/// Is the character on walkable ground AND constrained to ground.
/// </summary>

public bool isGrounded

/// <summary>
/// The signed distance to ground.
/// </summary>

public float groundDistance

/// <summary>
/// The current ground impact point.
/// </summary>

public Vector3 groundPoint

/// <summary>
/// The current ground normal.
/// </summary>

public Vector3 groundNormal

/// <summary>
/// The current ground surface normal.
/// </summary>

public Vector3 groundSurfaceNormal

/// <summary>
/// The current ground collider.
/// </summary>

public Collider groundCollider

/// <summary>
/// The current ground transform.
/// </summary>

public Transform groundTransform

/// <summary>
/// The Rigidbody of the collider that was hit. If the collider is not attached to a rigidbody then it is null.
/// </summary>

public Rigidbody groundRigidbody

/// <summary>
/// Structure containing information about current ground.
/// </summary>

public FindGroundResult currentGround

/// <summary>
/// Structure containing information about current moving platform (if any).
/// </summary>

public MovingPlatform movingPlatform

/// <summary>
/// The terminal velocity when landed (eg: isGrounded).
/// </summary>

public Vector3 landedVelocity

/// <summary>
/// Set this to true if riding on a moving platform that you know is clear from non-moving world obstructions.
/// Optimization to avoid sweeps during based movement, USE WITH CARE.
/// </summary>

public bool fastPlatformMove

/// <summary>
/// Whether the Character moves with the moving platform it is standing on.
/// If true, the Character moves with the moving platform.
/// </summary>

public bool impartPlatformMovement

/// <summary>
/// Whether the Character receives the changes in rotation of the platform it is standing on.
/// If true, the Character rotates with the moving platform.
/// </summary>

public bool impartPlatformRotation

/// <summary>
/// If true, impart the platform's velocity when jumping or falling off it.
/// </summary>

public bool impartPlatformVelocity

/// <summary>
/// If enabled, the player will interact with dynamic rigidbodies when walking into them.
/// </summary>

public bool enablePhysicsInteraction

/// <summary>
/// If enabled, the player will interact with other characters when walking into them.
/// </summary>

public bool physicsInteractionAffectsCharacters

/// <summary>
/// Force applied to rigidbodies when walking into them (due to mass and relative velocity) is scaled by this amount.
/// </summary>

public float pushForceScale

Public Methods

/// <summary>
/// Specifies the character's bounding volume (eg: capsule) dimensions.
/// </summary>
/// <param name="characterRadius">The character's volume radius.</param>
/// <param name="characterHeight">The character's volume height</param>

public void SetDimensions(float characterRadius, float characterHeight)

/// <summary>
/// Specifies the character's bounding volume (eg: capsule) height.
/// </summary>
/// <param name="characterHeight">The character's volume height</param>

public void SetHeight(float characterHeight)

/// <summary>
/// Returns the character current position.
/// </summary>

public Vector3 GetPosition()

/// <summary>
/// Update character current position.
/// If updateGround is true, will find for ground and update character's current ground result.
/// </summary>

public void SetPosition(Vector3 newPosition, bool updateGround = false)

/// <summary>
/// Returns the character current rotation.
/// </summary>

public Quaternion GetRotation()

/// <summary>
/// Update character current rotation.
/// </summary>

public void SetRotation(Quaternion newRotation)

/// <summary>
/// Sets the world space position and rotation of this character.
/// If updateGround is true, will find for ground and update character's current ground result.
/// </summary>

public void SetPositionAndRotation(Vector3 newPosition, Quaternion newRotation, bool updateGround = false)

/// <summary>
/// Orient the character's towards the given direction (in world space) using maxDegreesDelta as the rate of rotation change.
/// </summary>
/// <param name="worldDirection">The target direction in world space.</param>
/// <param name="maxDegreesDelta">Change in rotation per second (Deg / s).</param>
/// <param name="updateYawOnly">If True, the rotation will be performed on the Character's plane (defined by its up-axis).</param>

public void RotateTowards(Vector3 worldDirection, float maxDegreesDelta, bool updateYawOnly = true)

/// <summary>
/// Current plane constraint normal.
/// </summary>

public Vector3 GetPlaneConstraintNormal()

/// <summary>
/// Defines the axis that constraints movement, so movement along the given axis is not possible.
/// </summary>

public void SetPlaneConstraint(PlaneConstraint constrainAxis, Vector3 planeNormal)

/// <summary>
/// Returns the given DIRECTION (Normalized) vector constrained to current constraint plane (if _constrainToPlane != None)
/// or given vector (if _constrainToPlane == None).
/// </summary>

public Vector3 ConstrainDirectionToPlane(Vector3 direction)

/// <summary>
/// Constrain the given vector to current PlaneConstraint (if any).
/// </summary>

public Vector3 ConstrainVectorToPlane(Vector3 vector)

/// <summary>
/// Adds a force to the Character.
/// This forces will be accumulated and applied during Move method call.
/// </summary>

public void AddForce(Vector3 force, ForceMode forceMode = ForceMode.Force)

/// <summary>
/// Applies a force to this Character that simulates explosion effects.
/// The explosion is modeled as a sphere with a certain centre position and radius in world space; 
/// normally, anything outside the sphere is not affected by the explosion and the force decreases in proportion to distance from the centre.
/// However, if a value of zero is passed for the radius then the full force will be applied regardless of how far the centre is from the rigidbody.
/// The force direction is from the given origin to the Character center.
/// </summary>

public void AddExplosionForce(float strength, Vector3 origin, float radius, ForceMode forceMode = ForceMode.Force)

/// <summary>
/// Set a pending launch velocity on the Character. This velocity will be processed next Move call.
/// </summary>
/// <param name="launchVelocity">The desired launch velocity.</param>
/// <param name="overrideVerticalVelocity">If true replace the vertical component of the Character's velocity instead of adding to it.</param>
/// <param name="overrideLateralVelocity">If true replace the XY part of the Character's velocity instead of adding to it.</param>

public void LaunchCharacter(Vector3 launchVelocity, bool overrideVerticalVelocity = false, bool overrideLateralVelocity = false)

/// <summary>
/// Allows you to explicitly attach this to a moving 'platform' so it no depends of ground state.
/// </summary>

public void AttachTo(Rigidbody parent)

/// <summary>
/// Temporarily disable ground constraint allowing the Character to freely leave the ground.
/// Eg: LaunchCharacter, Jump, etc.
/// </summary>

public void PauseGroundConstraint(float unconstrainedTime = 0.1f)

/// <summary>
/// Moves the character along its current velocity.
/// This performs collision constrained movement resolving any collisions / overlaps found during this movement.
/// </summary>
/// <param name="deltaTime">The simulation deltaTime.</param>

public CollisionFlags Move(float deltaTime)

/// <summary>
/// Moves the character along the given velocity vector.
/// This performs collision constrained movement resolving any collisions / overlaps found during this movement.
/// </summary>
/// <param name="newVelocity">The updated velocity for current frame. It is typically a combination of vertical motion due to gravity and lateral motion when your character is moving.</param>
/// <param name="deltaTime">The simulation deltaTime. If not assigned, it defaults to Time.deltaTime.</param>
/// <returns>Return CollisionFlags. It indicates the direction of a collision: None, Sides, Above, and Below.</returns>

public CollisionFlags Move(Vector3 newVelocity, float deltaTime)

/// <summary>
/// Update the character's velocity using a friction-based physical model and move the character along its updated velocity.
/// This performs collision constrained movement resolving any collisions / overlaps found during this movement.
/// </summary>
/// <param name="desiredVelocity">Target velocity</param>
/// <param name="maxSpeed">The maximum speed when grounded. Also determines maximum horizontal speed when falling (i.e. not-grounded).</param>
/// <param name="acceleration">The rate of change of velocity when accelerating (i.e desiredVelocity != Vector3.zero).</param>
/// <param name="deceleration">The rate at which the character slows down when braking (i.e. not accelerating or if character is exceeding max speed).
/// This is a constant opposing force that directly lowers velocity by a constant value.</param>
/// <param name="friction">Setting that affects movement control. Higher values allow faster changes in direction.</param>
/// <param name="brakingFriction">Friction (drag) coefficient applied when braking (whenever desiredVelocity == Vector3.zero, or if character is exceeding max speed).</param>
/// <param name="gravity">The current gravity force. Defaults to zero.</param>
/// <param name="onlyHorizontal">Determines if the vertical velocity component should be ignored when falling (i.e. not-grounded) preserving gravity effects. Defaults to true.</param>
/// <param name="deltaTime">The simulation deltaTime.</param>
/// <returns>Return CollisionFlags. It indicates the direction of a collision: None, Sides, Above, and Below.</returns>

public CollisionFlags SimpleMove(Vector3 desiredVelocity, float maxSpeed, float acceleration,
            float deceleration, float friction, float brakingFriction, Vector3 gravity = default,
            bool onlyHorizontal = true, float deltaTime = 0.0f)
            
/// <summary>
/// Return the number of collisions found during last Move call.
/// </summary>

public int GetCollisionCount()

/// <summary>
/// Retrieves a CollisionResult from last Move call list.
/// </summary>

public CollisionResult GetCollisionResult(int index)

/// <summary>
/// Makes the character to ignore all collisions vs otherCollider.
/// </summary>

public void IgnoreCollision(Collider otherCollider, bool ignore = true)

/// <summary>
/// Makes the character to ignore collisions vs all colliders attached to the otherRigidbody.
/// </summary>

public void IgnoreCollision(Rigidbody otherRigidbody, bool ignore = true)

/// <summary>
/// Makes the character's collider (eg: CapsuleCollider) to ignore all collisions vs otherCollider.
/// NOTE: The character can still collide with other during a Move call if otherCollider is in CollisionLayers mask.
/// </summary>

public void CapsuleIgnoreCollision(Collider otherCollider, bool ignore = true)

/// <summary>
/// Sweeps the character's volume along its displacement vector, stopping at near hit point if collision is detected.
/// Returns True when the rigidbody sweep intersects any collider, otherwise false.
/// </summary>

public bool MovementSweepTest(Vector3 characterPosition, Vector3 sweepDirection, float sweepDistance,
    out CollisionResult collisionResult)
{
    return MovementSweepTest(characterPosition, velocity, sweepDirection * sweepDistance, out collisionResult);
}

/// <summary>
/// Compute distance to the ground from bottom sphere of capsule and store the result in collisionResult.
/// This distance is the swept distance of the capsule to the first point impacted by the lower hemisphere,
/// or distance from the bottom of the capsule in the case of a raycast.
/// </summary>

public void ComputeGroundDistance(Vector3 characterPosition, float sweepRadius, float sweepDistance,
    float castDistance, out FindGroundResult outGroundResult)

/// <summary>
/// Sweeps a vertical cast to find the ground for the capsule at the given location.
/// Will attempt to perch if ShouldComputePerchResult() returns true for the downward sweep result.
/// No ground will be found if collision is disabled (eg: detectCollisions == false).
/// </summary>

public void FindGround(Vector3 characterPosition, out FindGroundResult outGroundResult)

/// <summary>
/// Checks if any colliders overlaps the character's capsule-shaped volume in world space using testHeight as capsule's height.
/// Returns true if there is a blocking overlap, false otherwise.
/// </summary>

public bool CheckCapsule()

/// <summary>
/// Checks if any colliders overlaps the character's capsule-shaped volume in world space using testHeight as capsule's height.
/// Returns true if there is a blocking overlap, false otherwise.
/// </summary>

public bool CheckHeight(float testHeight)

/// <summary>
/// Casts a ray, from point origin, in direction direction, of length distance, against specified colliders (by layerMask) in the Scene.
/// </summary>

public bool Raycast(Vector3 origin, Vector3 direction, float distance, int layerMask, out RaycastHit hitResult, float thickness = 0.0f)

/// <summary>
/// Check the given capsule against the physics world and return all overlapping colliders.
/// Return overlapped colliders count.
/// </summary>

public int OverlapTest(Vector3 characterPosition, Quaternion characterRotation, float testRadius,
    float testHeight, int layerMask, Collider[] results, QueryTriggerInteraction queryTriggerInteraction)

/// <summary>
/// Check the character's capsule against the physics world and return all overlapping colliders.
/// Return an array of overlapped colliders.
/// </summary>

public Collider[] OverlapTest(int layerMask, QueryTriggerInteraction queryTriggerInteraction,
    out int overlapCount)

/// <summary>
/// Check the given capsule against the physics world and return all overlapping colliders.
/// Return an array of overlapped colliders.
/// </summary>

public Collider[] OverlapTest(Vector3 characterPosition, Quaternion characterRotation, float testRadius,
    float testHeight, int layerMask, QueryTriggerInteraction queryTriggerInteraction, out int overlapCount)

Callbacks

/// <summary>
/// Let you define if the character should collide with given collider.
/// </summary>
/// <param name="collider">The collider.</param>
/// <returns>True to filter (ignore) given collider, false to collide with given collider.</returns>

public delegate bool ColliderFilterCallback(Collider collider);

/// <summary>
/// Let you define the character behaviour when collides with collider.
/// </summary>
/// <param name="collider">The collided collider</param>
/// <returns>The desired collision behaviour flags.</returns>

public delegate CollisionBehaviour CollisionBehaviourCallback(Collider collider);

/// <summary>
/// Let you modify the collision response vs dynamic objects,
/// eg: compute resultant impulse and / or application point (CollisionResult.point).
/// </summary>

public delegate void CollisionResponseCallback(ref CollisionResult inCollisionResult, ref Vector3 characterImpulse, ref Vector3 otherImpulse);

/// <summary>
/// Let you define if the character should collide with given collider.
/// Return true to filter (ignore) collider, false otherwise.
/// </summary>

public ColliderFilterCallback colliderFilterCallback

/// <summary>
/// Let you define the character behaviour when collides with collider.
/// </summary>

public CollisionBehaviourCallback collisionBehaviourCallback

/// <summary>
/// Let you modify the collision response vs dynamic objects,
/// eg: compute resultant impulse and / or application point (CollisionResult.point).
/// </summary>

public CollisionResponseCallback collisionResponseCallback

Events

/// <summary>
/// Event triggered when characters collides with other during a Move.
/// Can be called multiple times.
/// </summary>

public event CollidedEventHandler Collided;

/// <summary>
/// Event triggered when a character finds ground (walkable or non-walkable) as a result of a downcast sweep (eg: FindGround method).
/// </summary>

public event FoundGroundEventHandler FoundGround;

Last updated