2
\$\begingroup\$

I am attempting to rotate a 3D object on its Y-axis towards a target it is looking at. I have always struggled with rotations so I would like to try to avoid using Unity's built in functions, i.e transform.LookAt() in order to get used to using rotations. Gotta master the basics first, right?

Vector3 currentOrientation = transform.rotation.eulerAngles;
Vector3 directionVector3 = transform.position - target.transform.position;
float angle = Mathf.Atan2(directionVector3.z, directionVector3.x) * Mathf.Rad2Deg;
currentOrientation.y =angle;
transform.rotation = Quaternion.Euler(currentOrientation);

However when I do run this, the character is not looking in the correct direction at all!

Here is a screen shot with the character's parameters

Character parameter's

The target object belongs in a hierarchy, where it's position in world space is (150,0,500)

Does atan2 somehow behave differently in 3D space vs 2D space? I ask because I've made 2D games where I would just use atan2(y,x) for a character's rotation.

\$\endgroup\$
  • 1
    \$\begingroup\$ Like mixing angles and direction vectors, mixing euler angles with quaternions usually ends in heartache. \$\endgroup\$ – jzx Feb 6 '15 at 15:43
  • \$\begingroup\$ You don't have to tell me, I've been trying to figure this out since yesterday and it's been a nightmare. \$\endgroup\$ – fryBender Feb 6 '15 at 16:03
2
\$\begingroup\$

Edit: Okay, with a little bit of testing I'm prepared to revise my answer.

Going from 2D to 3D, you must now consider that "Y" isn't always going to be up. When you try to treat 3D as 2D, you can actually make things a little more complicated. So I'll do it both ways and you can decide which way works better for you.

2D in 3D

As usual, we want the direction vector from our current position to the target.

var heading = target.transform.position - transform.position;

We're only interested in the planar rotation (yaw) so we'll flatten and normalize.

var heading2d = new Vector2(heading.x, heading.z).normalized;

Now the tricky bit - I'm not sure why, but when converting radians to degrees for Unity, we need to multiply the negative constant and add a quarter turn. This seems to be where everything went wrong.

var angle = Mathf.Atan2(heading2d.y, heading2d.x) * -Mathf.Rad2Deg + 90;

Finally, apply the rotation according to the axis:

transform.rotation = Quaternion.AngleAxis(angle, Vector3.up);

which is equivalent to (probably, Unity might have platform specific versions)

Quaternion AxisAngle(float angle, Vector3 axis)
{
    var sinA = Mathf.Sin(angle *0.5f);
    var cosA = Mathf.Cos(angle*0.5f);
    return new Quaternion(axis.x * sinA,
                          axis.y * sinA,
                          axis.z * sinA,
                          cosA);
}
\$\endgroup\$
  • \$\begingroup\$ When you say "Now you can use the normal 2D "angle between vectors", do you mean the arc tangent between two points, in this case new heading.z and newheading.x? \$\endgroup\$ – fryBender Feb 6 '15 at 17:23
  • \$\begingroup\$ Did some testing myself to check. Looks like you were sortof on the right track, but there's a gotcha in Unity's degree/radian conversion. \$\endgroup\$ – jzx Feb 6 '15 at 18:11
  • \$\begingroup\$ Okay, I think I'm happy with this now, haha. \$\endgroup\$ – jzx Feb 6 '15 at 18:45
  • \$\begingroup\$ I cannot thank you enough. I'm still trying to reach that fabled 15 reps so I can upvote you but I hope you know how much I appreciate this. Also, I wanted to add I was able to fix this part var angle = Mathf.Atan2(heading2d.y, heading2d.x) * -Mathf.Rad2Deg + 90; by changing it to var angle = Mathf.Atan2(heading2d.x, heading2d.y) * Mathf.Rad2Deg ; and the character is looking directly at the current target. \$\endgroup\$ – fryBender Feb 6 '15 at 19:30
0
\$\begingroup\$

I come from XNA, so I've had to psuedocode in places. Here is the same math in a different shape. The math was expanded for clarity and can be optimized.

Vector3 directionVector = target.transform.position - transform.position;
directionVector.Y = 0;
directionVector = Vector3.Normalize(directionVector);

//Need to flatten transform.Forward as well, if object can pitch
float directionDot = Vector3.Dot(transform.Forward, directionVector);
//directionDot ranges from -1 (opposite) to +1 (same)
directionDot /= -1; //now 1 (opposite) to -1 (same)
directionDot += 1; //now 2 (opposite) to 0 (same)
directionDot /= 2; //now 1 (opposite) to 0 (same)
directionDot *= MathHelper.ToRadians(180); //now Pi (opposite) to 0 (same)

bool clockWise = (Vector3.Dot(transform.Left, directionVector) > 0);
// >0 means target position is more like transform.Left
//<=0 means target position is more like transform.Right
if (!clockWise) directionDot *= -1;

if (Math.Abs(directionDot) > 0.00) //"Close-enough-for-now" factor
{
  //We need to rotate by directionDot instantly, or by storing it and lerping in Update()
  transform *= Matrix.CreateRotationY(directionDot);
}
\$\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.