Hi,
I'm working on my "Who dares" game (v0.13.3 for now) and now fight with bullets movements. Player and foes can move and shoot on 8 directions. To prevent all those sprites going faster on diagonals I just multiply their step value by 0.75, an approximation on cos(45) (or sqrt(2)/2, you choose) to get back to integers every 4 frames. Anyway, those sprites move look jagged on diagonals. I first found a way to fix this by copying Y decimals to X coord, but it's rather complicated and not the best way I think. Plus, it also messes with collisions.
So I went to basics: if movement is jagged because of float coords, how is drawn a float positioned pixel? I made the little cart here to experiment this. To make it short, a float pixel will be drawn at a floored, and not rounded, position. So pset(9.75,9.25) will put a pixel at 9,9. Fine with that, but further experimentations show bugs, as shown here.
The cart shows 6 different increment, named px, each one its own line. Every nth frames (fq value set on line 11) a value named x, initiated with 0, has px added, then printed at x position on light green if it's an integer, ie if x==flr(x), dark green otherwise.
Things go well when px=0.25, 0.5, 0.75 and 1: they turn light green when no decimal, the same moment they move 1 pixel right. Can't ask for more.
But things are not too good on first two lines (px=0.1 and 0.2):
x keeps being drawn with dark green, that means the x==flr(x) is always false, even when the value shown have no decimals.
When those values drop their decimal, they should move 1 pixel right. But they don't, it happens on the next increment. As if their no-decimal value is in fact lower than the integer.
While those steps only has 1 decimal, quickly their added value have decimals like 0.999, 0.998, etc. Quicker with px=0.1 (0-1, then 5 and >), later with 0.2 (33 and >)
My jagged movements probably have little to do with this bug and is more about using 0.75 factors and having X and Y that don't have their integer value changing at the same time. But well, I just discovered this and wanted to share.
Anyway if anyone knows how to simply have nice diagonally moving float positioned sprites, I'd really be happy to hear!
All numbers in PICO-8 must be an integer multiple of the smallest representable number, 2^-16. The problem is that "0.1" and "0.2" can't be represented exactly as multiples of 2^-16.
Choppy diagonal motion is probably happening when your sprite ends up at a fractional position -- let's say (0.75, 0). The sprite floors its coordinates and draws at (0, 0). When you move it diagonally to (1.5, 0.75), the on-screen position becomes (1, 0). The next frame it's (2.25, 1.5), then (3, 2.25), then (3.75, 3) -- that's (2, 1), (3, 2), (3, 3). The x and y coordinates moving out of sync causes choppy motion.
To fix it, you could try rounding the x and y coordinates any time you're not moving. Then you always start from a whole number, and the rounding cutoffs are at the same place for both coordinates.
Thank you ianh, you're right about the float, the resolution of 2^-16 is the point. Good to know. I'm still thinking too much in base 10 :)
Concerning choppy diagonal motion, I think rounding a bullet start point won't solve the problem. The problem mainly shows on the NE/SW diagonal: with a start point at (30, 50), the next step towards NE with 0.75 increment will be (30.75, 49.25). The bullet will show at (30, 49) which is already a problem.
I started making some drawings and found a somehow simple fix by incrementing only x, and using x to get y. Let's say a bullet starts at x0=30, y0=50:
- if the bullet goes NW, x & y both decrease. For SE, they both increase. So, as (y-x) keeps being 20 (ie y0-x0) all way long, bullet y will equal flr(x)+(y0-x0)
- if it goes NE, then x increases while y decreases. For SW, it's the opposite. So in both ways x+y=80 (ie y0+x0) and bullet y will be (y0+x0)-flr(x)
This way, only x manages factors (step, bult_speed, game_speed), which maybe goes a bit faster. But it's ok only as long as bullets keep going straight, and on strict 45° angles. An isometric game would need an adaptation (by having a 0.5 factor on y, I guess). And I had a bug with bouncing bullets, as I forgot resetting x0 and y0 each time the bullet bounces.
Thanks for sharing this demo! I was looking for specific details about the interaction of graphical functions with fixed-point values, and this gave me the exact information I was looking for!
[Please log in to post a comment]