Monday, May 6, 2013

Making a Guided Missile in libGDX with Box2D

To see an example, take a look at this video:


Or this one with overhead strikes:

Finally here is a video that shows a large number of missiles chasing another missile:


To create a guided missile like this, you have to do a few things:

  1. Have a target, that is updated every frame with its current position
  2. (Optional) set some fixed waypoints on the way to the target to alter the flight path, this creates the overhead strike you see in the video, and is one way to improve accuracy
  3. Create a "steer point" that will guide your missile towards the next waypoint/target, but corrects for velocity of the chaser and the chased
  4. Turn the body of the missile until it is facing the steer point.
  5. When we are happy with the facing of the body, we accelerate it.
  6. If you want the missile to explode, you can check distance from target, or use Box2D's contact listener
Setting the targeting and updated it's Vector2 position should be easy enough, so I won't explain it here. Let's skip to step 3.

Assuming we have a target, let's face the target
void steerToDesiredFacing() {
if (desiredFacing == null) {
return;
}
float facing = getForwardFacing();
// this contrain method keeps the angle between 180 and -180
float diff = constrainAngle180(facing - desiredFacing);
// steer within n degrees of desired facing, use this to control precision
float angularThreshold = 1;
float angularVel = body.getAngularVelocity();
// if we are turning too fast for the remaining turn distance, we slow down with this hack
// this can prevent an over-steering behavior that causes wobble
if (Math.abs(angularVel / diff) > 0.1f) {
body.setAngularVelocity(angularVel * 0.90f);
}
float torque = rotationForce;
// turn one way
if (diff >= angularThreshold) {
body.applyTorque(-torque, true);
// or turn the opposite way
} else if (diff <= -angularThreshold) {
body.applyTorque(torque, true);
// if we are within 5 degrees of desired facing
// we want to kill our turn gradually, but strong enough to stop
} else if (diff < 5) {
if (angularVel < 0.2) {
// turning slow enough to be on target
body.setAngularVelocity(0);
removeDesiredFacing();
} else {
body.setAngularVelocity(angularVel * 0.5f);
}
}
}
view raw faceTarget.java hosted with ❤ by GitHub
let's correct for velocities: 

private Vector2 getVelocityCorrectionSteerpoint(Vector2 waypoint) {
if (waypoint == null) {
return null;
}
Vector2 selfVel = body.getLinearVelocity().cpy();
// if this is not a regular waypoint, but instead a point we should chase
// (like a moving enemy target) then we want to correct for the target's velocity as well
if (isChaseTargetPosition(waypoint)) {
// add target's velocity to the waypoint, so we can predict movement
Vector2 targVel = targetEntity.body.getLinearVelocity();
waypoint.add(targVel);
}
Vector2 selfPos = body.getPosition();
// desired velocity is target's position minus our position
Vector2 desiredVelocity = waypoint.cpy().sub(selfPos);
// The correction we need to make to the waypoint is
// the difference between desire velocity and current velocity
Vector2 steeringCorrection = desiredVelocity.sub(selfVel);
// new waypoint = old waypoint + steering correction
Vector2 steeringCorrectionPoint = steeringCorrection.add(waypoint);
return steeringCorrectionPoint;
}

Now let's seek the waypoint:

void seekWaypoint(Vector2 waypoint) {
Vector2 pos = body.getPosition();
float dist = waypoint.dst(pos);
// you should set a threshold for arrival distance
// that is good enough to be considered "arrived" for your purposes
if (dist < arrivalThreshold) {
if (waypoints.contains(waypoint)) {
// if we are chasing a target, don't remove the waypoint upon reaching arrival distance
waypoints.remove(0);
waypoint = getCurrentWaypoint();
if (waypoint == null) {
return; // no more waypoints
}
} else if (waypoint.equals(chasePoint)) {
// just keep chasing!
}
}
// accelerate towards the waypoint, since it is beyond arrival distance threshold
// here is where we used the velocity correction method from above
Vector2 correctiveSteerpoint = getVelocityCorrectionSteerpoint(waypoint);
Vector2 destination = correctiveSteerpoint;
// to avoid over correction, we have a minimum distance from target for correction to occur at
// if we are under the minimum, just head towards the original waypoint
if (correctiveSteerpoint.dst(pos) < minSteeringDistance) {
destination = waypoint;
}
// we set the desired facing, so we now where to turn to face our destination
setDesiredFacing(getAngleFromAtoB(pos, destination));
// only accelerate if we are pointed at the destination
if (getSteeringDifference() < maxSteeringErrorForAcceleration) {
accelerate();
}
}

And that's about it.  Here is the code for constraining the angle to 180 and -180 degrees:
public static float constrainAngle180(float angle) {
while (angle > 180) {
angle = angle - 360;
}
while (angle < -180) {
angle = angle + 360;
}
return angle;
}
Now you need to implement acceleration and contact/explosion handling on your own.

2 comments:

  1. Can you share full of source code?It's not clear how to run it within a full project.Thank you

    ReplyDelete
  2. Hi Jon, please can you post the whole code on how to make this happen.
    From creating the missile to hitting the target.
    That will help alot and as a newbie it's hard as no one wants to help us even though everyone was a newbie themselves.
    Please Jon that will be so much and I will mention you in my future projects.
    Thank you so much in advance.
    Johnny Graham

    ReplyDelete