S.W.A.P. Missile: Perfect Orbits

Recently while I was tweaking the Swap missiles tracking in my upcoming game S.W.A.P. I encounter a funny little Bug where it would “orbit” its target. I recorded a short video which you can watch below:

I thought I’d briefly share the code that produced this result:

// ... Target Acquisition Ocures up here ...

void RotateTowards(Vector3 target)
{
	Vector3 dirToAvatar = (transform.position - target).normalized;

	// work out angle between us and the target.
	float angle = Vector3.Angle(dirToAvatar, transform.forward);
	float rotAmount = Mathf.Clamp01( maxTrunAmount / angle );	// this prevents us from rotating more them the maximum allowed!!
	Quaternion temp = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation(-dirToAvatar), rotAmount);
	rigidbody.MoveRotation(temp);
}

So the goal of this function is to rotate the missile so it faces the target. However the amount it can rotate is capped by maxTrunAmount. The problem was that if you got the distance and angle right the missile would need to rotate more then maxTrunAmount would allow it to. If you got this just right this would result in stable “orbits” where the missile would just keep circling the target.

The final code I ended up using looked like this:

void RotateTowards(Vector3 target)
{
	Vector3 dirToAvatar = transform.position - target;
	float distToAvatarSqr = dirToAvatar.sqrMagnitude;
	dirToAvatar.Normalize();

	// Work out how far we can rotate based on distance to target:
	float t = 1 - (distToAvatarSqr / (trackingDistanceSqr - 2));
	t = Mathf.Clamp01( missileTurningCurve.Evaluate(t) );

	Quaternion newRot = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation(-dirToAvatar), t);
	rigidbody.MoveRotation(newRot);
}

Needless to say this actually works quite differently. This time, in addition to tracking the target we also wanted to adjust how far the missile could turn, when it was right at the end of its tracking range, it would turn only a small amount. As it gets closer it can turn faster, When it gets really close it will got straight for the target.

On line 8 we work out how close we are to the target as a percentage (0 being far away, 1 being at the target). Note that we take 2 away from our max tracking distance, this is to take into account that the targets position is in the centre of the avatar and for this number to ever be 1 we must get to a distance of 0. Because of the Targets collider (and the missile’s too) this is impossible, so we have minus 2.

On line 9 we use a curve (which I have expose in the inspector) to transform this range percentage into a rotation percentage. We use the curve because we wanted to have the missile turn in gradually at first but much more sharply as it got closer. The curve looks like this:

SwapMissileTurnCurve
Swap Missile Turn Curve

By eliminating the artificial maxTrunAmount we have allowed the swap missile to (eventually) turn the full required amount to actually hit and eliminated the “orbits”. (of course if I couldn’t fix it we would have ended up with swap Sharks instead, which could have been fun :D).

Actually the single best thing to come out of this was my learning about the AnimationCurve.Evaluate() function. It makes it trivial to expose a curve in the inspector which can be edited by an artist and then use it to control a lerp between two values (colour, position, rotation , whatever). This actually allows me solve one of the most frequent questions I get from an artist when I setup a mechanic that involve a lerp, i.e Can you change it so…? Now I can just tell them: Do it yourself! and that always make me feel good.

Leave a comment