• Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
1
Question by Jimbobbedyjobob · Dec 08, 2015 at 02:45 PM · quaternionmathfrotatetowards

Quaternion Shortest Route?

I am trying to make a little abstract arcadey-esque gamey-thing.

I have a cube, around the centre point of which my camera rotates.

The camera always faces one side of the cube.

Each rotation is 90°. look at one face, button press, look at the next face. The rotation can be in any direction, "up", "down", "left" or "right".

My problem is that after a few rotations, rather than simply rotating the human-obvious 90° to the next face, the camera decides to rotate around the cube in an arc back the wrong way. It even ignores the 90° planes and whips round via corners and all sorts.

Chatting to an acquaintance, he suspects that my script doesn't like rotating from (hypothetical example) 270° to 90° via the 0° point. Rather than jumping the 0° point it will rotate alllllll the way back round the long way.

I am pretty much a scripting virgin, just doing tutorials and trying to apply them to my game when the light bulb goes on above me head.

PLEASE HELP ME

 public float turnSpeed = 200f;
     private int rotation = 0;
     private Quaternion qTo = Quaternion.identity;
     
     
     void Update () 
     {
     
             if ((Input.GetKeyDown ("w")) & (Input.GetKey (KeyCode.RightShift))) 
             {
                 rotation += 90;
                 qTo = Quaternion.Euler(rotation, 0, 0);
                 Debug.Log ("Camera Rotated Screen-North");
             }
             if ((Input.GetKeyDown ("s")) & (Input.GetKey (KeyCode.RightShift)))
             {
                 rotation -= 90;
                 qTo = Quaternion.Euler(rotation, 0, 0);
                 Debug.Log ("Camera Rotated Screen-South");
             }
             if ((Input.GetKeyDown ("d")) & (Input.GetKey (KeyCode.RightShift)))
             {
                 rotation -= 90;
                 qTo = Quaternion.Euler(0, 0, rotation);
                 Debug.Log ("Camera Rotated Screen-East");
             }
             if ((Input.GetKeyDown ("a")) & (Input.GetKey (KeyCode.RightShift))) 
             {
                 rotation += 90;
                 qTo = Quaternion.Euler(0, 0, rotation);
                 Debug.Log ("Camera Rotated Screen-West");
             }
 
         
             transform.rotation = Quaternion.RotateTowards(transform.rotation, qTo, turnSpeed * Time.deltaTime);
 
     }
Comment
Add comment
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

3 Replies

· Add your reply
  • Sort: 
avatar image
1
Best Answer

Answer by Bunny83 · Dec 09, 2015 at 01:02 PM

Your code doesn't make much sense. You only use one rotation variable which you increment / decrement in each of your cases but one time you use it as x-rotation and one time as z-rotation.

Also your rotations are absolute worldspace rotation an not relative to the screen. You should just dump your rotation variable and only use quaternions. Something like that:

 void Update () 
 {
     if (Input.GetKey (KeyCode.RightShift))
     {
         if ( Input.GetKeyDown ("w") ) 
         {
             qTo = Quaternion.AngleAxis(90, transform.right) * qTo;
         }
         else if ( Input.GetKeyDown ("s"))
         {
                  qTo = Quaternion.AngleAxis(-90, transform.right) * qTo;
         }
         else if (Input.GetKeyDown ("d"))
         {
             qTo = Quaternion.AngleAxis(90, transform.up) * qTo;
         }
         else if (Input.GetKeyDown ("a")) 
         {
             qTo = Quaternion.AngleAxis(-90, transform.up) * qTo;
         }
         transform.rotation = Quaternion.RotateTowards(transform.rotation, qTo, turnSpeed * Time.deltaTime);
 }

Note: this is untested but should work that way. You might need to flip the sign of the "90" if it rotates into a wrong direction.

Keep in mind that you can implicitly rotate the cube around the z axis as well. If you do an up, right down rotation you're back on the starting side but it's rotated by 90° counterclockwise. It's 3d, you can't prevent that unless you force a certain orientation on a certain side. However that will yield some strange transitions where you will experience a double rotation when going from one side to another.

Games which allow such a game mechanic might want to provide a way to rotate around z as well (usually with "q" and "e")

Also note that using tranform.up /.right /.forward might cause problems if you quickly press two different keys in succession. You might want to replace them with aTo*Vector3.up and aTo*Vector3.right. That way the axis is calculated from the current target orientation and not the current intermediate rotation.

Comment
Add comment · Show 4 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Jimbobbedyjobob · Dec 09, 2015 at 01:31 PM 0
Share

AAAAAAAAAAA!

That's working JUST how I want it to with the qto*Vector3 rather than transform. !

I now just have to add something to stop it from allowing a rotation command before the current action is completed.

Also, regarding the point about rotating all the way around the cube and being inverted once you return to the starting face: yup. I know. But it's important that screen-up is true to itself rather than the cube. I think it will be more intuitively understandable for gameplay. The cube around which I am rotating is the play-space. It's a very abstract game, so word-orientation doesn't really apply.

In case you're interested, this is the script as it stands now :)

Thanks,

Jo

     public float turnSpeed = 200f;
     private Quaternion qTo = Quaternion.identity;
     
     // Update is called once per frame
     void Update () 
     {
         if (Input.Get$$anonymous$$ey ($$anonymous$$eyCode.RightShift)) {
             if ((Input.Get$$anonymous$$eyDown ("w")) & (Input.Get$$anonymous$$ey ($$anonymous$$eyCode.RightShift)))
             {
                 qTo = Quaternion.AngleAxis (90, qTo*Vector3.right) * qTo;
             } 
             else if ((Input.Get$$anonymous$$eyDown ("s")) & (Input.Get$$anonymous$$ey ($$anonymous$$eyCode.RightShift))) 
             {
                 qTo = Quaternion.AngleAxis (-90, qTo*Vector3.right) * qTo;
             } 
             else if ((Input.Get$$anonymous$$eyDown ("d")) & (Input.Get$$anonymous$$ey ($$anonymous$$eyCode.RightShift))) 
             {
                 qTo = Quaternion.AngleAxis (-90, qTo*Vector3.forward) * qTo;
             } 
             else if ((Input.Get$$anonymous$$eyDown ("a")) & (Input.Get$$anonymous$$ey ($$anonymous$$eyCode.RightShift))) 
             {
                 qTo = Quaternion.AngleAxis (90, qTo*Vector3.forward) * qTo;
             }
             transform.rotation = Quaternion.RotateTowards (transform.rotation, qTo, turnSpeed * Time.deltaTime);
         }
     }

avatar image Bunny83 Jimbobbedyjobob · Dec 09, 2015 at 03:49 PM 0
Share

Well, in that case i suggest to use a coroutine. It could be implemented like this:

 public float turnSpeed;
 Coroutine RotateCo = null;

 IEnumerator RotateTo(Quaternion aTarget)
 {
     Quaternion startRotation = transform.rotation;
     for (float t = 0; t < 1.0f; t += Time.deltaTime * turnSpeed / 90)
     {
         transform.rotation = Quaternion.Slerp(startRotation, aTarget, t);
         yield return null;
     }
     RotateCo = null;
 }
 void StartRotateTo(Quaternion aTarget)
 {
     RotateCo = StartCoroutine( RotateTo( aTarget ) );
 }
 void Update()
 {
     if (RotateCo == null && Input.Get$$anonymous$$ey($$anonymous$$eyCode.RightShift))
     {
         if (Input.Get$$anonymous$$eyDown($$anonymous$$eyCode.W))
         {
             StartRotateTo(Quaternion.AngleAxis(90, transform.right));
         }
         else if (Input.Get$$anonymous$$eyDown($$anonymous$$eyCode.S))
         {
             StartRotateTo(Quaternion.AngleAxis(-90, transform.right));
         }
         else if (Input.Get$$anonymous$$eyDown($$anonymous$$eyCode.D))
         {
             StartRotateTo(Quaternion.AngleAxis(90, transform.up));
         }
         else if (Input.Get$$anonymous$$eyDown($$anonymous$$eyCode.A))
         {
             StartRotateTo(Quaternion.AngleAxis(-90, transform.up));
         }
     }
 }

I've replaced the "string version" of Get$$anonymous$$eyDown with the $$anonymous$$eyCode version. Also you don't need to check "RightShift" in each case since it's checked once for all. The RotateCo == null check will prevent you from starting a new coroutine while another one is still executing. The coroutine will set RotateCo back to "null" when it's finished rotating.

As a side note: In most cases you don't want to use & to combine several conditions. & will always evaluate both sides, even then the first one is already false. && which is the logical "and" operator will only evaluate the second operand if the first is true.

So this:

 if (A() & B())
     DoSomething();

is the same as

 bool a = A();
 bool b = B();
 if (a && b)
     DoSomething();

while this:

 if (A() && B())
     DoSomething();

is the same as

 if (A())
     if (B())
         DoSomething();

So "B" is only executed when "A" returns true. Since both (A and B )have to return true in order to execute DoSomething it's enough to know that A returns false. So we don't have to call "B" since it's return value can't change the outcome.

avatar image Jimbobbedyjobob Bunny83 · Jan 04, 2016 at 02:35 PM 0
Share

Hey Bunny83,

You're code is working well, $$anonymous$$y rotations no longer go wild and rotation the longest way round in some free-form jazzy manner!

Unfortunatly i think the coroutine is only disallowing execution for the duration of the registry of the button-press, rather than the execution of the full rotation. If you catch my drift...

I can press for a new rotation and it will start before the current rotation is completed.

I worry this will lead to confusion in gameplay.

Is there a way around it? Can I simply return the RotateCo to null at a different point in the code?

avatar image Jimbobbedyjobob Jimbobbedyjobob · Dec 10, 2015 at 01:12 PM 0
Share

you are clearly a marvellous human being, and we should set up temples in your honour.

I will test this whole shebang later once I have got my dayjob stuff done :D

avatar image
0

Answer by wibble82 · Dec 08, 2015 at 02:58 PM

Hey

I thought Quaternion.RotateTowards handled this automatically, but perhaps it doesn't.

Assuming it's not handled internally, the answer lies in the fact that a quaternion represents 720 degrees of rotation! Your rotation 'qTo' does represent the euler angle required. However if you imagine it as an instruction 'rotate to 90 degrees around the x axis', you could also say 'rotate to -270 degrees around the x axis' - the end result is the same.

If you want to make sure you always go in the correct direction, you need to do a bit of extra work, You need to make sure your quaternion and the other quaternion represent rotations in the same direction. This is done with a dot product:

 if (Quaternion.Dot(transform.rotation,qTo) < 0)
     qTo = -qTo;
 

Here we've said 'if transform.rotation is in the opposite direction to qTo, flip the direction of qTo'.

That should solve the problem, though I've not tested it. Let me know if you're still struggling and I'll take a look at home once I have unity in front of me.

-Chris

Comment
Add comment · Show 2 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Jimbobbedyjobob · Dec 09, 2015 at 12:36 PM 0
Share

Hey $$anonymous$$r Chris

Thanks for your rapid response!

Sadly, on adding your code to the script, Unity tells me - cannot be applied to Quaternions :/

Assets/Scripts/CamShift2QuatEul.cs(42,32): error CS0023: The -' operator cannot be applied to operand of type UnityEngine.Quaternion'

Is there a way around this? Or am i going to have to add something terrifying?

Thanks

Jo

 public float turnSpeed = 200f;
     private int rotation = 0;
     private Quaternion qTo = Quaternion.identity;
     
     
     void Update () 
     {
     
             if ((Input.Get$$anonymous$$eyDown ("w")) & (Input.Get$$anonymous$$ey ($$anonymous$$eyCode.RightShift))) 
             {
                 rotation += 90;
                 qTo = Quaternion.Euler(rotation, 0, 0);
                 Debug.Log ("Camera Rotated Screen-North");
             }
             if ((Input.Get$$anonymous$$eyDown ("s")) & (Input.Get$$anonymous$$ey ($$anonymous$$eyCode.RightShift)))
             {
                 rotation -= 90;
                 qTo = Quaternion.Euler(rotation, 0, 0);
                 Debug.Log ("Camera Rotated Screen-South");
             }
             if ((Input.Get$$anonymous$$eyDown ("d")) & (Input.Get$$anonymous$$ey ($$anonymous$$eyCode.RightShift)))
             {
                 rotation -= 90;
                 qTo = Quaternion.Euler(0, 0, rotation);
                 Debug.Log ("Camera Rotated Screen-East");
             }
             if ((Input.Get$$anonymous$$eyDown ("a")) & (Input.Get$$anonymous$$ey ($$anonymous$$eyCode.RightShift))) 
             {
                 rotation += 90;
                 qTo = Quaternion.Euler(0, 0, rotation);
                 Debug.Log ("Camera Rotated Screen-West");
             }
 
             if (Quaternion.Dot (transform.rotation, qTo) < 0) 
             {
             qTo = -qTo;
             }
 
             transform.rotation = Quaternion.RotateTowards(transform.rotation, qTo, turnSpeed * Time.deltaTime);
 
     }
avatar image Jimbobbedyjobob · Dec 09, 2015 at 01:57 PM 0
Share

Hey $$anonymous$$r Chris,

I added the line of code you suggested to the bottom of the script, before the execution of the rotation.

Sadly on entering Unity, it tells me - cannot be applied to Quaternions :/

" Assets/Scripts/CamShift2QuatEul.cs(42,32): error CS0023: The -' operator cannot be applied to operand of type UnityEngine.Quaternion' "

Is there a way around this?

 public float turnSpeed = 200f;
     private int rotation = 0;
     private Quaternion qTo = Quaternion.identity;
     
     
     void Update () 
     {
     
             if ((Input.Get$$anonymous$$eyDown ("w")) & (Input.Get$$anonymous$$ey ($$anonymous$$eyCode.RightShift))) 
             {
                 rotation += 90;
                 qTo = Quaternion.Euler(rotation, 0, 0);
                 Debug.Log ("Camera Rotated Screen-North");
             }
             if ((Input.Get$$anonymous$$eyDown ("s")) & (Input.Get$$anonymous$$ey ($$anonymous$$eyCode.RightShift)))
             {
                 rotation -= 90;
                 qTo = Quaternion.Euler(rotation, 0, 0);
                 Debug.Log ("Camera Rotated Screen-South");
             }
             if ((Input.Get$$anonymous$$eyDown ("d")) & (Input.Get$$anonymous$$ey ($$anonymous$$eyCode.RightShift)))
             {
                 rotation -= 90;
                 qTo = Quaternion.Euler(0, 0, rotation);
                 Debug.Log ("Camera Rotated Screen-East");
             }
             if ((Input.Get$$anonymous$$eyDown ("a")) & (Input.Get$$anonymous$$ey ($$anonymous$$eyCode.RightShift))) 
             {
                 rotation += 90;
                 qTo = Quaternion.Euler(0, 0, rotation);
                 Debug.Log ("Camera Rotated Screen-West");
             }
 
             if (Quaternion.Dot (transform.rotation, qTo) < 0) 
             {
             qTo = -qTo;
             }
 
             transform.rotation = Quaternion.RotateTowards(transform.rotation, qTo, turnSpeed * Time.deltaTime);
 
     }
avatar image
0

Answer by ATLGAN · Aug 18, 2020 at 11:41 AM

 using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
 
 public class RotateQuaNoneShortWay : MonoBehaviour
 {
     public float rotateAngle;
     public float speed;
 
     Quaternion targetRot;
 
     float targetAngle;
     float currentAngle;
 
     Quaternion firstRotation;
 
     public bool canRotate;
 
     Transform c_Transform;
 
     private void Start()
     {
         c_Transform = GetComponent<Transform>();
 
         firstRotation = c_Transform.rotation;
     }
     void Update()
     {
         if (Input.GetKeyDown(KeyCode.R))
         {
             targetAngle += rotateAngle;
 
             if (!canRotate)
             {
                 firstRotation = c_Transform.rotation;
 
                 canRotate = true;
             }
         }
         if (canRotate)
         {
             currentAngle = Mathf.Lerp(currentAngle, targetAngle, Time.deltaTime * speed);
             targetRot = Quaternion.AngleAxis(currentAngle, c_Transform.up) * firstRotation;
             c_Transform.rotation = Quaternion.Slerp(c_Transform.rotation, targetRot, Time.deltaTime * speed);
 
             if (Quaternion.Angle(c_Transform.rotation,targetRot) < 0.2f)
             {
                 c_Transform.rotation = targetRot;
                 currentAngle = 0;
                 targetAngle = 0;
 
                 canRotate = false;
             }
         }
     }
 }
 
Comment
Add comment · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

7 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

Rotate Towards doesnt work as expected 2 Answers

Mathf.Atan changes instantly even with a slow lerped value 1 Answer

Vector3.MoveTowards and Quaternion.RotateTowards 1 Answer

Quaternion.RotateTowards() immediately snaps to target rotation 1 Answer

SmoothDamp from 360 to 1 1 Answer


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges