ID:1792060
 
(See the best response by Lummox JR.)
Hello,

I'm having an issue with my projectile traveling accurately toward the end destination. Take a look at this short video, you'll see the problem at 0:10.

Here is the straight movement setup for projectiles:
#define abs_x(a) (a.x * TILE_SIZE) + (ismovable(a) ? (a:step_x) : (0))
#define abs_y(a) (a.y * TILE_SIZE) + (ismovable(a) ? (a:step_y) : (0))

    proc/straight_travel(atom/end)
// end is the atom the projectile should be traveling toward.

var/delta_x = abs_x(end) - abs_x(src)
var/delta_y = abs_y(end) - abs_y(src)
var/distance = sqrt(delta_x * delta_x + delta_y * delta_y)

var/move_speed = step_size
vel_x = (delta_x / distance) * move_speed
vel_y = (delta_y / distance) * move_speed

while(src)
sleep(tick_speed)
var/m = Move(loc, dir,
step_x + vel_x,
step_y + vel_y)
if(!m)
break

dispose()


I'm looking to be lead in the right direction, but any help is appreciated.

I have a strong feeling it has to do with not taking the projectile's size into account, because the larger the projectile, the easier it is for it to miss. I'm not too experienced making projectile systems though, and as such I'm looking to learn any and everything about the topic. Thanks.
#define center_x(a) (a.x * TILE_WIDTH) + (ismovable(a) ? (a:step_x + a:bound_x + round(a:bound_width/2)) : (round(TILE_WIDTH/2))
#define center_y(a) (a.y * TILE_HEIGHT) + (ismovable(a) ? (a:step_y + a:bound_y + round(a:bound_height/2)) : (round(TILE_HEIGHT/2))


Try using mutual centers rather than the bottom-left corner of both.
In response to Ter13
Thanks for the response.

Unfortunately that did not work. You can see how it affected the system here.
Best response
If you want absolute accuracy you'll probably also have to start taking fractions into account. Each time I'd calculate this way:

var/dx = vel_x + rem_x
var/dy = vel_y = rem_y
rem_x = dx - round(dx,1)
rem_y = dy - round(dy,1)
if(!Move(loc, dir, step_x+round(dx,1), step_y+round(dy,1)))
break

This requires you have rem_x/y vars for the projectile, set to 0 initially. They represent the remainder of movement.
In response to Lummox JR
I don't think I'm following you. This is my attempt at implementing your advice:

    proc/straight_travel(atom/end)
// end is the atom the projectile should be traveling toward.

var/delta_x = center_x(end) - center_x(src)
var/delta_y = center_y(end) - center_y(src)
var/distance = sqrt(delta_x * delta_x + delta_y * delta_y)

var/move_speed = step_size
vel_x = (delta_x / distance) * move_speed
vel_y = (delta_y / distance) * move_speed

rem_x = vel_x - round(vel_x, 1)
rem_y = vel_y - round(vel_y, 1)

world.log << "vel x = [vel_x]"
world.log << "vel y = [vel_y]"

while(src)
sleep(tick_speed)

var/dx = vel_x + rem_x
var/dy = vel_y + rem_y
rem_x = dx - round(dx, 1)
rem_y = dy - round(dy, 1)

var/m = Move(loc, dir,
step_x + round(dx, 1),
step_y + round(dy, 1),
)
if(!m)
break

dispose()


With the above changes, although still not accurate, I can see it's close to getting there.

If you could further explain the process, I could probably finish the rest of this up.

All the help is appreciated.
Ter's center definitions need an extra set of parentheses surrounding them.
In response to Kaiochao
I fixed that before I used it, but yeah.
rem_x/y should be 0 before the loop. The name "rem" is for remainder, and is basically to track the fractional ±0.5 pixels left over after rounding.

The way this is calculated in the loop, rem_x/y accumulates slowly until it makes an adjustment for the rounding error.

For instance, if vel is 9.1,0.7:

Tick 1: rem = 0,0; d = 9.1,0.7; step = 9,1; rem (new) = 0.1,-0.3
Tick 2: rem = 0.1,-0.3; d = 9.2,0.4; step = 9,0; rem (new) = 0.2,0.4
Tick 3: rem = 0.2,0.4; d=9.3,1.1; step = 9,1; rem (new) = 0.3,0.1
Tick 4: rem = 0.3,0.1; d=9.4,0.8; step = 9,1; rem (new) = 0.4,-0.2
Tick 5: rem = 0.4,-0.2; d=9.5,0.5; step = 10,1; rem (new) = -0.5,-0.5

After 5 ticks, the total step change has been 46,4, which is fairly close to 45.5,3.5. The change will be magnified over more ticks. At the end of tick 10, expect 91,7.

Tick 6: rem = -0.5,-0.5; d=8.6,0.2; step = 9,0; rem (new) = -0.4,0.2
Tick 7: rem = -0.4,0.2; d=8.7,0.9; step = 9,1; rem (new) = -0.3,-0.1
Tick 8: rem = -0.3,-0.1; d=8.8,0.6; step = 9,1; rem (new) = -0.2,-0.4
Tick 9: rem = -0.2,-0.4; d=8.9,0.3; step = 9,0; rem (new) = -0.1,0.3
Tick 10: rem = -0.1,0.3; d=9,1; step = 9,1; rem (new) = 0,0

And now it's 91,7 exactly after 10 ticks. Had this simply stayed rounded off without accounting for the decimals, each move would be 9,1 and it would have moved 90,10. Over a larger distance the error is magnified.
In response to Lummox JR
Ah, now I understand. Thanks for the explanation.

I'll continue experimenting for now until I get the desired results. I appreciate all the help.
I've spent a few hours now trying to do this. Here's where I got.

Either I have something 100% accurate but will sometimes jump great distances and other times small distances.

Or.. I have something that approximates but wobbles side to side as it tries to stay on track.
I figured out your problem.

You need to take a look at what this compiles down to in order to understand what's going wrong.

The problem is here:

var/delta_x = abs_x(end) - abs_x(src)
var/delta_y = abs_y(end) - abs_y(src)


abs_x and abs_y are preprocessor macros. They will be replaced by whatever follows their definition. Let's do what the compiler does and expand these two lines:

var/delta_x = (end.x * 32) + (ismovable(end) ? (end:step_x) : (0)) - (src.x * 32) + (ismovable(src) ? (src:step_x) : (0))
var/delta_y = (end.y * 32) + (ismovable(end) ? (end:step_y) : (0)) - (src.y * 32) + (ismovable(src) ? (src:step_y) : (0))


See the problem yet? If not, follow along. Let's assume end is at (2:16,2:16), and src is at (1:16,1:16). This should give us a value of (TILE_SIZE,TILE_SIZE) for the delta x and y, right? Well, let's see what actually happens:

Evaluate:
var/delta_x = (2 * 32) + (1 ? (16) : (0)) - (1 * 32) + (1 ? (16) : (0))
var/delta_y = (2 * 32) + (1 ? (16) : (0)) - (1 * 32) + (1 ? (16) : (0))


parentheses go first:
var/delta_x = 64 + 16 - 32 + 16
var/delta_y = 64 + 16 - 32 + 16


Addition and subtraction are all that's left, so solve left to right:

var/delta_x = 80 - 32 + 16
var/delta_x = 48 + 16
var/delta_x = 64


And there you have it, that's the problem with your projectiles. What's happening, is you are missing parentheses within the abs_x and abs_y definitions, which would fix this problem:

#define abs_x(a) (a.x * TILE_SIZE + (ismovable(a) ? a:step_x : 0))
#define abs_y(a) (a.y * TILE_SIZE + (ismovable(a) ? a:step_y : 0))


There you go. That should be a portion of your inaccuracy solved.
In response to Ter13
Tested that using his proc, worked like a charm. Seems to have accuracy down to + or - about 16 pixels.
In response to Ter13
You know... the crazy thing is, I spent a great deal of time staring at those definitions wondering if there was something missing. Something was telling me they were the source of my troubles since I hadn't had this issue before with similar systems (I never used #defines for those until this time around - I've come to love them), but I couldn't quite put my finger on it.

Between both you and Lummox's solutions, everything seems to be working beautifully now. I can't begin to explain how much of a lifesaver you are though. A weight has been lifted off my chest (now all I have to worry about are these redundant school assignments, ugh)!
The funny thing is, I was implementing a similar system for MDC's SDBO at the time you were writing this.

We both made the same mistake. Although, I was using atan2 to determine the direction of the projectile's travel and simply adding the step_x/step_y every frame, so our implementations were a little different.
Darn, if only I saw missing parentheses.
Oh, hey. Just noticed your post there, Kaio. Good eye.