Hitting a moving target
Missiles are unguided and take time to arrive. Aim straight at a moving opponent and it has moved on by the time the missile gets there. Aim where it will be instead.
Why aiming straight at it misses
A basic missile flies at 140 metres a second. At 140 metres that's a second in the air, and a vehicle crossing your path at 15 metres a second has moved 15 metres by then: a clean miss. The fix is called leading the target, and it needs two things: how fast the target is moving, and how long the missile will take.
Measuring the target's speed
A contact gives the target's position (contact.x, contact.y) but not its velocity. Work it out yourself: remember where it was, and a moment later divide how far it went by the time it took. memory keeps the mark between updates.
if memory.mark_t == None or state.time - memory.mark_t > 1.5:
# No mark yet, or an old one: start again from here.
memory.mark_x = contact.x
memory.mark_y = contact.y
memory.mark_t = state.time
memory.vx = 0
memory.vy = 0
elif state.time - memory.mark_t >= 0.5:
passed = state.time - memory.mark_t
memory.vx = (contact.x - memory.mark_x) / passed
memory.vy = (contact.y - memory.mark_y) / passed
memory.mark_x = contact.x
memory.mark_y = contact.y
memory.mark_t = state.timeHalf a second between sightings smooths out the radar's rounding (a basic radar reports range to the nearest few metres). A mark more than 1.5 seconds old means the target was lost for a while, so it starts afresh rather than trusting a stale speed.
Where it will be
The missile's flight time is roughly the range divided by its speed. Move the target on by its velocity for that long, and aim at that point with bearing_to():
flight = contact.range / 140
aim = bearing_to(state.x, state.y, contact.x + memory.vx * flight, contact.y + memory.vy * flight)
missiles.aim(aim)Fire when the turret is there
The turret turns at a limited rate, so aim every update and fire only once it has come round. A few degrees of tolerance is enough at most ranges; firing from too far away wastes missiles on a target that has time to turn.
on_target = abs(angle_diff(aim, missiles.turret())) <= 4
if on_target and contact.range < 170 and missiles.ready():
missiles.fire()The whole program
from vehicle import drive, radar, missiles
def update(state, memory):
if memory.sweep == None:
memory.sweep = 0
contact = radar.contact()
if contact and state.time - contact.seen_at <= 0.2:
radar.scan(contact.bearing)
# The target's speed, from where it was half a second ago.
if memory.mark_t == None or state.time - memory.mark_t > 1.5:
memory.mark_x = contact.x
memory.mark_y = contact.y
memory.mark_t = state.time
memory.vx = 0
memory.vy = 0
elif state.time - memory.mark_t >= 0.5:
passed = state.time - memory.mark_t
memory.vx = (contact.x - memory.mark_x) / passed
memory.vy = (contact.y - memory.mark_y) / passed
memory.mark_x = contact.x
memory.mark_y = contact.y
memory.mark_t = state.time
# Aim where it will be when the missile gets there.
flight = contact.range / 140
aim = bearing_to(state.x, state.y, contact.x + memory.vx * flight, contact.y + memory.vy * flight)
missiles.aim(aim)
# Close in at an angle, which makes this vehicle harder to hit too.
course = contact.bearing + 35
turn = clamp(angle_diff(course, state.heading) / 45, -1, 1)
drive.set(throttle=1, turn=turn)
on_target = abs(angle_diff(aim, missiles.turret())) <= 4
if on_target and contact.range < 170 and missiles.ready():
missiles.fire()
else:
memory.sweep = wrap(memory.sweep + 15)
radar.scan(memory.sweep)
drive.set(throttle=0.5, turn=0.2)Approaching at an angle (contact.bearing + 35) rather than head on matters as much as the lead: a vehicle coming straight at its opponent is the easiest target there is.
Going further
- Watch the replay's timeline: a MISS against a wall usually means the lead was too large, one behind the target too small.
- A target that zigzags defeats a simple lead. Firing only when close (
contact.range) helps more than a cleverer estimate. - A tracking radar (Silver) reports the target every update without scanning: see
radar.track().