Search Unity

Rotation to Surface Normal Issues

Discussion in 'Scripting' started by Atomike, Oct 14, 2017.

  1. Atomike

    Atomike

    Joined:
    Mar 25, 2014
    Posts:
    15
    I'm attempting to make a character that can run through a loop. I'm using the following code to rotate the character to match the surface normal:

    Code (CSharp):
    1. RaycastHit hit;
    2.         Vector3 pos = new Vector3 (transform.position.x, transform.position.y + 0.1f, transform.position.z);
    3.         if (Physics.Raycast (pos, -transform.up, out hit, 0.3f)) {
    4.  
    5.  
    6.             transform.rotation = Quaternion.Slerp (transform.rotation, Quaternion.FromToRotation (transform.up, hit.normal) * transform.rotation, Time.deltaTime * 15f);
    7. }
    This works fine up until the character rotates more than 90 degrees. This graph shows kind of what I'm explaining:

    The capsule represents the character and the red line represents the raycast.

    Anyone know why this is?
     
  2. TimmyTheTerrible

    TimmyTheTerrible

    Joined:
    Feb 18, 2017
    Posts:
    186
    Is it actually colliding the whole time? You might change the ray position to vectorPos = transform.position + transform.up *0.1.

    I'm assuming your trying to offset the ray position a little so the collision registers but in your code your accidentally offsetting in the positive up direction, when you want to be offsetting in the up direction of the capsule.
     
    Atomike and Polymorphik like this.
  3. Atomike

    Atomike

    Joined:
    Mar 25, 2014
    Posts:
    15
    Thank you! I'm having another issue regarding rotation. I don't think I should move it to a new thread since it ties into this problem.
    I'm using this code to rotate the character based on the Vertical and Horizontal Input Axis and the direction the camera is facing:
    Code (CSharp):
    1.  
    2.         float myAngle = Mathf.Atan2 (Input.GetAxis ("Horizontal"), Input.GetAxis ("Vertical")) * Mathf.Rad2Deg;
    3.  
    4.         float bodyRotation = myAngle + forward.transform.eulerAngles.y;
    5.  
    6.         Vector2 inputPower = new Vector2 (Input.GetAxis ("Horizontal"), Input.GetAxis ("Vertical"));
    7.  
    8.         anim.SetFloat ("MoveSpeed", rb.velocity.magnitude);
    9.  
    10.         float rotationValue = Input.GetAxis ("Horizontal") * 90f;
    11.         Quaternion originalRot = transform.rotation;
    12.  
    13.         if (inputPower.magnitude > 0.1) {
    14.             transform.rotation = Quaternion.Slerp (transform.rotation, Quaternion.Euler (transform.eulerAngles.x, bodyRotation, transform.eulerAngles.z), Time.deltaTime * 10f);
    15.         }
    The problem is that if I'm on an uneven surface, when I go to turn it seems to rotate the player as if they are standing vertically rather than diagonally on the surface.
     
  4. TimmyTheTerrible

    TimmyTheTerrible

    Joined:
    Feb 18, 2017
    Posts:
    186
    The problem i think is in your use of Quaternion.Euler(). It rotates around the world xyz, not the localy xyz, which is what you want. If you want to rotate around the world xz, but the local up axis, try storing it as 2 different quaternions and multiplying them, such as Quaternion.Euler(x, 0, y), and the second Quaternion.AngleAxis(bodyRotation, capsule.up).
     
  5. Atomike

    Atomike

    Joined:
    Mar 25, 2014
    Posts:
    15
    Code (CSharp):
    1.         Quaternion newRot = Quaternion.AngleAxis (bodyRotation, transform.up);
    2.  
    3.         Quaternion oRot = new Quaternion (transform.rotation.x, 0, transform.rotation.z, transform.rotation.w);
    4.  
    5.         if (inputPower.magnitude > 0.1) {
    6.             transform.rotation = newRot * oRot;
    7.         }
    Do you mean like this? I'm still having the same issue. I tried Quaternion.Euler for oRot as well with the same result.
     
  6. TimmyTheTerrible

    TimmyTheTerrible

    Joined:
    Feb 18, 2017
    Posts:
    186
    try oRot * newRot and see if that fixes it. the order of the rotations is important. But what are you trying to achieve exactly? I'm slightly confused.

    You have an object, i'm assuming a 3rd person perspective, you said you wanted to rotate it based off of the camera, but i do not see anything representing the camera in your earlier code.

    and you appear to want to rotate the object when it is aligned to a surface that it is standing on, is that about right?
     
  7. TimmyTheTerrible

    TimmyTheTerrible

    Joined:
    Feb 18, 2017
    Posts:
    186
    here is some pseudo code for what i think you are trying to do:

    Code (CSharp):
    1.  
    2.  
    3. // In your previous code example i noticed your generating an angle from the x and y user inputs but i couldn't see a
    4. // reason for this? Am i wrong?
    5. float angleFromInput =  Input.Axis("Horizontal") * Time.deltaTime * desiredRotationSpeed;
    6.  
    7. // From here what is your input actually used for? is it to rotate around the local up vector?
    8.  
    9. transform.rotation *= Quaterion.AngleAxis(angleFromInput, transform.up);
    10.  
    11. // I'm also not sure at which point your object needs to be aligned to the surface normal, but you might need that
    12. // here next
    13. transform.rotation *= Quaternion.FromTo(transform.up, surfaceNormal);
    14.  
    15.  
     
    Last edited: Oct 20, 2017
  8. Atomike

    Atomike

    Joined:
    Mar 25, 2014
    Posts:
    15
    the GameObject "forward" is the camera object.
    Code (CSharp):
    1. float myAngle = Mathf.Atan2 (Input.GetAxis ("Horizontal"), Input.GetAxis ("Vertical")) * Mathf.Rad2Deg;
    2. float bodyRotation = myAngle + forward.transform.eulerAngles.y;

    It didn't seem to change the result when I reversed the order in which the quaternions were multiplied.

    The goal is to have the player's up direction match the surface normal and still maintain the ability to rotate the player and turn using the input axis relative to the character's current rotation, Similar to the Sonic the Hedgehog games. When you hold down the "D" key, he runs in the camera's right direction, if you hold down the "W" key, he runs in the camera's forward direction while still maintaining the ability to run up walls and loops. The code you posted doesn't seem to resolve this issue.
     
    Last edited: Oct 21, 2017
  9. Atomike

    Atomike

    Joined:
    Mar 25, 2014
    Posts:
    15

    This image should show what I'm trying to do.

    Code (CSharp):
    1. transform.rotation *= Quaterion.AngleAxis(angleFromInput, transform.up);
    This works except turning left causes the character to rapidly spin around counter-clockwise instead of facing the direction of the joystick axis relative to camera rotation.
     
    Last edited: Oct 21, 2017
  10. TimmyTheTerrible

    TimmyTheTerrible

    Joined:
    Feb 18, 2017
    Posts:
    186
    Alright so this is pretty quick and dirty. Maybe when i'll play with it more when i'm done pulling my hair out over my own code. But this "mostly" works. You will have to play with it. There will be some issues, mostly in defining what is forward as your characters orientation changes. Rotating the cameras up to match the players up might be the best bet.

    Code (CSharp):
    1.  
    2.  
    3. using UnityEngine;
    4.  
    5. [RequireComponent(typeof(CharacterController))]
    6. public class TimsSimpleMotor : MonoBehaviour
    7. {
    8.     [Tooltip("The speed of our motor.")]
    9.     public float movementSpeed = 5f;
    10.     [Tooltip("The layer mask for our ground detector ray.")]
    11.     public LayerMask layerMask;
    12.  
    13.     CharacterController charController;
    14.  
    15.     // Use this for initialization
    16.     void Start ()
    17.     {
    18.         charController = GetComponent<CharacterController>();
    19.     }
    20.  
    21.     // Update is called once per frame
    22.     void Update ()
    23.     {
    24.         float x = Input.GetAxis("Horizontal") * Time.deltaTime;
    25.         float y = Input.GetAxis("Vertical") * Time.deltaTime;
    26.  
    27.         Vector3 velocity = Vector3.zero;
    28.  
    29.         // Spherecast instead of ray cast, using ray casts can miss possible collisions and was giving me issues on occasion.
    30.         RaycastHit hit = new RaycastHit();
    31.         bool collisionResult = Physics.SphereCast(transform.position + transform.up * 0.1f, charController.radius, -transform.up, out hit, 10f, layerMask, QueryTriggerInteraction.Ignore);
    32.         // Debug ray drawn to the collision point. Helps when the ray doesn't intersect anything to 'help'(i.e. irritate me)  detect errors.
    33.         Debug.DrawLine(transform.position, hit.point, Color.green);
    34.  
    35.         if (collisionResult)
    36.         {
    37.             // Project the camera forward onto the plane to get our desired forward direction. Note that this could cause
    38.             // issues if the cameras forward direction is pointing exactly along the up direction of the character. Maybe
    39.             // i'll try and fix this later.
    40.             Vector3 projectedForward = Vector3.ProjectOnPlane(Camera.main.transform.forward, hit.normal);
    41.             projectedForward.Normalize();
    42.  
    43.             // Ray to test what the hell is going on with the projected forward...just incase it misbehaves....
    44.             Debug.DrawLine(hit.point, hit.point + projectedForward, Color.yellow);
    45.  
    46.             transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(projectedForward, hit.normal), 10f * Time.deltaTime);
    47.             velocity = projectedForward * (y * movementSpeed) + transform.right * (x * movementSpeed);
    48.  
    49.             // Stick to surface.
    50.             if (collisionResult && hit.distance < 5f)
    51.                 transform.position = hit.point + transform.up * ((charController.height / 2f) + 0.01f);
    52.         }
    53.         else
    54.         {
    55.             Debug.Log("ERROR!!! : There was no collision detected!!! WHAT-THE-HELL!!!!");
    56.             transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(transform.forward, Vector3.up), 10f * Time.deltaTime);
    57.         }
    58.  
    59.         charController.Move(velocity);
    60.     }
    61. }
    62.  
    63.  
     
  11. TimmyTheTerrible

    TimmyTheTerrible

    Joined:
    Feb 18, 2017
    Posts:
    186
    After a little bit more tinkering i was able to come away with this, give it a shot. You will have to customize it to
    suit your own needs and i still feel it works best when the camera rotates with the character but it works ok all things consider. Hopefully it helps :p best a scrub like me can do for ya. Good luck!


    Code (CSharp):
    1.  
    2.  
    3. using UnityEngine;
    4.  
    5. [RequireComponent(typeof(CharacterController))]
    6. public class TimsSimpleMotor : MonoBehaviour
    7. {
    8.     [Tooltip("The speed of our motor.")]
    9.     public float movementSpeed = 5f;
    10.     [Tooltip("The layer mask for our ground detector ray.")]
    11.     public LayerMask layerMask;
    12.  
    13.     CharacterController charController;
    14.  
    15.     Vector3 savedContactNormal = Vector3.up;
    16.  
    17.     // Use this for initialization
    18.     void Start ()
    19.     {
    20.         charController = GetComponent<CharacterController>();
    21.     }
    22.  
    23.     // Update is called once per frame
    24.     void Update ()
    25.     {
    26.         float x = Input.GetAxis("Horizontal") * Time.deltaTime;
    27.         float y = Input.GetAxis("Vertical") * Time.deltaTime;
    28.      
    29.         Vector3 movement = new Vector3(x, 0, y);
    30.         movement.Normalize();
    31.  
    32.         movement = Camera.main.transform.TransformDirection(movement);
    33.         Debug.DrawLine(transform.position, transform.position + movement * 10f, Color.black);
    34.  
    35.         Vector3 velocity = Vector3.zero;
    36.  
    37.         // Spherecast instead of ray cast, using ray casts can miss possible collisions and was giving me issues on occasion.
    38.         RaycastHit hit = new RaycastHit();
    39.         bool collisionResult = Physics.SphereCast(transform.position + transform.up * 0.1f, charController.radius, -transform.up, out hit, 10f, layerMask, QueryTriggerInteraction.Ignore);
    40.         // Debug ray drawn to the collision point. Helps when the ray doesn't intersect anything to 'help'(i.e. irritate me)  detect errors.
    41.         Debug.DrawLine(transform.position, hit.point, Color.green);
    42.  
    43.         if (movement.magnitude > 0.1f && collisionResult)
    44.         {
    45.             savedContactNormal = hit.normal;
    46.  
    47.             Quaternion yMatchSurfaceRotation = Quaternion.FromToRotation(transform.up, hit.normal);
    48.             movement = Quaternion.FromToRotation(Camera.main.transform.up, hit.normal) * movement;
    49.             transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(movement, hit.normal), 5f * Time.deltaTime);
    50.  
    51.             Debug.DrawLine(transform.position, transform.position + movement * 10f, Color.gray);
    52.  
    53.             velocity = transform.forward * movementSpeed * Time.deltaTime;
    54.  
    55.             // Stick to surface.
    56.             if (hit.distance < 5f)
    57.                 transform.position = Vector3.Lerp(transform.position, hit.point + transform.up * ((charController.height / 2f) + 0.01f), 10f * Time.deltaTime);
    58.         }
    59.         else
    60.             transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(transform.forward, savedContactNormal), 10f * Time.deltaTime);
    61.  
    62.         charController.Move(velocity);
    63.     }
    64. }
    65.  
    66.  
     
    Last edited: Oct 22, 2017
  12. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    916
    When you want to rotate relative to the world you affect transform.rotation. however when you want to rotate relative to an objects orientation you should affect transform.localRotation instead.

    Your surface normal calcuations are done in world space so they should use rotation, yet you want your character's rotation from input to be done in localspace so you should use localRotation with your inputs.
     
  13. Atomike

    Atomike

    Joined:
    Mar 25, 2014
    Posts:
    15
    LocalRotation is relative to the parent object's rotation. My character controller has no parent gameobject.

    Here's my entire script if anyone can make any sense of it and fix it lol:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class PlayerController : MonoBehaviour {
    6.     public bool grounded;
    7.     public Animator anim;
    8.     public Rigidbody rb;
    9.     public GameObject forward;
    10.     public bool jump;
    11.     public bool jumpActivated;
    12.     public AudioClip jumpSFX;
    13.     public GameObject rotOBJ;
    14.     public bool inHouse;
    15.  
    16.     void Start () {
    17.         grounded = true;
    18.         rb = GetComponent<Rigidbody> ();
    19.     }
    20.  
    21.     void FixedUpdate () {
    22.         anim.SetFloat ("MoveSpeed", (Mathf.Lerp (anim.GetFloat ("MoveSpeed"), rb.velocity.magnitude, Time.deltaTime * 7f)));
    23.     }
    24.  
    25.     void Update () {
    26.         RaycastHit hit;
    27.         Vector3 pos = transform.position + transform.up * 0.1f;
    28.         if (Physics.Raycast (pos, -transform.up, out hit, 0.3f)) {
    29.  
    30.  
    31.             transform.rotation = Quaternion.Slerp (transform.rotation, Quaternion.FromToRotation (transform.up, hit.normal) * transform.rotation, Time.deltaTime * 15f);
    32.      
    33.             if (jumpActivated == false) {
    34.                 if (Vector3.Dot (transform.forward, rb.velocity) > 4f) {
    35.                     transform.position = new Vector3 (transform.position.x, hit.point.y, transform.position.z);
    36.                 }
    37.             }
    38.         }
    39.  
    40.         RaycastHit[] cast = Physics.RaycastAll (transform.position + transform.up * 0.6f, -transform.up, 0.75f);
    41.         int idx = 0;
    42.         grounded = false;
    43.         while (idx < cast.Length) {
    44.             if (cast [idx].collider.gameObject.tag == "Floor") {
    45.                 grounded = true;
    46.             }
    47.             idx++;
    48.         }
    49.  
    50.         float myAngle = Mathf.Atan2 (Input.GetAxis ("Horizontal"), Input.GetAxis ("Vertical")) * Mathf.Rad2Deg;
    51.  
    52.         float bodyRotation = myAngle + forward.transform.rotation.y;
    53.  
    54.         Vector2 inputPower = new Vector2 (Input.GetAxis ("Horizontal"), Input.GetAxis ("Vertical"));
    55.  
    56.         Quaternion originalRot = transform.rotation;
    57.  
    58.         Vector3 newrot = Vector3.Scale (new Vector3 (0, bodyRotation, 0), transform.up);
    59.  
    60.         if (inputPower.magnitude > 0.1) {
    61.             transform.localRotation = Quaternion.Euler (newrot);
    62.         }
    63.  
    64.         float vel;
    65.         if (inHouse == true) {
    66.             vel = Mathf.Lerp (Vector3.Dot (transform.forward, rb.velocity), inputPower.magnitude * 10f, Time.deltaTime * 3f);
    67.         } else {
    68.             vel = Mathf.Lerp (Vector3.Dot (transform.forward, rb.velocity), inputPower.magnitude * 35f, Time.deltaTime * 1.5f);
    69.         }
    70.  
    71.         if (grounded == true) {
    72.             rb.velocity = transform.forward * vel;
    73.  
    74.         } else {
    75.             transform.rotation = Quaternion.Slerp (transform.rotation, new Quaternion (0, originalRot.y, 0, originalRot.w), Time.deltaTime * 5f);
    76.             rb.AddForce (-Vector3.up * 25f);
    77.             rb.AddForce (transform.forward * inputPower.magnitude * 25f);
    78.         }
    79.     }
    80. }
    81.