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\$
8
  • \$\begingroup\$ I think you need if (time >= 1/60 ) instead of if (time > 0) \$\endgroup\$
    – dimitris93
    Commented Mar 22, 2015 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\$ Commented Mar 22, 2015 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
    Commented Mar 22, 2015 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\$ Commented Mar 22, 2015 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
    Commented Mar 22, 2015 at 3:05

2 Answers 2

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\$
2
  • \$\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\$ Commented Mar 22, 2015 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
    Commented Mar 22, 2015 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\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .