Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Join us on Thursday, June 8, for a Q&A with Unity's Content Pipeline group here on the forum, and on the Unity Discord, and discuss topics around Content Build, Import Workflows, Asset Database, and Addressables!
    Dismiss Notice

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:
    2,713
    tsp120 likes this.
  3. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    4,494
    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,856
    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).