Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Biggest show stopper I have experienced

Discussion in 'Physics' started by seandolan, Dec 18, 2017.

  1. seandolan

    seandolan

    Joined:
    Jun 5, 2015
    Posts:
    9
    I have really struggled with the bouncing down slopes and character grounding problems. It seems like there are a lot of partial solutions out there and tons of code to dig through, but no real definitive solution. Every time I have made a 3d game with a character I want to jump around freely off the scene elements - I always reach the point where my character doesn't handle slopes correctly.

    I have about 20 to 30 projects that got about 4 hours into development and I simply lost motivation to keep working on them when I hit the slopes problem. Tried CharacterControllers and Rigidbody solutions but I either end up with a character that can't tell when it's properly grounded or bounces down a slope that really it should be able to handle.

    One of the biggest solutions is to supercharge the gravity. But it really feels like broken physics is the problem. I have also tried raycast solutions but even with 20+ raycasts and over-correcting the character physics I still can't accomplish what I would expect from the physics calculations. It even seems to be different depending on what direction the character is facing when all coding is independent of facing direction.

    Is there any perfect solution out there? Am I doing things fundamentally wrong and others just don't have to deal with the slope bounce/grounding problems? Should I finally go learn another coding language not c#/js just to get into a different engine that doesn't experience these problems?

    Any insight or suggestions would be greatly appreciated. I would post some code here too but I think if anyone understands what I am experiencing, they would also recognise the code I am trying from other sources available online. If I haven't explained the state of things correctly, please give me the opportunity to correct myself before slamming me. I have a little trouble in the outside world communicating with others so please be kind. I love Unity, I just feel I might be stuck treading water on this problem forever.

    Thanks for your time,

    Sean
     
  2. Iron-Warrior

    Iron-Warrior

    Joined:
    Nov 3, 2009
    Posts:
    838
    I wrote a post about this on my blog a few years back. My writing was a bit meandering back then. You can mostly sum up the issue with this image.



    The solution is to do two things (either one or the other or both). Instead of making your character always run "forward" parallel to the ground, they instead move in the direction of the ground beneath them. That way, they tend to stay more pinned to the ground. This mirrors the real world a bit too: when we run down slopes, we angle our body accordingly.

    The second solution is to always "clamp" your character to the ground at the end of the frame. That way, you can never just bounce around over rough terrain. This solution brings with it an obvious problem: how does your character fall off ledges or jump if they're always clamped to the ground? In general this is handled by disabling clamping when the character initiates a jump action, or when the game detects the ground beneath them is too far away to reasonably clamp to.

    The above techniques are used in Super Mario 64, but pretty much all games use something to this effect.
     
    PhilSA and seandolan like this.
  3. seandolan

    seandolan

    Joined:
    Jun 5, 2015
    Posts:
    9
    That seems so straight forward. Thank you so much. It's an extremely weird coincidence that the one application I was thinking about how they did it was Mario64. Just crazy. There is a lot of different people asking the same question I had in many different forums and even different youtube videos on how to address it that seem more complicated. I will try this as soon as I can (maybe at work if the boss isn't looking). Would a raycast down from the character be fast enough to detect the ground underneath? I have found raycasts tend not to return a result instantly is all.
     
  4. seandolan

    seandolan

    Joined:
    Jun 5, 2015
    Posts:
    9
    Was able to get the normal of the plane underneath player with a raycast and I know how to move my player on it's current x,y,z but I am unsure of how to take the movement Vector3(1,0,1) and combine it with hit.normal to make it move in the direction of the plane.

    upload_2017-12-19_23-20-38.png
     
  5. Fu11English

    Fu11English

    Joined:
    Feb 27, 2012
    Posts:
    258
    Try Vector3.Cross of your transform.right and rayhit.normal.

    You may need to switch the two round if the new vector is the reverse of what you want.
     
    seandolan likes this.
  6. seandolan

    seandolan

    Joined:
    Jun 5, 2015
    Posts:
    9
    Transform.right seems like it would be for a 2d setup.. or am I just assuming that incorrectly? Will try it out and see what happens. Thanks for your help.
     
  7. seandolan

    seandolan

    Joined:
    Jun 5, 2015
    Posts:
    9
    I gave it a go, the code worked out but my directions (sourced from Input.GetAxis of my keyboard) got all turned around.

    [original] = [what it changed to]
    Strafe Left = Backwards
    Strafe Right = Forwards
    Forwards = Strafe Left
    Backwards = Strafe Right

    I got it to work by swapping the Strafe and Vertical axis and making the Strafe a negative of the input. I would rather not do this, can anyone help with fixing the code part?

    Code (CSharp):
    1.     Rigidbody rb;
    2.     public LayerMask castMask;
    3.  
    4.     public Vector3 groundSlope = Vector3.zero;
    5.  
    6.     public float speedWalk = 5.0f;
    7.     public float speedTurn = 115.0f;
    8.  
    9.     void Start () {
    10.         rb = GetComponent<Rigidbody> ();
    11.     }
    12.  
    13.     void Update () {
    14.      
    15.     }
    16.  
    17.     void FixedUpdate () {
    18.         RaycastHit hit;
    19.         if (Physics.Raycast (transform.position, -transform.up, out hit, 1.2f, castMask)) {
    20.             Debug.DrawRay (hit.point, hit.normal * 2.0f, Color.red);
    21.             groundSlope = hit.normal;
    22.         } else {
    23.             Debug.DrawRay (transform.position, -transform.up * 2.0f, Color.yellow);
    24.         }
    25.     }
    26.  
    27.     void LateUpdate () {
    28.         Vector3 moveTo = Vector3.zero;
    29.         transform.eulerAngles += new Vector3(0,Input.GetAxis ("Horizontal") * speedTurn * Time.deltaTime,0);
    30.  
    31.         // ORIGINAL CODE
    32.         // moveTo = transform.TransformDirection(new Vector3 (Input.GetAxis ("Strafe"), 0, Input.GetAxis ("Vertical")).normalized  * speedWalk * Time.deltaTime);
    33.         //
    34.  
    35.         // ADJUSTED TO CORRECT FOR MOVEMENT PROBLEMS
    36.         moveTo = transform.TransformDirection(new Vector3 (Input.GetAxis ("Vertical"), 0, -Input.GetAxis ("Strafe")).normalized  * speedWalk * Time.deltaTime);
    37.         //
    38.  
    39.         rb.MovePosition (transform.position + Vector3.Cross(moveTo,groundSlope));
    40.     }
     
  8. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    I find that Vector3.ProjectOnPlane (as used in the standard assets 3rd person) is great.
    For a while , when I started, I ignored it and went another direction. Later, I found myself returning to it and I like it very much :)

    I never modify eulerangles directly. I either modify the rotation directly, or update a float variable that I then use to apply a Quaternion rotation via Quaternion.Euler

    Beyond that, I'm not sure about your input issue.

    One last thing, purely out of curiousity.. Does your character have a non-kinematic rigidbody? And if so, how is that MovePosition working for you? Good?
     
    seandolan likes this.
  9. seandolan

    seandolan

    Joined:
    Jun 5, 2015
    Posts:
    9
    I will have to research what Vector3.ProjectOnPlane does if you feel it will be helpful.

    The rotation doesn't seem to be causing an issue as even at different rotations I get the exact same unexpected directions from the keys. The ugly solution I have there works fine no matter the direction.

    I am using only that code for now, nothing else. Non-kinematic rigidbody but with the all 3 rotation constraints in place. It is working amazingly for very little code so far.
     
  10. Iron-Warrior

    Iron-Warrior

    Joined:
    Nov 3, 2009
    Posts:
    838
    Hey, sorry for not replying earlier, here's a quick sample script clarifying some of the stuff I explained above.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3.  
    4. // To use: apply this to an object with a CharacterController component attached
    5. // Was tested with a controller with height 2f, radius 0.5f
    6.  
    7. public class PlayerMover : MonoBehaviour
    8. {
    9.     [SerializeField]
    10.     WalkType walkType;
    11.  
    12.     [SerializeField]
    13.     float walkSpeed = 5.0f;
    14.  
    15.     enum WalkType { Clamped, Directional }
    16.  
    17.     private CharacterController controller;
    18.  
    19.     void Start()
    20.     {
    21.         controller = GetComponent<CharacterController>();
    22.     }
    23.  
    24.     void Update ()
    25.     {
    26.         Vector3 moveDirection = new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical")).normalized;
    27.  
    28.         if (walkType == WalkType.Clamped)
    29.         {
    30.             controller.Move(moveDirection * walkSpeed * Time.deltaTime);
    31.  
    32.             Ray ray = new Ray(transform.position, Vector3.down);
    33.  
    34.             RaycastHit hit;
    35.             if (Physics.SphereCast(ray, controller.radius, out hit, Mathf.Infinity))
    36.             {
    37.                 float distance = hit.distance;
    38.  
    39.                 // Modify the distance to ensure it evaluates to the distance
    40.                 // from the bottom of the controller to the ground
    41.                 distance += controller.radius;
    42.  
    43.                 distance -= controller.height * 0.5f;
    44.                 distance -= controller.skinWidth;
    45.  
    46.                 // Offset the controller to pin it to the ground
    47.                 transform.position += distance * Vector3.down;
    48.  
    49.                 Debug.Log(distance);
    50.                 Debug.DrawRay(hit.point, hit.normal * 3, Color.blue);
    51.             }
    52.         }
    53.         else if (walkType == WalkType.Directional)
    54.         {
    55.             Ray ray = new Ray(transform.position, Vector3.down);
    56.  
    57.             RaycastHit hit;
    58.             if (Physics.SphereCast(ray, controller.radius, out hit, Mathf.Infinity))
    59.             {
    60.                 Vector3 right = Vector3.Cross(Vector3.up, moveDirection);
    61.  
    62.                 // Get the normal vector of the slope aligned on the axis rightwards that we are moving
    63.                 // This is important to prevent the character from walking the wrong direction on oddly rotated
    64.                 // surface
    65.                 Vector3 projectedNormal = Vector3.ProjectOnPlane(hit.normal, right);
    66.  
    67.                 // Always normalize after projection to prevent incorrect movement magnitudes
    68.                 moveDirection = Vector3.ProjectOnPlane(moveDirection, projectedNormal).normalized;
    69.  
    70.                 Debug.DrawRay(transform.position, moveDirection * 3, Color.blue);
    71.             }
    72.  
    73.             controller.Move(moveDirection * walkSpeed * Time.deltaTime);
    74.         }
    75.     }
    76. }
    77.  
    It has two different modes to solve the problem: directly clamping the controller, or just having it move aligned to the surface below it. Note that I use SphereCast instead of Raycast, since from a top view capsules are just spheres! In addition, I use the CharacterController component instead of rigidbody, since rigidbody character controllers can be impractical.

    https://gfycat.com/InfamousConcreteHerald
     
    seandolan likes this.
  11. seandolan

    seandolan

    Joined:
    Jun 5, 2015
    Posts:
    9
    Thanks for the input. Also Iron-Warrior, you were extremely quick in getting back to reply on this thread. I really wanted to use the rigidbody as I find the concepts of it seem to work better when trying to problem solve. The way my brain processes the problems works well with how rigidbody's are setup - sorry that's hard to explain correctly.

    I can get your code to work with the rigidbody. I still don't really understand how Vector3.Cross and Vector3.ProjectOnPlane work - but there are enough resources out there for me to read and work it all out without asking you or others to explain it to me. The help everyone has given has allowed me to keep working with Unity. It's amazing how such a small sample of code can seem to stop you in your tracks on development projects. It has been a great lesson for me.

    One thing I did find is that the sphere cast allowed my character to move up very high angled planes. But for what I am using this stuff for, I can easily get away with the single point raycast and it will all still work.

    Thank you everyone for your help and Iron-Warrior for letting me know about your concepts. You have no idea the amazing gift you have given me with this help.
     
    Iron-Warrior likes this.
  12. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    Hey there. For ProjectOnPlane, using a raycast to the ground below you can get your the plane.normal and you pass your move direction and the normal direction and the method returns to you the direction you're going as if you were on the plane's surface. :)
     
    seandolan likes this.
  13. seandolan

    seandolan

    Joined:
    Jun 5, 2015
    Posts:
    9
    That is a great way of explaining it. I understand that perfectly. I see now why it's so useful - that has a lot of applications. Thank you so much. So is Vector3.Cross just like a multiplication?

    As in, is the following correct? I know they aren't coded correctly with the whole "new Vector3" part, just wondering about the concept.

    Vector3(1,1,2) * Vector3(1,1,3) = Vector3(1,1,6)

    is the same as

    Vector3.Cross(Vector3(1,1,2),Vector3(1,1,3))

    and also equals Vector3(1,1,6)
     
  14. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    seandolan likes this.