Search Unity

First person controller jumping problem

Discussion in 'Scripting' started by varie-tea, Oct 5, 2014.

  1. varie-tea

    varie-tea

    Joined:
    Jul 4, 2012
    Posts:
    24
    Hello,

    For a physics based 3D puzzle game I made a rigidbody based FP player (standard capsule shape). This game is tile based (image 1).



    Originally I used raycasting, but since the game has 90 degree edges on the floor (steps downstairs), the player got stuck on them because the ray was no longer hitting a collider (image 2), so the player was assumed to be in the air, while in fact the player's collider was still standing on the edge.

    As a solution, instead of raycasting I added a trigger-collider at the player's feet to check whether he's on the ground or not (image 3). It works fine but occasionally when I jump, it does not react, this does not return an error. To my knowledge this can not be my fault since it only happens occasionally, if it were an error in my code or anything like that it would happen consistently and return an error.

    1. Player object with component:

    - Player.cs (movement script, see below)

    2. Player's child object (trigger object) with components:
    - PlayerFloorColl.cs (collision detection script, see below)
    - Trigger collider

    PlayerFloorColl.cs sets the boolean variable "coll" in Player.cs to "true" or "false", depending on wether a collider with a proper tag is within the trigger collider at any frame.
    According to "coll" Player.cs turns jumping on/off, enables/disables changing of momentum, etc.

    This is really a big problem since jumping is one of my game's main elements.

    Many thanks!

    Code (CSharp):
    1.  
    2. =============================================================
    3. P L A Y E R . C S :
    4.  
    5. using UnityEngine;
    6. using System.Collections;
    7. using System.Collections.Generic;
    8.  
    9. public class Player : MonoBehaviour {
    10.     public float forwardSpeed = 5.0f;
    11.     public float sideSpeed = 5.0f;
    12.     public float pinkTimes = 2.0f;
    13.     public float jumpForce = 15.0f;
    14.     public float limeJumpForce = 30.0f;
    15.     public float inDrag;
    16.     public bool coll;
    17.     public GameObject other;
    18.     public List<int> spacePress = new List<int>();
    19.     public List<int> timesJumped = new List<int>();
    20.  
    21.     void Start ()
    22.     {
    23.         inDrag = transform.rigidbody.drag;
    24.     }
    25.  
    26.     void Update ()
    27.     {
    28.         if(Input.GetButtonDown ("Restart Level"))
    29.         {
    30.             Application.LoadLevel (Application.loadedLevelName);
    31.         }
    32.     }
    33.  
    34.     /*
    35.     void OnCollisionStay (Collision otherl)
    36.     {
    37.         coll = true;
    38.         other = otherl.gameObject;
    39.         Debug.Log ("Collision");
    40.     }
    41.  
    42.     void OnCollisionExit (Collision otherl)
    43.     {
    44.         coll = false;
    45.     }
    46.     */
    47.  
    48.     void FixedUpdate ()
    49.     {
    50.         //RaycastHit hit;
    51.  
    52.  
    53.         if(coll && Input.GetAxis ("Vertical") != 0)
    54.         {
    55.             if(other != null)
    56.             {
    57.                 if(other.transform.tag == "normalTile" || other.transform.tag == "limeTile") transform.rigidbody.AddForce (transform.forward * forwardSpeed * Input.GetAxis("Vertical"));
    58.             }
    59.         }
    60.  
    61.         if(coll && Input.GetAxis ("Vertical") != 0)
    62.         {
    63.             if(other != null)
    64.             {
    65.                 if(other.transform.tag == "pinkTile") transform.rigidbody.AddForce (transform.forward * forwardSpeed * pinkTimes * Input.GetAxis("Vertical"));
    66.             }
    67.         }
    68.  
    69.         if(coll && Input.GetAxis ("Horizontal") != 0)
    70.         {
    71.             if(other != null)
    72.             {
    73.                 if(other.transform.tag == "normalTile" || other.transform.tag == "limeTile") transform.rigidbody.AddForce (transform.right * sideSpeed * Input.GetAxis ("Horizontal"));
    74.             }
    75.         }
    76.  
    77.         if (coll && Input.GetAxis ("Horizontal") != 0)
    78.         {
    79.             if(other != null)
    80.             {
    81.                 if(other.transform.tag == "pinkTile") transform.rigidbody.AddForce (transform.right * sideSpeed * pinkTimes * Input.GetAxis ("Horizontal"));
    82.             }
    83.         }
    84.  
    85.  
    86.         if(!coll && Input.GetAxis ("Vertical") != 0)
    87.         {
    88.             transform.rigidbody.AddForce (transform.forward * forwardSpeed * 0.1f * Input.GetAxis("Vertical"));
    89.         }
    90.        
    91.         if(!coll && Input.GetAxis ("Horizontal") != 0)
    92.         {
    93.             transform.rigidbody.AddForce (transform.right * sideSpeed * 0.1f * Input.GetAxis ("Horizontal"));
    94.         }
    95.  
    96.  
    97.         if(coll && Input.GetButtonDown ("Jump"))        //was 1.05f
    98.         {
    99.             spacePress.Add(1);
    100.  
    101.             if(other.transform.tag == "normalTile" || other.transform.tag == "pinkTile")
    102.             {
    103.                 transform.rigidbody.AddForce (transform.up * jumpForce);
    104.                 timesJumped.Add(1);
    105.             }else if(other.transform.tag == "limeTile")
    106.             {
    107.                 transform.rigidbody.AddForce (transform.up * limeJumpForce);
    108.                 timesJumped.Add(1);
    109.             }
    110.         }
    111.  
    112.         if(coll)
    113.         {
    114.             if(other != null)
    115.             {
    116.                 if(other.transform.tag == "normalTile" || other.transform.tag == "limeTile" || other.transform.tag == "pinkTile")
    117.                 {
    118.                     if(transform.rigidbody.drag != inDrag)
    119.                     {
    120.                         transform.rigidbody.drag = inDrag;
    121.                     }
    122.                 }else{
    123.                     transform.rigidbody.drag = 0.0f;
    124.                 }
    125.             }
    126.         }else{
    127.             if(other != null)
    128.             {
    129.                 if(transform.rigidbody.drag != 0.0f)
    130.                 {
    131.                     transform.rigidbody.drag = 0.0f;
    132.                 }
    133.             }
    134.         }
    135.     }
    136. }
    137.  
    138. =============================================================
    139.  
    140. P L A Y E R F L O O R C O L L . C S :
    141.  
    142. using UnityEngine;
    143. using System.Collections;
    144.  
    145. public class PlayerFloorColl : MonoBehaviour {
    146.     public Player player;
    147.  
    148.     void OnTriggerExit (Collider otherl)
    149.     {
    150.         if(otherl.transform.tag == ("normalTile") || otherl.transform.tag == ("limeTile") || otherl.transform.tag == ("pinkTile"))
    151.         {
    152.             player.coll = false;
    153.         }
    154.     }
    155.  
    156.     void OnTriggerStay (Collider otherl)
    157.     {
    158.         if(otherl.transform.tag == ("normalTile") || otherl.transform.tag == ("limeTile") || otherl.transform.tag == ("pinkTile"))
    159.         {
    160.             player.coll = true;
    161.             player.other = otherl.gameObject;
    162.         }
    163.     }
    164. }
    165.  
     
  2. Pirs01

    Pirs01

    Joined:
    Sep 30, 2012
    Posts:
    389
    I heaven't read your code but it seams to me you're making this more complex of a problem then it really is. If your player is physics based then why do you need to raycast or use triggers to detect player standing on ground? Can't you just use the default gravity / collider beahviour? Apply force to make player go up and then rely on gravity for player to go down and rely on standard collider behaviour to have playr stop / stand on ground and not falling any further.

    Why do you need to implement the physics yourself?
     
    varie-tea likes this.
  3. varie-tea

    varie-tea

    Joined:
    Jul 4, 2012
    Posts:
    24
    Hi and thanks for your fast answer.
    I am using gravity to make the player fall down.
    I need the trigger to know when the player is able to jump.
    I can't use the collider of the player to know when the player is on the ground, because the walls MUST have the same tags/names as the floor, so the player would be able to jump up walls if I were to do that.
     
  4. Pirs01

    Pirs01

    Joined:
    Sep 30, 2012
    Posts:
    389
    varie-tea likes this.
  5. Pirs01

    Pirs01

    Joined:
    Sep 30, 2012
    Posts:
    389
    Or another idea would bo to tag all your ground colliders as ground as oppose to all other like walls and then use the primary collider and check the tag on collision.

    EDIT: Or instead of tagging you could check on collision if the collider you collided with is generally below you rather then some other direction. You can use the collided collider position and bounds to check if it's top end is below (or the same as) your players bottom end and thus knowing you are colling with something below you rather then next to you like a wall
     
    varie-tea likes this.
  6. cranky

    cranky

    Joined:
    Jun 11, 2014
    Posts:
    180
    You have to raycast to determine the normal of the ground. What you want to do is raycast from the appropriate contact points (you can get them from the OnCollisionStay function). If the contact point's Y coordinate is greater than the Y coordinate of the center bottom sphere of the capsule, then ignore the contact point. Wow, that sounded confusing. Examine this image:


    If the Y coordinate of the coordinate of the contact point is greater than the Y coordinate of the red line in the above image, ignore it. This is to prevent the sides of the capsule from trigger raycasts (and maybe registering as grounded if you are hugging a wall!)

    So after checking the Y coord and after getting a hit from the raycast, you can then get the angle of the normal by either using the built in Unity function, or through my optimized method, which cancels a lot of the zeros out:

    Code (CSharp):
    1.     private float GroundAngle(Vector3 normal)
    2.     {
    3.         return (float)System.Math.Acos((double)normal.y) * Mathf.Rad2Deg;
    4.     }
    Please note: this function assumes normal is normalized beforehand. It will fail if it is not normalized. The normal given by the raycast hit WILL be normalized though, so this is alright :). If the angle is less than or equal to your maximum walkable slope, the player is grounded.

    To recap: iterate through all the contact points testing them for the Y coord check, raycast hit check, and then checking the angle of the ground. If any one contact point passes all three tests, then stop, ignore the other contact points this frame, and say the player is grounded.

    I hope that made sense lol.
     
    varie-tea likes this.
  7. varie-tea

    varie-tea

    Joined:
    Jul 4, 2012
    Posts:
    24
    Hello,

    Thank you for the answers and suggestions. I decided to go with SphereCast and now IT WORKS!

    Nevertheless I am still interested in knowing why the triggers in my original script didn't work all the time. :confused:

    (Sorry for the late answer -- it took a while to implement SphereCast into the entire game.)

    --
    Our games: www.sharadise.com/games.html
     
  8. Cpt Chuckles

    Cpt Chuckles

    Joined:
    Dec 31, 2012
    Posts:
    86
    you don't have to raycast or spherecast to find the normal of the ground, you can do that just with your player's capsule collider. this is the code that i use and it works fine

    Code (csharp):
    1. private bool grounded = false;
    2. public float floorAngleThreshhold = 15.0f; //how steep the floor can be
    3.  
    4. void OnCollisionStay(Collision col)
    5. {
    6.     foreach(ContactPoint hit in col)
    7.     {
    8.         if(Vector3.Angle(Vector3.up, hit.normal) <= floorAngleThreshhold)
    9.         {
    10.             grounded = true;
    11.             return;
    12.         }
    13.     }
    14. }
    15.  
    16. void OnCollisionExit()
    17. {
    18.     grounded = false; //i find this is sufficient
    19. }
     
    HaGGGames likes this.
  9. cranky

    cranky

    Joined:
    Jun 11, 2014
    Posts:
    180
    ContactPoint.normal returns the normal of contact (or some other weird definition), not the normal of the polygon it is intersecting. Set your floor angle threshold to 45 degrees, then make a 45 degree surface and note that walking up it is very inconsistent. Also, print ContactPoint.normal to the console and note that it changes when you move.

    Good to hear! Make sure you rigorously test it! Run into walls while spamming jump and make some weird geometry to see how your controller plays. I found tons of bugs in mine days after thinking it was perfect :).
     
  10. varie-tea

    varie-tea

    Joined:
    Jul 4, 2012
    Posts:
    24
    Hello,

    Thanks for more solutions, but now that I've altered my game to use SphereCast I don't want to spend days changing it to use the capsule collider. It's good to know anyway. :)

    I am testing it very carefully, thanks.

    I still want to know why the triggers didn't work all the time, it's important to know something like that for future projects.