Search Unity

Question Rotating a gameobject question

Discussion in 'Scripting' started by tsp120, Mar 31, 2023.

  1. tsp120

    tsp120

    Joined:
    Dec 6, 2013
    Posts:
    15
    I feel like this should be Unity 101, but I am completely stuck.

    I have a gameobject that I want to rotate -90 degrees on the x axis if I click the left arrow, 90 degrees on the x axis if I click the right arrow, 90 degrees on the z axis if I click the up arrow and -90 degrees on the z axis if I click the down arrow. The object should take 1 second to do the rotation. To clarify a little further, from the perspective of a static camera, the object should appear to rotate in the same direction each time a given button is clicked.

    When my object starts at an initial 0 rotation I can easily code this to work. However, once the object is at 90 degrees on the x axis, the object appears to rotate on the y axis instead of the z axis if I click up or down. I also notice this even in the unity editor if I manually slide the rotation values.

    Any insight into how I can make the object always rotate in my desired location in world space regardless of the objects current rotation? I clearly don't have a full grasp of Quaternions and rotations.

    Here's a quick script thanks to chatGPT that demonstrates what I'm talking about.

    Code (CSharp):
    1. using System.Collections;
    2. using UnityEngine;
    3.  
    4. public class CubeController : MonoBehaviour
    5. {
    6.     public float rotationTime = 1f;
    7.     public KeyCode leftKey = KeyCode.LeftArrow;
    8.     public KeyCode rightKey = KeyCode.RightArrow;
    9.     public KeyCode upKey = KeyCode.UpArrow;
    10.     public KeyCode downKey = KeyCode.DownArrow;
    11.  
    12.     private bool isRotating = false;
    13.  
    14.     void Update()
    15.     {
    16.         if (!isRotating && Input.GetKeyDown(leftKey))
    17.         {
    18.             StartCoroutine(RotateObjectLocalX(-90f));
    19.         }
    20.         else if (!isRotating && Input.GetKeyDown(rightKey))
    21.         {
    22.             StartCoroutine(RotateObjectLocalX(90f));
    23.         }
    24.         else if (!isRotating && Input.GetKeyDown(upKey))
    25.         {
    26.             StartCoroutine(RotateObjectLocalZ(90f));
    27.         }
    28.         else if (!isRotating && Input.GetKeyDown(downKey))
    29.         {
    30.             StartCoroutine(RotateObjectLocalZ(-90f));
    31.         }
    32.     }
    33.  
    34.     IEnumerator RotateObjectLocalX(float angle)
    35.     {
    36.         isRotating = true;
    37.  
    38.         Quaternion targetRotation = transform.localRotation * Quaternion.Euler(angle, 0, 0);
    39.         Quaternion initialRotation = transform.localRotation;
    40.         float elapsedTime = 0f;
    41.  
    42.         while (elapsedTime < rotationTime)
    43.         {
    44.             transform.localRotation = Quaternion.Slerp(initialRotation, targetRotation, elapsedTime / rotationTime);
    45.             elapsedTime += Time.deltaTime;
    46.             yield return null;
    47.         }
    48.  
    49.         isRotating = false;
    50.     }
    51.  
    52.     IEnumerator RotateObjectLocalZ(float angle)
    53.     {
    54.         isRotating = true;
    55.  
    56.         Quaternion targetRotation = transform.localRotation * Quaternion.Euler(0, 0, angle);
    57.         Quaternion initialRotation = transform.localRotation;
    58.         float elapsedTime = 0f;
    59.  
    60.         while (elapsedTime < rotationTime)
    61.         {
    62.             transform.localRotation = Quaternion.Slerp(initialRotation, targetRotation, elapsedTime / rotationTime);
    63.             elapsedTime += Time.deltaTime;
    64.             yield return null;
    65.         }
    66.  
    67.         isRotating = false;
    68.     }
    69. }
    70.  
     
  2. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,104
    tsp120 likes this.
  3. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,842
    Rotate your object on world axis, such as
    Vector.right
    or
    Vector3.up
    .

    You should also generate the rotation around the axis with Quaternion.AngleAxis, rather than using euler angles. You also don't need to be using co-routines here.

    Here's something I whipped up quickly to show how it can be simplified:
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class CubeController : MonoBehaviour
    4. {
    5.    #region Inspector Fields
    6.  
    7.    [SerializeField]
    8.    private float _rotationSpeed = 10f;
    9.  
    10.    [SerializeField]
    11.    private KeyCode _upKeycode = KeyCode.W;
    12.  
    13.    [SerializeField]
    14.    private KeyCode _downKeycode = KeyCode.S;
    15.  
    16.    [SerializeField]
    17.    private KeyCode _leftKeycode = KeyCode.A;
    18.  
    19.    [SerializeField]
    20.    private KeyCode _rightKeycode = KeyCode.D;
    21.  
    22.    #endregion
    23.  
    24.    #region Internal Members
    25.  
    26.    private Quaternion _targetRotation;
    27.  
    28.    #endregion
    29.  
    30.    #region Unity Callbacks  
    31.  
    32.    private void Awake()
    33.    {
    34.        _targetRotation = transform.rotation;
    35.    }
    36.  
    37.  
    38.    private void Update()
    39.    {
    40.        float vertInput = GetAxisInput(_downKeycode, _upKeycode);
    41.        float hozInput = GetAxisInput(_rightKeycode, _leftKeycode);
    42.  
    43.        if (vertInput != 0f)
    44.        {
    45.            UpdateTargetRotation(90 * vertInput, Vector3.right);
    46.        }
    47.  
    48.        if (hozInput != 0f)
    49.        {
    50.            UpdateTargetRotation(90 * hozInput, Vector3.up);
    51.        }
    52.  
    53.        transform.rotation = Quaternion.RotateTowards(transform.rotation, _targetRotation, _rotationSpeed);
    54.    }
    55.  
    56.    private float GetAxisInput(KeyCode inputNegative, KeyCode inputPositive)
    57.    {
    58.        if (Input.GetKeyDown(inputNegative))
    59.        {
    60.            return -1f;
    61.        }
    62.        else if (Input.GetKeyDown(inputPositive))
    63.        {
    64.            return 1f;
    65.        }
    66.        else
    67.        {
    68.            return 0f;
    69.        }
    70.    }
    71.  
    72.    private void UpdateTargetRotation(float angle, Vector3 axis)
    73.    {
    74.        Quaternion rotation = Quaternion.AngleAxis(angle, axis);
    75.        _targetRotation = rotation * _targetRotation;
    76.    }
    77.  
    78.    #endregion
    79. }
     
    tsp120 likes this.
  4. tsp120

    tsp120

    Joined:
    Dec 6, 2013
    Posts:
    15
    Thank you both!

    The code provided does almost exactly what I wanted. I just needed to modify the Vector directions as they weren't spinning the gameObject in the direction I wanted. But otherwise yes, this is perfect!
     
  5. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,997
    Spiney used a different method of rotation, which is fine. But just so you know, I think the problem with your original code was it used local space instead of global. In other words, if your object is pointed straight up then its personal y runs now along z, so y-rotations are now z-rotations (and z rotations are y-rotations since z was changed to point up). The Inspector also uses local. It goes in order YXZ, which means Y is always global, but X-rotations change based on Y and Z based on Y then X (which is actually what we want most of the time).

    It turns out the order in * determines local vs. global. Your
    transform.localRotation * Quaternion.Euler(0, 0, angle)
    makes (0,0,angle) be local rotation, since it goes last. That's just the rule. Flipping to put it first:
    Quaternion.Euler(0, 0, angle)*transform.localRotation
    makes it global. That seems bad, but for real every rotation can be used as local or global. That's just a fact of rotations. Being able to choose using A*B or B*A is nice, once you get used to it.

    If you look at Spiney's code you'll find the same flip to global (in UpdateRotation):
    _targetRotation = rotation * _targetRotation
    ("rotation" is the new rotation, which is before the *, not after).