Friday, February 15, 2013

Unity3D: Rotate Object to Face Another

I've been working on porting an HTML5 demo to Unity3D, when I stopped for more than a day just to rotate a cannon towards a point!

I have to admit that Unity3D is great, but it can turn simple tasks into complex ones for no reason. Anyways, after some time with Quaternion class and Lerp function and searching for online solutions, I decided to go back to basics. The problem with Lerp is that it forces rotation in a certain time. So the cannon will rotate 30 degrees in -say- 5 seconds, and will also rotate 60 degrees in 5 seconds, which is not logical. And to solve this I have to do some extra math.

So what did I do? I just rotated with a certain amount of degrees per second (using rotate() function) , and checked if the angle between my cannon and the target is within the allowed range to shoot (with a cross product of two vectors). That's it!

The code works as follows:
- If cannon is in attack mode, rotate towards target point with a fixed speed.
- The cannon object has a child object called nose, it is a point representing the missile start point and used for measuring the angle between the cannon-nose vector and nose-target vector.
- If the angle is within an accepted value, stop and fire.
- The assumption is that the game is in the Z-plane and the cannon rotates around the Z axis.

public float rotationSpeed = 30;
public float rotationErrorFraction = 0.01f;
bool attackMode = false;
Vector3 targetPoint;

void Update()
{
 
 if(attackMode)
 {   
  Transform nose = transform.FindChild("Nose");

  // check if cannon looks at target
  Vector3 targetDirection = (targetPoint - nose.position);
  targetDirection.z = 0;
  Vector3 cannonDirection = (nose.position - transform.position);
  cannonDirection.z = 0;
  
  targetDirection.Normalize();
  cannonDirection.Normalize();
  Vector3 cross = Vector3.Cross(cannonDirection, targetDirection);
  float crossZ = cross.z;

  // If within range, fire. Else, rotate again.
  if(crossZ < rotationErrorFraction && crossZ > -rotationErrorFraction)
  {
   // fire
   if(missile)
   {
    Instantiate(missile, nose.position, nose.rotation);
   }
   attackMode = false;
  }
  else
  {
   if(crossZ > 0)
    transform.Rotate(Vector3.forward, Time.deltaTime*rotationSpeed);
   else
    transform.Rotate(Vector3.back, Time.deltaTime*rotationSpeed);
  }
 }
}

No comments:

Post a Comment