Search Unity

CharacterController.isGrounded - Unreliable or bad code?

Discussion in 'Physics' started by Snick0rz, Dec 12, 2015.

Thread Status:
Not open for further replies.
  1. Snick0rz

    Snick0rz

    Joined:
    Dec 12, 2015
    Posts:
    2
    I've started (semi-)serious development with Unity yesterday and started out with a First-Person Controller Script. For the movement I'm using a Character Controller, which works just fine.

    Once I started coding the gravity interaction however, things got a bit iffy. I started coding the falling part of gravity, which works fine and then started coding the jumping part.

    I used controller.isGrounded to find out if the CharacterController had contact to the ground, therefore enabling it to jump, but for some reason it seems to "oscillate" so to speak. What I mean by that is that sometimes controller.isGrounded returns false, even though it is sitting on the ground. Interestingly enough, Physics.RayCast seems to produce a much more reliable result.

    Can someone explain why that is? Is it actually faulty or is it me expecting the wrong things out of controller.isGrounded? I'd like to know so I can improve and maybe correct expectations towards the usage of controller.isGrounded.

    If you want a more thorough explanation of the code, feel free to message me and/or post in the thread!

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class ControlScript : MonoBehaviour {
    5.  
    6.     public float maxSpeed = 0.15f;
    7.     public float acceleration = 1;
    8.     public float momentum = 0;
    9.     public float horizontalMomentum = 0;
    10.     public float jumpHeight = 0.15f;
    11.     public float verticalMomentum = 0;
    12.     public int rotationSpeed = 36;
    13.     public float hitscanLength = 1.6f; //Length of collision hitscan Ray
    14.     public bool isGrounded; //To display isGrounded status in Inspector
    15.     CharacterController controller;
    16.  
    17.     public Vector3 movementVector;
    18.  
    19.     // Use this for initialization
    20.     void Start () {
    21.  
    22.     controller = GetComponent<CharacterController>();
    23.    
    24.     }
    25.    
    26.     // Update is called once per frame
    27.     void FixedUpdate ()
    28.     {
    29.         isGrounded = controller.isGrounded; //Use this to check controller.isGrounded in Inspector
    30.         //isGrounded = Physics.Raycast(this.transform.position, Vector3.down, hitscanLength, 1); //Use this to check using RayCast
    31.  
    32.         /*-- Depth Movement --*/
    33.         if(Input.GetKey(KeyCode.W))
    34.         {  
    35.             momentum += ((1 * acceleration) * Time.deltaTime);
    36.             momentum = Mathf.Clamp(momentum, 0, maxSpeed);
    37.         }
    38.         else if(Input.GetKey(KeyCode.S))
    39.         {
    40.             momentum -= ((1 * acceleration) * Time.deltaTime);
    41.             momentum = Mathf.Clamp(momentum, -maxSpeed, 0);
    42.         }
    43.         else
    44.         {
    45.             if(momentum < 0)
    46.             {
    47.                 momentum += ((1 * acceleration) * Time.deltaTime);
    48.                 //momentum = Mathf.Clamp(momentum, 0, 0);
    49.                 if(momentum > -0.002f)
    50.                 {
    51.                     momentum = 0;
    52.                 }
    53.             }
    54.             if(momentum > 0)
    55.             {
    56.                 momentum -= ((1 * acceleration) * Time.deltaTime);
    57.                 //momentum = Mathf.Clamp(momentum, 0, 0);
    58.                 if(momentum < 0.002f)
    59.                 {
    60.                     momentum = 0;
    61.                 }
    62.             }
    63.         }
    64.  
    65.         /*-- Horizontal Movement -- */
    66.         if(Input.GetKey(KeyCode.A))
    67.         {  
    68.             horizontalMomentum -= ((1 * acceleration) * Time.deltaTime);
    69.             horizontalMomentum = Mathf.Clamp(horizontalMomentum, -maxSpeed, 0);
    70.         }
    71.         else if(Input.GetKey(KeyCode.D))
    72.         {
    73.             horizontalMomentum += ((1 * acceleration) * Time.deltaTime);
    74.             horizontalMomentum = Mathf.Clamp(horizontalMomentum, 0, maxSpeed);
    75.         }
    76.         else
    77.         {
    78.             if(horizontalMomentum < 0)
    79.             {
    80.                 horizontalMomentum += ((1 * acceleration) * Time.deltaTime);
    81.                 if(horizontalMomentum > -0.002f)
    82.                 {
    83.                     horizontalMomentum = 0;
    84.                 }
    85.             }
    86.             if(horizontalMomentum > 0)
    87.             {
    88.                 horizontalMomentum -= ((1 * acceleration) * Time.deltaTime);
    89.                 if(horizontalMomentum < 0.002f)
    90.                 {
    91.                     horizontalMomentum = 0;
    92.                 }
    93.             }
    94.         }
    95.  
    96.         /*-- Gravity and Jumping --*/
    97.         if(isGrounded) //If the character controller is on the ground...
    98.         {
    99.             verticalMomentum = 0; //... kill the vertical Momentum.
    100.             if(Input.GetKeyDown(KeyCode.Space))  //If the user presses space...
    101.             {
    102.                 verticalMomentum = .45f;  //Apply verticalMomentum...
    103.             }
    104.         }
    105.         else  //If in freefall...
    106.         {
    107.             verticalMomentum += ((Physics.gravity.y * Time.deltaTime) / 10); //... Apply gravity acceleration...
    108.             verticalMomentum = Mathf.Clamp(verticalMomentum, -.85f, 255); //... but clamp it at a certain speed.
    109.         }
    110.  
    111.         //Debug.DrawLine(this.transform.position, new Vector3(this.transform.position.x, this.transform.position.y - hitscanLength, this.transform.position.z), Color.red, 0, false);
    112.  
    113.         movementVector = new Vector3(horizontalMomentum, verticalMomentum, momentum);
    114.         controller.Move(movementVector);
    115.     }
    116. }
     
  2. Lancemaker_

    Lancemaker_

    Joined:
    Nov 14, 2011
    Posts:
    34
  3. Lancemaker_

    Lancemaker_

    Joined:
    Nov 14, 2011
    Posts:
    34
    Im getting the same behaviour with the collisionflags. mabe its the same method.
     
  4. Snick0rz

    Snick0rz

    Joined:
    Dec 12, 2015
    Posts:
    2
    I've actually forgotten about this thread! It was actually bad code.
    If you set your y (assuming downward gravity) velocity to zero, Unity, so to speak, "forgets" that it's touching the ground. What you want to do is to set your Y velocity to the either your gravity times DeltaTime/FixedDeltaTime, to the controller's skinWidth times DT/FDT. This thread and this post in particular were very helpful.
     
  5. PharmacyBrain

    PharmacyBrain

    Joined:
    Sep 8, 2020
    Posts:
    6
    This is a top Google search result so I am going to add a bit more.

    The above statement proved true for me: "If you set your y (assuming downward gravity) velocity to zero, Unity, so to speak, "forgets" that it's touching the ground."

    The problem is that I could not avoid setting the y velocity to zero, so I needed another solution.

    For me, this was causing issues when the player slowly approached a cliff/edge. As the "shoulder" of the capsule collider slowly goes down the edge/cliff the Character Controller is saying it is losing the grounded state for brief periods of time. This ended up firing falling animation trigger events which was awful.

    I serialized a field and started counting how long the player was in the "not grounded" state.

    Code (CSharp):
    1. private void ProcessGravity()
    2.     {
    3.         playerVelocity.y += gravityValue * Time.deltaTime;
    4.         controller.Move( playerVelocity * Time.deltaTime );
    5.  
    6.         playerVelY = playerVelocity.y;
    7.     }
    8.  
    9.     private void UpdateGroundedState()
    10.     {
    11.         // the below is used to prevent a fall event from triggering when
    12.         // the player falls very tiny distances; this happens a lot when
    13.         // the player slowly approaches a ledge
    14.  
    15.         // transitioning from grounded to not grounded
    16.         if ( groundedPlayer && controller.isGrounded == false )
    17.         {
    18.             notGroundedTime += Time.deltaTime;
    19.         }
    20.         // previously grounded and still grounded
    21.         else if ( !groundedPlayer && controller.isGrounded == false )
    22.         {
    23.             notGroundedTime += Time.deltaTime;
    24.         }
    25.         // transitioning from not grounded to grounded
    26.         else if ( !groundedPlayer && controller.isGrounded == true )
    27.         {
    28.             notGroundedTime = 0.0f;
    29.         }
    30.  
    31.         groundedPlayer = controller.isGrounded;
    32.         if ( groundedPlayer && playerVelocity.y < 0 )
    33.         {
    34.             playerVelocity.y = 0.0f;
    35.         }
    36.     }
    This shows how I am applying gravity and handling the grounded state. This is the Unity Character Controller example code modified quite a bit.

    So basically what I do is count in seconds how long the playing is falling for and only fire a fall animation trigger/event if the player has been falling for a minimum amount of time. This fixed my issue.


    Code (CSharp):
    1.     private void ProcessFalling()
    2.     {
    3.         if ( !groundedPlayer && notGroundedTime >= minFallTime )
    4.         {
    5.             heightBeforeDrop = this.transform.position.y;
    6.             characterAnimator.SetTrigger( "fallStart" );
    7.             playerMotionState = MotionState.fallStart;
    8.         }
    9.     }
     
  6. Radivarig

    Radivarig

    Joined:
    May 15, 2013
    Posts:
    121
    Try setting minMoveDistance to 0... Docs say "In most situations this value should be left at 0" but they still made the default value 0.001 which also breaks isGrounded... Wasted so much time on this.
     
    Nido likes this.
  7. zaizr

    zaizr

    Joined:
    Mar 9, 2021
    Posts:
    1
    Still facing the same issue :(
     
Thread Status:
Not open for further replies.