Very basic rotation BUG. transform.up = transform.up sets Y rotation to 0.
Okay I've nailed it all down to an issue that looks like a bug. To reproduce it, simply create any 3D objects eg. a cube in an empty project, set its Y rotation to anything else than 0, add a rigidbody and a new script. In the script's Start (or Update) method, simply write:
transform.up = transform.up;
Obviously, this will change nothing, right? Wrong. If you view the cube falling from above, you'll see that its Y axis gets reset to 0 degrees as soon as you start Play Mode.
This issue is present for all axes. For example if I have an object traveling in direction Z, with a Z rotation different from 0, that Z rotation gets zeroed on startup if my scripts Update has:
transform.forward = transform.forward;
This somehow then evolves into a more general problem, for example I can't keep my forward moving object's original Z angle, it gets zeroed just like above, as soon as I set the object to face the direction of its velocity with
transform.forward = transform.GetComponent<Rigidbody> ().velocity;
In my game, as the object that should at certain moments start to follow the direction of its own velocity is the parent to the first person camera, this means you'll see the camera view angle making instant rotations which looks awful.
Anyone knows if this is indeed a bug I should report or maybe there's something I'm missing that explains it?
Any workaround?
Answer by Bunny83 · May 25, 2016 at 10:15 PM
This is not a bug. I was expecting someone bringing this up sooner or later ^^. The properties "up", "right" and "forward" return the objects local space axis. Unity also implements a setter for those properties. Of course when you set them Unity will create a rotation so that the given axis points along your given vector. However a single vector is not enough information to define a rotation in 3d.
Keep in mind that setting those properties do not perform some sort of "rotate towards" but it simply sets the absolute rotation. Imagine "forward" currently points to (0,0,1) so the object is not rotated at all. If you assign this vector (0,0,-1) to forward, what rotation do you expect? Did you mean to rotate around the y axis so up is still up? Or did you mean to rotate around the x axis so up becomes "-up" and the x axis remains? Unity could have implemented them as "choose a rotation that takes the shortest route from the current rotation", but even that wouldn't work in that example since both cases have the same distance.
That's why Quaternion.LookRotation actually takes two vectors as input. You can omit the second in which case it defaults to Vector3.up
. This second "up vector" defines the rotation around the first vector. So by default when you use an arbitrary forward vector, the resulting rotation will be rotated so the objects up vector is as close as possible to the given up vector. That's also the reason why then looking straight down and rotate even further (over 90°) the view will flip 180°. So it's not possible to get the view upside down without providing a proper up vector to LookRotation.
Of course when you assign something to those properties you can't specify an additional reference vector so Unity always uses the default orientation as reference which is
up = Vector3.up
forward = Vector3.forward
right = Vector3.right
Unity only provides a LookRotation method for the z axis. However you can use it for any other axis with a bit of logic. Say you want to set the up vector to point along a certain vector (targetUp). You can simply use:
transform.rotation = Quaternion.LookRotation(targetUp, -transform.forward);
This will make your object to look straight up along your desired up direction. Now you just need to rotate 90 downwards to get your final rotation. For this you need to rotate around the right axis 90°
transform.Rotate(Vector3.right, 90f);
So those two lines will be the same as setting the up vector to a certain vector but it tries to keep the old forward axis.
If you want to set a new forward and want to keep the last up vector you would simply do:
Vector3 targetDir = transform.GetComponent<Rigidbody> ().velocity;
transform.rotation = Quaternion.LookRotation(targetDir, transform.up);
Thank you Bunny83, I'm new to the Unity forum but I swear it's the first time in my life that someone actually answered (and solved) a question I've posted on any Q&A forums, maybe my questions are just weird lol. I was about to press the Send bug report button when I saw your answer. I was like that can't be normal that x=x makes any difference but hell yeah there's an explanation! Funny thing I'd tried almost this exact line before but I wrote Quaternion.LookRotation(targetDir, Vector3.up) which had the same issue so I gave up on it. Well I guess that's the benefit when you understand all these quaternions inside out! Thanks again, and especially for the detailed explanation, a deep insight into Unity's $$anonymous$$d :)
Just FYI, it's not a Unity thing -- just standard geometry and program$$anonymous$$g. It's also a commonish Q here. Googling "Unity set transform.up" gives a few older questions and answers.
You're right @Owen Reynolds I've found something similar here although it only reports the issue in a specific context so the answer there isn't as generally explained as that of bunny83: http://answers.unity3d.com/questions/138173/cant-understand-why-object-rotates-when-setting-tr.html
Naah, this isn't just standard geometry and program$$anonymous$$g, Unity has its own way of handling it as @Bunny83 points it out. They could make the transform.up.Set method require a 2nd rotation parameter as well, for instance that avoids such confusions, maybe even disable the setting of up forward and right by direct assignment (and allowing only by a 2 param setter method) considering the uncertainty introduced by assigning a direction to it without specifying a rotation. There could be other, probably better ways to handle it by program$$anonymous$$g standards. And things like transform.up = transform.up sets the Y rotation are just contraintuitive, could be taken care of by an if statement or a warning.
In fact the most intuitive way to do it would be like this: when I use transform.up = targetDir, ins$$anonymous$$d of setting Y rotation to default, just automatically assign whatever rotation leaves Y unchanged. User hardly ever wants the way it is now, he just wants to set his object's up direction and preserve its local Y rotation. Or set its forward and preserve the local Z rotation. Or set its right dir and preserve local X rotation.
Thank you for your detailed and very easy to follow explanation, you rock!