Search Unity

Getting Player GameObject to rotate to the slope of the ground it is walking on?

Discussion in 'Scripting' started by egonspengler_84, Apr 13, 2021.

  1. egonspengler_84

    egonspengler_84

    Joined:
    Jan 26, 2021
    Posts:
    153
    Hi there,

    I've created a simple 2D platformer project at the moment whereby I can control the Player horizontally. I'm just trying to figure out how to get the player to rotate to the slope of the ground similar to how Sonic The Hedgehog rotates as he runs up a hill or around a loop?

    From reading online I know that getting the player to rotate with the slope of the ground has something to do with the slope's "normal"? I understand that it is possible to figure out what the slope of the ground is by calculating the angle between "global up" and the slope's "normal". But how do I use this information to make the player rotate with the slope of the ground?

    On top of this I know that it is possible to use the "Quaternion.LookRotation" function in order to get the Player to rotate with the slope of the ground but again I don't really know how to go about doing this...

    Could someone give me a clue as to what to do?

    Kind regards
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,726
    You need to sample some number of points (perhaps with raycasting) and decide what the slope is.

    Easiest is to use the .normal that comes back from a RaycastHit / RaycastHit2D object.

    Next easiest is to take 2 (or more?) points and subtract their contact positions, or else average their normals.

    From any of the above potential sources of rise-over-run (Y/X) slope data (doesn't matter where it comes from), you can use
    Mathf.Atan2()
    to find the angle (in radians) and multiply it by
    Mathf.Rad2Deg
    to get it in degrees so you can use it to rotate the sprite.

    To smooth it out you can apply any type of filtering or movement-over-time you like, as suits the particular speed or snappiness you're looking for.

    Remember you will need to parent the sprite in such a way that the pivoting point is between the two sampled points on the ground, otherwise the rotation won't match.
     
    egonspengler_84 likes this.
  3. egonspengler_84

    egonspengler_84

    Joined:
    Jan 26, 2021
    Posts:
    153
    Cheers for the helpful reply.

    I know exactly how to shoot a Raycast from the bottom right corner of the Player's Bounding Box. I also know how to get the Normal of the point where this Raycast collides with the ground.

    When you say that you can use "Mathf.Atan2()" to find the angle.." are you talking about the angle of the slope? If so, is using Mathf.Atan2() in the way you have described the same as using Vector2.Angle(hit.normal, Vector2.up)?
     
  4. egonspengler_84

    egonspengler_84

    Joined:
    Jan 26, 2021
    Posts:
    153

    Okay so I know how to get the angle of the slope as you described through getting the slope's normal but I don't understand how I can use the angle to rotate the actual player game object as it is moving horizontally up a slope?
     
  5. egonspengler_84

    egonspengler_84

    Joined:
    Jan 26, 2021
    Posts:
    153
    Okay so I've written the following code in an attempt to get the Player GameObject to rotate with the angle of the slope it is walking on here:

    Code (CSharp):
    1.       if(hit){
    2.  
    3.        Debug.Log("The angle between the Normal and Global Up is: " + Vector2.Angle(Vector2.up,hit.normal));
    4.        transform.Rotate(0, 0, (Vector2.Angle(Vector2.up,hit.normal)));
    5.      
    6.       }
    However this doesn't seem to work - I've uploaded a video on Youtube of my project where you can see that I'm trying to get the Player to travel up a slope but it does not rotate properly with the slope's angle:


    Could you give me any advice on how to fix this?
     
  6. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,726
    Transform.Rotate() is relative. It says "from whatever your rotation is now, rotate this much"

    You want to just drive the rotation explicitly to whatever you compute:

    Code (csharp):
    1. transform.rotation = Quaternion.Euler( 0, 0, computedAngle);
     
    egonspengler_84 likes this.
  7. egonspengler_84

    egonspengler_84

    Joined:
    Jan 26, 2021
    Posts:
    153

    Okay I've changed the code as you suggested but now I'm getting this weird buggy issue when the Player moves up the slope. It's like it's changing it's angle with the slope for a split second but then it reverts back to its original position. I'm not sure why this is? Here's a video of what I see:


    This is the code that I've written here:


    Code (CSharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. public class Player : MonoBehaviour
    7. {
    8.  
    9.      public Vector2 _botRight;
    10.      public LayerMask _whatIsGround;
    11.     private float _jumpForce = 7;
    12.     private float _speed = 10f;
    13.     private BoxCollider2D _col2D;
    14.     private Rigidbody2D _rb;
    15.     private float _moveInput;
    16.  
    17.     // Start is called before the first frame update
    18.     void Start()
    19.     {
    20.      
    21.         _col2D = GetComponent<BoxCollider2D>();
    22.      
    23.         _rb = GetComponent<Rigidbody2D>();
    24.     }
    25.  
    26.     // Update is called once per frame
    27.     void Update()
    28.     {
    29.  
    30.       _botRight = new Vector2(_col2D.bounds.max.x,_col2D.bounds.min.y);
    31.      
    32.  
    33.        _moveInput = Input.GetAxisRaw("Horizontal");
    34.  
    35.        _rb.velocity = new Vector2(_moveInput * _speed, _rb.velocity.y);
    36.  
    37.    
    38.       if(Input.GetKeyDown(KeyCode.Space)){
    39.           _rb.velocity = new Vector2(_rb.velocity.x,_jumpForce);
    40.       }
    41.  
    42.  
    43.  
    44.      RaycastHit2D hit = Physics2D.Raycast(_botRight,Vector2.down,1f,_whatIsGround);
    45.       Debug.DrawRay(_botRight,Vector2.down, Color.red);
    46.    
    47.       Debug.DrawRay(_botRight,Vector2.up * 2f, Color.blue);
    48.       Debug.DrawRay(_botRight,hit.normal, Color.green);
    49.  
    50.       if(hit){
    51.  
    52.        Debug.Log("The angle between the Normal and Global Up is: " + Vector2.Angle(Vector2.up,hit.normal));
    53.       transform.rotation = Quaternion.Euler( 0, 0, (Vector2.Angle(Vector2.up,hit.normal)));
    54.      
    55.       }
    56.  
    57.  
    58.  
    59.  
    60.  
    61.  
    62.  
    63.    
    64.  
    65.     }
    66. }
    67.  
    68.  
    I've frozen the Z axis rotation in the constraints box of the Rigidbody2D component of the player so that it wont fall over but I don't think this has anything to do with the bugginess?
     
  8. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,726
    Didn't realize you were using physics... always use the .MovePosition() and .MoveRotation() methods on the Rigidbody (or Rigidbody2D), when using physics, otherwise you risk startling the physics engine.

    Alternately don't even involve the rotation in the physics: put the rotate-able sprite as a child and then constrain the Rigidbody2D to never rotate, and then just "drive" the rotation of the child object directly.
     
    egonspengler_84 likes this.
  9. egonspengler_84

    egonspengler_84

    Joined:
    Jan 26, 2021
    Posts:
    153
    I'm just focusing on the first part of your answer. I've never come across .MovePosition() before and I'm not sure what it's purpose is? From reading on Unity Docs it seems to give a new position for a Rigidbody? I'm not sure what that mean?

    How could I incorporate the .MovePosition() function into my existing code to fix the bug?
     
  10. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,726
    If you set position or rotation directly, it is a "teleport" to the physics engine. That's why you are supposed to use MovePosition / MoveRotation, which gives the physics engine a chance to consider how to solve the problem.
     
  11. egonspengler_84

    egonspengler_84

    Joined:
    Jan 26, 2021
    Posts:
    153

    Okay so I've been doing a lot of reading about MoveRotation and I still don't understand how to implement it properly in my existing code. Just for practice I wrote to following code in the Start() function to see if it would rotate my Player game object 90 degrees like so:

    Code (CSharp):
    1.  
    2.          
    3.     void Start()
    4.     {
    5.  
    6.  
    7.         _col2D = GetComponent<BoxCollider2D>();
    8.         _rb = GetComponent<Rigidbody2D>();
    9.  
    10.            Quaternion deltaRotation = Quaternion.Euler(0,0,90f * Time.fixedDeltaTime);
    11.         _rb.MoveRotation(_rb.rotation * deltaRotation.z);
    12.     }
    13.  
    But when I run the code nothing happens. I've tried reading about moveRotation on Unity docs and through looking at Youtube videos but I'm not sure how to properly use it. I'm guessing I would put the following two lines of code within the "if raycast hit" statement when the raycast on the bottom right corner of the gameobject hits the slope like so:

    Code (CSharp):
    1. if(hit){
    2.  
    3. Quaternion deltaRotation = Quaternion.Euler(0,0,Vector2.Angle(Vector2.up,hit.normal) * Time.fixedDeltaTime);
    4.         _rb.MoveRotation(_rb.rotation * deltaRotation.z);
    5.  
    6. }

    Am I on the right track? I'm really struggling to understand how to do this.
     
  12. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,726

    whoa, stop, I don't know where all that came from... you're using 2D physics, not 3D. you don't need quaternions in 2D, rotation is just a float, and you definitely do NOT ever want to touch the .x, .y, .z, or .w of a Quaternion... those are quaternion internals, NOT euler angles!!!

    Put this in Update():

    Code (CSharp):
    1.         GetComponent<Rigidbody2D>().MoveRotation(100 * Time.time);
    2.  
     
  13. egonspengler_84

    egonspengler_84

    Joined:
    Jan 26, 2021
    Posts:
    153

    Okay so I've written that code that you suggested into the Update function and the only way it works is if I "unfreeze" the Z rotation constraint in the Rigidbody2D component which is not good considering that the Player game object will fall over itself when I try to move it horizontally.

    Also, I attempted to implement this code into the "Raycast Hit" if statement to see if I can get the Player Game Object to rotate to the angle of the slope like so:


    Code (CSharp):
    1. if(hit){
    2.  
    3.        Debug.Log("The angle between the Normal and Global Up is: " + Vector2.Angle(Vector2.up,hit.normal));
    4.    
    5.         _rb.MoveRotation(Vector2.Angle(Vector2.up,hit.normal) * Time.time);
    6.       }
    But it's giving me this buggy result as shown in this video here: