0
\$\begingroup\$

I was facing another rotation problem yesterday, that i managed to solve by rotating the object like this :

transform.Rotate (speed * Time.deltaTime, 0, 0, Space.World);

I tried almost every other way but nothing seems to work, usually i only use Euler angles to rotate my objects but in this case i couldn't.

So what am trying to accomplish here is to rotate the cube 90 degree around the X or Z axe everytime the player press the arrow key (doesn't have to stay pressing), and theoretically i thought this would work :

if (Input.GetKeyDown (KeyCode.LeftArrow)) {
            targetEul = transform.eulerAngles;
            targetEul.x += 90;
            timer = 90;
     //also tried this alone:
    targetQuat = Quaternion.Euler (transform.rotation.eulerAngles.x + 90, transform.rotation.eulerAngles.y, transform.rotation.eulerAngles.z);   
   }

if (timer>0){
     timer -= speed * Time.deltaTime;
     transform.Rotate (0, 0, speed *  Time.deltaTime, Space.World);
  }else{
    transform.rotation = Quaternion.Euler (targetEul);
 }

This is to avoid the risk of getting the object rotation out of sync which will eventually happens if i don't use that line inside the else statement : GIF PREVIEW

Now if i use that to adjust the rotation, this happens : GIF PREVIEW

in the rotation number: 4 the object start to flip itself strangely, which i have no idea why.

So what i exactly need is this, say am working with just a float that i want to increase in time to reach 10.00, if i do this :

if  (x<10){
    x+=Time.deltaTime;
}

Then the final result of x won't be exactly 10.00, it would be something a little bit more. and so i need to make it like this :

if(x<10){
   x+=Time.deltaTime;
} else{
   x=10;
}

That is exactly what i am trying to make but instead of X i want the object rotation, and instead of 10 i want the previous object rotation+90 on X or Z

Is it really "impossible" to do in that simple way?

Thank you very much

EDIT here is the entire Class am using in this example :

using UnityEngine;
using System.Collections;
using UnityEngine.UI;

public class RotateCube : MonoBehaviour
{
    public Text txt;
    public  float speed;
    public Vector3  targetEul, myEul;
    public bool canRotate, doLeft, doRight;
    int i ;

    void Update ()
    {
        //check input when possible
        if (canRotate) 
            checkInput ();
        if (doLeft) 
            checkLeft ();
        if (doRight) 
            checkRight ();
    }

    //rottate left
    float chrono;
    void checkLeft ()
    {
        if (chrono > 0) {
            chrono -= speed * Time.deltaTime;
            transform.Rotate (speed * Time.deltaTime, 0, 0, Space.World);
            myEul.x += speed * Time.deltaTime;
        } else {
            transform.rotation = Quaternion.Euler (targetEul);
            //transform.rotation = Quaternion.Slerp(transform.rotation,targetQuat,100);
            canRotate = true;
            doLeft = false;
        }
    }

    //rotate right
    float chronoR;

    void checkRight ()
    {
        if (chronoR > 0) {
            chronoR -= speed * Time.deltaTime;
            transform.Rotate (0, 0, -speed * Time.deltaTime, Space.World);
            myEul.z += speed * Time.deltaTime;
        } else {
            transform.rotation = Quaternion.Euler (targetEul);
            //transform.rotation = Quaternion.Slerp (transform.rotation, targetQuat, 100);
            canRotate = true;
            doRight = false;            
        }
    }

    //check input
    Quaternion targetQuat;

    void checkInput ()
    {
        if (Input.GetKeyDown (KeyCode.LeftArrow)) {
            //targetQuat = transform.rotation * (Quaternion.AngleAxis (90,Vector3.left));Quaternion.Euler (transform.rotation.eulerAngles.x + 90, 0, 0);
            targetQuat = Quaternion.Euler (transform.rotation.eulerAngles.x + 90, transform.rotation.eulerAngles.y, transform.rotation.eulerAngles.z);//Quaternion.AngleAxis(90,Vector3.right);
            targetEul = transform.eulerAngles;
            targetEul.x += 90;          
            chrono = 90;
            canRotate = false;
            doLeft = true;
            i++;
        }

        if (Input.GetKeyDown (KeyCode.RightArrow)) {
            targetQuat = Quaternion.Euler (transform.rotation.eulerAngles.x, transform.rotation.eulerAngles.y, transform.rotation.eulerAngles.z + 90);
            targetEul = transform.eulerAngles;
            targetEul.z += 90;
            chronoR = 90;
            canRotate = false;
            doRight = true;
            i++;
        }
        txt.text = "" + i;
    }   
}
\$\endgroup\$
  • \$\begingroup\$ I think you need if (time >= 1/60 ) instead of if (time > 0) \$\endgroup\$ – dimitris93 Mar 22 '15 at 2:37
  • \$\begingroup\$ not really, beside even if that work (which it doesn't i just tested it) it meant to make me avoid the "manual adjustment" of the rotation inside the else statement, which is the purpose of this question, i need to know my target rotation before doing transform.Rotate(); \$\endgroup\$ – alaslipknot Mar 22 '15 at 2:41
  • \$\begingroup\$ could you explain what "didn't work" means ? you got the same result ? could you try if (time >= 1/60 + 0.01f) ? \$\endgroup\$ – dimitris93 Mar 22 '15 at 2:44
  • \$\begingroup\$ yes i tried it, when i keep the 'transform.rotation = Quaternion.Euler (targetEul);' inside the else the statement i get the same result as the 2nd GIF preview, when i remove that, i get the same result as the first GIF preview but only a bit "later" \$\endgroup\$ – alaslipknot Mar 22 '15 at 2:50
  • \$\begingroup\$ i never said you should remove the else statement. It is needed. Also, shouldn't targetEul.x += 90; be targetEul.z += 90; instead ? you seem to want to rotate according to Z axis here: transform.Rotate (0, 0, speed * Time.deltaTime, Space.World); \$\endgroup\$ – dimitris93 Mar 22 '15 at 3:05
1
\$\begingroup\$

This sounds like a perfect candidate to use coroutines. By using a coroutine, you can keep the amount of (class) state variables to a minimum and you end up with really clean code.

Here's an example how this could look like:

// duration of the rotation in seconds, can be set via Inspector
public float RotationDuration = 0.5f;
private bool _isRotating = false;

void Update () {
    if (Input.GetKeyDown(KeyCode.LeftArrow) && !_isRotating) { 
        StartCoroutine(RotateObject(
            transform.rotation, 
            transform.rotation * Quaternion.Euler(90, 0, 0), 
            RotationDuration
        ));
    }
    if (Input.GetKeyDown(KeyCode.RightArrow) && !_isRotating) {
        StartCoroutine(RotateObject(
            transform.rotation, 
            transform.rotation * Quaternion.Euler(0, 0, 90), 
            RotationDuration
        ));
    }
}

IEnumerator RotateObject(Quaternion start, Quaternion end, float duration)
{
    float endTime = Time.time + duration;
    _isRotating = true;
    while (Time.time <= endTime) {
        // normalized progress from 0 - 1
        float t = 1f - (endTime - Time.time) / duration;
        transform.rotation = Quaternion.Lerp(start, end, t);
        yield return 0;
    }
    transform.rotation = end;
    _isRotating = false;
}

If you're new to using coroutines, or if you need a refresher, check out this excellent introduction to coroutines.

\$\endgroup\$
  • \$\begingroup\$ yeah but this might go wrong in case of an FPS drop or any unexpected issue, i really don't want to have more control on the time but instead i need to know the correct target rotation. \$\endgroup\$ – alaslipknot Mar 22 '15 at 15:29
  • \$\begingroup\$ No, it will not go wrong with an FPS drop, because at the end of the coroutine, the transform is set to end, which is a predefined value not dependent on time at all. It's really basically the same as you do, just cleaner and more extensible (say if you wanted to rotate around other axes as well) \$\endgroup\$ – bummzack Mar 22 '15 at 18:38
0
\$\begingroup\$

Special thanks mintman from Reddit/r/Unity3D who fixed this issue here

(The following is just copied from his original answer)


After doing the interpolation, you set your result to a different quaternion:

float chronoP;
void checkQuatParent ()
{
    if (chronoP > 0) {
        chronoP -= speed * Time.deltaTime;
        myTrans.parent.rotation *= Quaternion.Euler (Vector3.forward * speed * Time.deltaTime);
    } else {
        // ---------- this line -------
        myTrans.parent.rotation = Quaternion.Euler (parentTargetEul);
        canRotate = true;
        doRight = false;
    }
}

Figure out your final final final target quaternion, and then just interpolate to it. I finally just opened Unity to make this. Apply it to just a vanilla cube - no parenting magic or anything. It actually did have more of a trick to it than I thought:

using UnityEngine;
// Attach to just a cube.
public class RotatingCube : MonoBehaviour {

        public float TotalSecondsInRotation = 0.7f;
        float t;
        Quaternion initial;
        Quaternion target;
        bool isRotating;

        void Update ()
        {
                if (!isRotating) {
                        if (Input.GetKeyDown (KeyCode.LeftArrow)) {
                                t = 0;

                                var unrotatedLeft = Quaternion.Inverse(transform.rotation) * Vector3.left;
                                var worldRotateLeft = Quaternion.AngleAxis(90, unrotatedLeft);

                                initial = transform.rotation;
                                target = initial * (worldRotateLeft);

                                isRotating = true;
                        }
                        if (Input.GetKeyDown (KeyCode.RightArrow)) {
                                t = 0;

                                var unrotatedForward = Quaternion.Inverse(transform.rotation) * Vector3.forward;
                                var worldRotateForward = Quaternion.AngleAxis(90, unrotatedForward);

                                initial = transform.rotation;
                                target = initial * (worldRotateForward);

                                isRotating = true;
                        }
                }

                if (isRotating) {
                        InterpRotation();
                        if (t >= 1)
                        {
                                isRotating = false;
                        }
                }
        }

        void InterpRotation ()
        {
                t += Time.deltaTime / TotalSecondsInRotation;
                transform.localRotation = Quaternion.Slerp(initial, target, t);
        }

}

The big trick was finding how to actually rotate around the correct axis:

var unrotatedLeft = Quaternion.Inverse(transform.rotation) * Vector3.left;
var worldRotateLeft = Quaternion.AngleAxis(90, unrotatedLeft);

initial = transform.rotation;
target = initial * (worldRotateLeft);

Multiplying Vector3.left by the inverse of the current rotation gives us the correct world axis to rotate around. We're effectively "undoing" the current rotation of the object to make sure the axis we're rotating around is correct.

Good luck!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.