Search code examples
c#unity-game-enginegame-physics

Character Slope Behaviour


I am developing a game which focusses mainly on outdoor movement, therefore I want the character controls to feel as good as possible.

The issue I am currently working on is slope behaviour: The character should not slide down when standing on a slope that is not very steep, and slide down on slopes that are too steep.

I implemented this by activating and deactivating rigidbody constraints depending on the current angle of the ground under the player.

private const RigidbodyConstraints DefaultConstraints = RigidbodyConstraints.FreezeRotation;
private const RigidbodyConstraints StayOnSlope = RigidbodyConstraints.FreezePositionX | RigidbodyConstraints.FreezePositionZ | DefaultConstraints;
private const RigidbodyConstraints SlideDownSlope = DefaultConstraints;

The angle of the ground is calculated in a seperate method, returning the angle between the up vector and the ground normal in degrees.

private float GetGroundAngle()
{
    RaycastHit hit;
    if (Physics.Raycast(transform.position, Vector3.down, out hit, 0.5f))
    {
        return Vector3.Angle(Vector3.up, hit.normal);
    }
    return 0;
}

The actual activation and deactivation of the constraints is implemented inside the FixedUpdate method. Additionally, the player movement is being slowed down the steeper the slope is.

private void FixedUpdate()
{
    const float MAX_SLOPE_ANGLE = 45;

    // If the player is grounded, check the ground angle and prevent slope sliding
    float angle = GetGroundAngle();
    // Apply the constraints
    m_rigidbody.constraints = (m_movementVector.magnitude < Vector3.kEpsilon) && angle <= MAX_SLOPE_ANGLE ? StayOnSlope : SlideDownSlope;

    // Calculate the movement coefficient to ensure the player cannot run up slopes
    float slopeCoefficient = Mathf.Cos(angle * Mathf.Deg2Rad);
    // Calculate and apply the movement vector
    Vector3 movement = m_movementVector * slopeCoefficient * Time.fixedDeltaTime;
    m_rigidbody.MovePosition(m_rigidbody.position + movement);

    // ...
}

The issues with this functionality are the following:

  • The player stops correctly when standing still on slopes, but when moving towards a slope that is way too steep, the player slides down, but will continue to slide down until either the movement vector is changed or no input is given at all.
    The expected result is that the player will stop sliding down as soon as he is on a slope that is flat enough to walk, which is currently not the case.
  • Activating and deactivating constraints seems a bit ... too hacky to be used inside the controller.

Is there a better way to make slope movement behaviour?


Solution

  • The character should not slide down when standing on a slope that is not very steep, and slide down on slopes that are too steep.

    I believe the CharacterController component may be of use for you.

    Notice that it has an adjustable Slope Limit variable.

    https://docs.unity3d.com/Manual/class-CharacterController.html

    There is a First Person controller implementing this in the Unity Standard Assets which can be found inside Unity under Assets -> Import Package -> Characters.