2D LookAt Function

I've noticed a few complaints in Unity forums that the 2D engine in Unity doesn't have its own LookAt function.  If you just use Transform.LookAt() in the usual way, an object seems to disappear.  It's Z-axis turns to face the requested direction, as usual, but in a 2D world this will mean that the Z-axis is now at a right angle to the camera, and since the sprite is flat and intended to have its Z-axis facing the user, it will be invisible.

There are several solutions to this problem, but I'll just explain two.  Firstly, we should note that Transform.LookAt() takes two arguments, the 'target' vector, and an optional 'worldUp' vector.  For a sprite to keep pointing forward, 'target' must just point along the Z-axis, or Vector3.forward.  But 'worldUp' determines which way the sprite's Y-axis should point, so we can still use this to orient our sprite.  Obviously if you want your Y-axis point along some vector called 'direction', you can just use the line:

transform.LookAt(Vector3.forward,direction);

It's slightly more complicated if it's the X-axis of your sprite you want to point along 'direction', but either of the following lines will work:

transform.LookAt(Vector3.forward,new Vector3(-direction.y,direction.x,0f));
transform.LookAt(Vector3.forward,Vector3.Cross(Vector3.forward,direction));

(N.B. There's a similar function in the Quaternion class which works as well, but don't use Quaternion.FromToRotation(Vector3.right,direction);  This will seem like its working at first, but occasionally your direction rotation will be (-1,0,0), and the rotation will be 180°, but it won't be around the Z-axis!  While we're on the subject of things not to do, avoid using Atan2.  Indeed, avoid using angles at all if you can - trigonometric functions are all pretty slow, and there's rarely any reason to use trigonometric functions in a game.)

This works, but there's another way which is, if nothing else, interesting.  Quaternions have to work out the axis you're rotating around, and since we know the axis is always the Z-axis, the calculations involved become a bit simpler if you're only working in 2D.  It's hard to imagine a scenario where the amount of efficiency involved here could be significant, but nevertheless, somebody might want to know about it.

A quaternion stores the cosine of half the angle we're rotating by (this is called w in the Unity Quaternion class), and a unit vector of the axis of rotation multiplied by the sine of half the angle we're rotating by.  Since the unit vector of our axis is (0,0,1), two of the values will always be 0, and the other two will be the sine and cosine of half our angle.  If you're familiar with the unit circle, you'll know that means that all we need is a normalised vector in the correct direction, then the x component will be our cosine and y component will be our sine.

Since angles start from 0 pointing right along the x-axis, our half-angle-vector must be pointing to an angle halfway between that and our desired direction, or halfway between the vector (1,0) and our normalised direction vector (x,y).  One way to get such a vector is to take the average - we can add them up (but we're only bothered about direction, not magnitude, so we won't bother dividing by 2) giving (1+x,y).  Another is to take the vector from our target direction back to the beginning (1-x,-y) and rotate it by 90, giving (y,1-x).  The trouble with either of these is that for some normalised input vectors, they would give a zero vector.  But by doubling one of them and adding it to the other, we get another vector, still in a suitable direction (just with a different magnitude) - (2+2x+y,1-x+2y).  By normalising this, we have the required values for our quaternion.  The code will look something like this:

Vector2 halfangle, toward;
Quaternion q;
toward = (aim.position - transform.position);
toward.Normalize();
halfangle = new Vector2(2f+(2f*toward.x)+toward.y,1f-toward.x+(2f*toward.y));
halfangle.Normalize();
Quaternion q = new Quaternion(0f,0f,halfangle.y,halfangle.x);
transform.localRotation = q;

I'm pretty sure that would actually be less efficient than the simple version unless it was in a loop that avoided allocating new structures every time.  But the principle is there, and if you ever have some situation that requires you to work out a normalised half-angle vector anyway, or even if your target vector is already normalised for some reason, then this way might become noticeably more efficient.

Although the real reason I posted this latter half was because I'd figured it out, and couldn't help sharing it somewhere, natch.

This entry was posted in Unity and tagged , , , , , . Bookmark the permalink.

Comments are closed.