ID:2079409
 
(See the best response by Lummox JR.)
Code:
proc/WeakHoming(obj/projectile,mob/target)
projectile.destination = target
//save the location of the target, and head for it
//if the target moves in the next few ticks, it will remain headed for that location
var/x = 8
while(p&&x)
x--
step_towards(projectile,projectile.destination)
sleep(0.35)
//after 8 loops, it stops seeking that location, and heads in a straight line
walk(projectile,projectile.dir)


Problem description:

So this function works to sent a projectile towards the target, and ceases homing after 8*0.35 ticks, and then goes in a straight line from there on out.

The goal was to have a projectile aimed at a target, but allow the target the opportunity to use skill to evade. I figured the best way to achieve this was to do the above. But I can't get the movement to be 'natural', the above code can miss stationary targets if the step_x/y are unfavourable, and the distance is far enough.

Other attempts I've made end up with the projectile being too precise, or other undesirable results.

There must be a better way to have the projectile vaugely find its way to the target? Something like, perfect homing for 30% of the distance -> homing for where the target is now standing (in case theyve moved since you created it) -> once it reaches that destination, it will travel in a straight line given the angle it is currently travelling based on its trajectory from the 30% distance mark...


help?
Best response
If you're using step_x/y pixel movement, then I would suggest this approach:

obj/projectile
/*
This projectile has no acceleration, except to turn.
turn_speed is used when homing, and is defined as the curve length
from the old velocity vector to a new one with the same speed.
I.e., your max turn angle in radians is turn_speed / speed.
*/

var/vx,vy // velocity
var/turn_speed = 0
var/atom/target

proc/Step()
// step_x/y are integers, and this does not cover fractional movement
var/d = 0
var/ax=abs(vx), ay=abs(vy)
if(ay < ax*2) d = (vx>0) ? EAST : WEST
if(ax < ay*2) d = (vy>0) ? NORTH : SOUTH
Move(loc,d,step_x+vx,step_y+vy)

proc/Homing()
if(!target || (!isturf(target) && !target.loc)) return
if(turn_speed <= 0) return
// these are macros people like to define for pixel games, not covered here
var/dx = CENTER_X(target) - CENTER_X(src)
var/dy = CENTER_Y(target) - CENTER_Y(src)
// get speed and distance to target; use at last 0.001 to avoid division by 0
var/speed = max(0.001, sqrt(vx*vx+vy*vy))
var/dist = max(0.001, sqrt(dx*dx+dy*dy))
// find the delta angle between the current vector and the one we want
// atan2 is covered in another post
var/angle = atan2(vx,vy)
var/d_angle = atan2(dx,dy) - angle
if(d_angle > 180) d_angle -= 360
else if(d_angle < -180) d_angle += 360
// PI should be a macro too
var/max_angle = (turn_speed / speed) * (180 / PI)
// limit d_angle to ħmax_angle
if(d_angle < 0) d_angle = max(d_angle, -max_angle)
else d_angle = min(d_angle, max_angle)
angle += d_angle
vx = speed * cos(angle)
vy = speed * sin(angle)

You have a few options for your home-and-forget strategy. One is to give it so many ticks, like you said. Another is to reduce turn_speed gradually until it reaches 0, so homing is strongest in the beginning but gets weaker.

You can take another approach that uses acceleration, which is kind of cooler. In this case your projectile's speed isn't fixed.

obj/projectile
var/vx,vy // velocity
var/accel = 0 // acceleration available on each tick
var/atom/target

...

proc/Homing()
if(!target || (!isturf(target) && !target.loc)) return
if(accel <= 0) return
// these are macros people like to define for pixel games, not covered here
var/dx = CENTER_X(target) - CENTER_X(src)
var/dy = CENTER_Y(target) - CENTER_Y(src)
// get speed and distance to target; use at least 0.001 to avoid division by 0
var/speed = max(0.001, sqrt(vx*vx+dy*dy))
var/dist = max(0.001, sqrt(dx*dx+dy*dy))

// Aim for d/|d| * min(|v|+a, |d|)
var/px,py,p
p = min((speed+accel) / dist, 1)
// find the desired acceleration vector to get to that point
px = dx * p - vx
py = dy * p - vy
// now change it so |p| <= a
p = accel / max(1, sqrt(px*px+py*py))
vx += px * p
vy += py * p