Search Unity

[SOLVED] Rigidbody character accelerates against slopes.

Discussion in 'Physics' started by lilrobot, Aug 28, 2019.

  1. lilrobot


    Feb 11, 2018

    I'm giving a whack at making my own Third Person Character script using the provided one in the SA as a reference -- however I've taken an alternate route and have instead built my character around using velocity (I preferred doing this over restricting my movement to Root Motion animations, as it feels kind of clunky to me.)

    I've gotten some pretty promising results with a well-rounded, controllable character that I'm satisfied with!

    However, I've run into a rather frustrating issue where my character will accelerate uncontrollably against slopes. The problem is most noticeable when airborne, and will sometimes show itself if I'm simply walking up a slope and the character ends up "flying" through the air a short bit after the ramp turns flat.

    Note that the airborne issues only occur if I'm continuing to provide input (the move vector) against the slope. The Collider is a Capsule Collider and uses a Physics Material of ZeroFriction.

    Here are some GIFs of the problem:

    - Accelerating uncontrollably against a high-sloped wall while airborne:

    - Accelerating traversing up a ramp, and then remaining in the air for a short amount of time:

    Here's the code I use for handling movement in general, with a condition for being grounded and not grounded, which is updated every frame. In the airborne sample, the player is not considered grounded while flying up the orange slope.

    Code (CSharp):
    1.         private void HandleMovement( Vector3 move, float speed )
    2.         {
    3.             if(!m_IsGrounded)
    4.             {
    5.                 //multiply our movement by our desired AirControl value
    6.                 move *= m_AirControl;
    8.                 //grab the heading direction for our velocity in thea ir
    9.                 Quaternion headingDir = Quaternion.LookRotation( new Vector3(m_StartJumpVelocity.x, 0, m_StartJumpVelocity.z), Vector3.up );
    10.                 //grab the desired direction for our controller
    11.                 Quaternion desiredDir = Quaternion.LookRotation( move, Vector3.up );
    13.                 //the difference between our heading direction and angle direction
    14.                 float angleDiff = Quaternion.Angle( headingDir, desiredDir );
    16.                 if(angleDiff > m_MaxAirInfluenceAngle) //if our angle is greater than our max turn angle for air influence, then make the turn slow and difficult
    17.                     move *= 0.5f;
    18.                 else if(angleDiff < 45) //if we're moving forward at our existing velocity, prevent the player from accelerating in the air
    19.                     move = new Vector3(0, 0, 0);
    20.                 move = new Vector3( move.x + m_StartJumpVelocity.x, 0, move.z + m_StartJumpVelocity.z ); //combine our final move influence by our jump velocity
    21.             }
    22.             else
    23.                 move *= (speed * m_MoveSpeed); //multiply our move direction by the speed modifier (e.g. sprinting) and then multiply it by our actual move speed.
    25.             m_PlayerPhys.velocity = new Vector3( move.x, m_PlayerPhys.velocity.y, move.z ); //set the player physics velocity to our new desired velocity
    26.         }
    I've never been top of the class in physics so this issue is boggling me. I'm assuming it's because I'm continuing to apply force due to movement without taking the slope into account, but how do I go about detecting the slope and understanding how much I should "nerf" the incoming movement?
  2. JanOtt


    Apr 23, 2019
    I've got an idea but first: How do you determine whether your character is grounded (or not)?
  3. lilrobot


    Feb 11, 2018
    Here's the code I used for detecting the ground:

    Code (CSharp):
    2. private void UpdateGroundCheck()
    3. {
    4.     m_GroundedLastFrame = m_IsGrounded;
    5.     RaycastHit HitInfo;
    7.     if( Physics.SphereCast( transform.position + (Vector3.up * m_GroundCheckDist), m_Capsule.radius * m_GroundCheckDist, Vector3.down, out HitInfo, m_GroundCheckDist, 1 << 10 ) )
    8.     {
    9.         Vector3 hitNormal = HitInfo.normal;
    11.         m_GroundContactNormal = hitNormal;
    13.         m_StartJumpVelocity = new Vector3(0, 0, 0);
    14.         m_IsGrounded = true;
    15.     }
    16.     else
    17.     {
    18.         if( m_StartJumpVelocity == new Vector3(0, 0, 0) )
    19.         m_StartJumpVelocity = m_PlayerPhys.velocity;
    20.         m_GroundContactNormal = Vector3.up;
    21.         m_IsGrounded = false;
    22.     }
    23. }
  4. JanOtt


    Apr 23, 2019
    Okay, so if I understood your code correctly, you're only really directly controlling the horizontal movement (forward/backward, right/left) of the character, and you let the physics engine deal with the vertical movement (up/down)? I'm assuming the 'use gravity' setting of your character's rigidbody is enabled?

    My theory is that by continuously moving the character into the wall/slope, the physics engine pushes the character upwards to resolve the collision. As a result, upwards velocity is added to the rigidbody, which results in the unwanted acceleration.

    I'd recommend directly controlling the vertical velocity of the character! This can be as simple as:

    Code (CSharp):
    1. HandleGravity()
    2. {
    3.     float currentVerticalSpeed = rigidbody.velocity.y;
    5.     if(isGrounded)
    6.     {
    7.         if(currentVerticalSpeed < 0f)
    8.             currentVerticalSpeed = 0f;
    9.     }
    10.     else if(!isGrounded)
    11.     {
    12.         currentVerticalSpeed -= gravity * Time.deltaTime;
    13.     }
    15.     rigidbody.velocity.y = currentVerticalSpeed;
    16. }
    And you'll need to disable the 'use gravity' setting as well, of course.
    It's just pseudocode (kind of), so you'll need to make some adjustments, but that's essentially what I'm using for my own character controllers (including the one I'm selling on the Asset Store).

    Hope that helps!
  5. lilrobot


    Feb 11, 2018
    This certainly put me in the right direction, thanks! I added gravity handling to the movement action and it helped a bit with some glitchy physics, but I was still able to dart up slopes, albeit there was a noticeable difference in speed.

    To help the slopes, I actually added a SlopeMultiplier math function that will take the movement vector as an argument. It will then raycast check if I'm on a slope, take the forward normal of that raycast, and get the dot product of the forward normal and the default up vector. I will then multiply the final speed by this multiplier (e.x. the slope in the first gif above has an product of 0.2f).

    This function is executed on the move vector just before setting the player's velocity.

    Code (CSharp):
    2.         private float SlopeMultiplier( Vector3 move )
    3.         {
    4.             var slideEmission = p_slideGustFx.emission;
    6.             RaycastHit HitInfo, HitInfo2;
    7.             Vector3 m_ForwardNormal = new Vector3(0, 0, 0);
    8.             float SlopeRadius = m_GroundCheckDist;
    10.   if( Physics.SphereCast( transform.position + (Vector3.up * SlopeRadius), (m_Capsule.radius * 1.0f), Vector3.down, out HitInfo, SlopeRadius, 1 << 10 )
    11.             && !Physics.Raycast( transform.position + (Vector3.up * SlopeRadius), Vector3.down, out HitInfo2, m_GroundCheckDist, 1 << 10 ) )
    12.             {
    13.                 //if the angle of our collision is too different from the direction we're trying to head, ignore
    14.                 //the dampening of the velocity so we don't get stuck on ledges.                float angleDiff = Vector3.Angle( new Vector3(m_PlayerPhys.velocity.x, 0, m_PlayerPhys.velocity.z) + move, HitInfo.point - transform.position );
    15.                 if(angleDiff > 90f) //the ledge is behind me. ignore it.
    16.                     return 1;
    18.                 m_ForwardNormal = HitInfo.normal;
    19.                 float angle = Vector3.Dot( m_ForwardNormal, Vector3.up );
    21.                 if(angle < 0.5f) //this upper limit is the slope limit for my character. i don't want them to pass a slope that reaches this limit. enable our "slide" emission, and our move function will take the resulting angle of <0.5f and set the velocity to the magnitude of our movement so we can still "slide" against the wall.
    22.                 {
    23.                     slideEmission.enabled = true;
    24.                 }
    26.                 return angle;
    27.             }
    29.             slideEmission.enabled = false;
    31.             return 1; //we're not on a slope so just return one
    32.         }
    I take the resulting slope different and multiply my movement vector by it before setting the Rigidbody's velocity to the resulting vector. I also AddForce for gravity just before changing the velocity, and always set the vertical velocity to the existing velocity.