12
\$\begingroup\$

Pixels are either on or off. The minimum amount you can move a sprite is a single pixel. So how you make the sprite move slower than 1 pixel per frame?

The way I did it was to add the speed to a variable and test if it had reached 1 (or -1). If it did, then I would move the sprite and reset the variable to 0, like so:

update(dt):
    temp_dx += speed * dt
    temp_dy += speed * dt

    if (temp_dx > 1)
        move sprite
        reset temp_dx to 0
    if (tempy_dy > 1)
        move sprite
        reset temp_dy to 0

I disliked this approach because it feels silly and the sprite's movement looks very jerky. So in what way would you implement sub-pixel movement?

\$\endgroup\$
2
  • \$\begingroup\$ The only thing you could do is bilinear filtering. \$\endgroup\$
    – AturSams
    Nov 16, 2014 at 21:56
  • \$\begingroup\$ If you're moving less than 1 px per frame, how can it look jerky? \$\endgroup\$
    – user5196
    Nov 17, 2014 at 13:43

6 Answers 6

17
\$\begingroup\$

There's a number of options:

  1. Do as you do. You've already said it doesn't look smooth. There are some flaws with your current method though. For x, you could use the following:

    tempx += speed * dt
    
    while (tempx > 0.5)
      move sprite to x+1
      tempx -= 1
    while (tempx < -0.5)
      move sprite to x-1
      tempx += 1
    

    this should be better. I've switched the if statments to use 0.5, as when you're passed 0.5 you're closer to the next value than the previous. I've used while loops to allow for movement of more than 1 pixel per time step (it's not the best way to do it, but it makes for compact and readable code). For slow moving objects it'll still be jumpy though, as we haven't dealt with the fundamental issue of pixel alignment.

  2. Have multiple graphics for your sprite and use a different one depending on the subpixel offset. To do subpixel smoothing only in x, for example, you could create a graphic for your sprite at x+0.5, and if the position is between x+0.25 and x+0.75, use this sprite instead of your original. If you want finer positioning, just create more subpixel graphics. If you do this in x and y your number of renderings can quickly balloon, as the number scales with the square of the number of intervals: a spacing of 0.5 would require 4 renderings, 0.25 would require 16.

  3. Supersample. This is a lazy (and potentially very expensive) way of creating a image with subpixel resolution. Essentally, double (or more) the resolution at which you render your scene, then scale it down at runtime. I would recommend care here, as performance can drop quickly. A less agressive way of doing this would just be to supersample your sprite, and downscale it at runtime.

  4. As suggested by Zehelvion, it may be that the platform you are using already supports this. If you are allowed to specify non-integer x and y co-ordinates, there may be options to change the texture filtering. Nearest-neighbour is often the default, and this will cause "jerky" movement. Other filtering (linear/cubic) would result in a much smoother effect.

The choice between 2. 3. and 4. depends on quite how your graphics are implemented. A bitmap style of graphics suits pre-rendering much better, whereas a vector style may suit sprite supersampling. If your system supports it, 4. may well be the way to go.

\$\endgroup\$
6
  • \$\begingroup\$ Why replace the threshold by 0.5 in opt 1? I also don't get why you moved the statements into a while loop. The update functions gets called once per tick. \$\endgroup\$
    – bzrr
    Nov 16, 2014 at 21:13
  • 1
    \$\begingroup\$ Good question - I've clarified the answer based on your comment. \$\endgroup\$
    – Jez
    Nov 16, 2014 at 21:42
  • \$\begingroup\$ Supersampling may be "lazy", but in many cases the performance cost of 2x or even 4x oversampling would not be a particular problem, especially if it allowed other aspects of rendering to be simplified. \$\endgroup\$
    – supercat
    Nov 17, 2014 at 0:33
  • \$\begingroup\$ In big-budget games, I typically see 3 as an option listed in the graphics settings as anti-aliasing. \$\endgroup\$
    – Kevin
    Nov 17, 2014 at 0:41
  • 1
    \$\begingroup\$ @Kevin: You actually probably don't. Most games use multisampling, which is somewhat different. Other variants are also commonly used, with e.g. varying coverage as well as depth samples. Supersampling is almost never done due to the cost of fragment shader execution. \$\endgroup\$
    – geometrian
    Nov 17, 2014 at 2:37
14
\$\begingroup\$

One way in which many old-skool games solved (or hid) this problem was to animate the sprite.

That is, if your sprite was going to move less than one pixel per frame (or, especially, if the pixels/frame ratio was going to be something odd like 2 pixels in 3 frames), you could hide the jerkiness by making an n frame animation loop that, over those n frames, ended up moving the sprite by some k < n pixels.

The point is that, as long as the sprite always moves in some way on each frame, there's never going to be any single frame where the whole sprite would suddenly "jerk" forward.


I couldn't find an actual sprite from an old video game to illustrate this (although I think e.g. some of the digging animations from Lemmings were like that), but it turns out that the "glider" pattern from Conway's Game of Life makes a very nice illustration:

enter image description here
Animation by Kieff / Wikimedia Commons, used under the CC-By-SA 3.0 license.

Here, the little blocks of black pixels crawling crawling down and right are the gliders. If you look carefully, you'll notice that they take four animation frames to crawl one pixel diagonally, but since they move in some way on each of those frames, the movement doesn't look that jerky (well, at least not any more jerky than anything looks at that frame rate, anyway).

\$\endgroup\$
2
  • 2
    \$\begingroup\$ This is an excellent approach if the velocity is fixed, or if it is less than 1/2 pixel per frame, or if the animation is "large" enough and fast enough to obscure the variations in motion. \$\endgroup\$
    – supercat
    Nov 18, 2014 at 0:48
  • 1
    \$\begingroup\$ This is a good approach, although if a camera has to follow the sprite you'll still run into issues because it won't move quite smoothly. \$\endgroup\$
    – Elden Abob
    Dec 10, 2014 at 21:18
6
\$\begingroup\$

The sprite's position should be kept as a floating point quantity, and rounded to an integer only just before display.

You can also maintain the sprite at super resolution and downsample the sprite before display. If you maintained the sprite at 3x display resolution,you'd have 9 different actual sprites depending on the subpixel position of the sprite.

\$\endgroup\$
3
\$\begingroup\$

The only real solution here is to use bilinear filtering. The idea is to let the GPU compute the value of each pixel based on the four sprite pixels that are overlapping with it. It's a common and effective technique. You simply need to place the sprite on a 2d-plain (a billboard) as a texture; then use the GPU to render these plains. This works well but expect to get somewhat blurry results and lose the 8-bit or 16-bit look if you were aiming for it.

pros:

Already implemented, very fast, hardware based solution.

cons:

Loss of 8-bit / 16-bit fidelity.

\$\endgroup\$
1
\$\begingroup\$

Floating point is the normal way of doing this, especially if you're eventually rendering to a GL target where the hardware is quite happy with a shaded polygon with float coordinates.

However there is another way of doing what you're currently doing but slightly less jerkily: fixed point. A 32 bit position value can represent values 0 through 4,294,967,295 even though your screen almost certainly has less than 4k pixels along either axis. So put a fixed "binary point" (by analogy with decimal point) in the middle and you can represent pixel positions from 0-65536 with another 16 bits of subpixel resolution. You can then add numbers as normal, you just have to remember to convert by right shifting 16 bits whenever converting to screen space or when you've multiplied two numbers together.

(I wrote a 3D engine in 16-bit fixed point back in the day when I had an Intel 386 with no floating point unit. It achieved the blinding speed of 200 Phong-shaded polygons per frame.)

\$\endgroup\$
1
\$\begingroup\$

In addition to the other answers here you can use dithering to some extent. Dithering is where the edge of an object's pixels are lighter/darker in color to match the background making for a softer edge. For example, say you had a 4 pixel square I'll aproximate with:

OO
OO

If you were to move this 1/2 a pixel to the right, nothing would really move. But with some dithering you could make an illusion of movement a bit. Consider O as black and o as gray, so you might do:

oOo
oOo

In one frame and

 OO
 OO

In the next. The actual "square" with the grayed edges would actually take 3 pixels across, but since they are lighter gray than the black they appear smaller. This can be difficult to code, however, and I'm not really aware of anything that does this right now. About the time they started using dithering for things resolutions quickly became better and there wasn't as much need except in such things as image manipulation.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .