Help with diagonal/sub-pixel movement in a very low-res game

mrdaneeyul

Member
Hey all! So I have an ongoing struggle with getting my character to move at a good diagonal speed. My resolution is 320x180, as I'm using 16x16 tiles--kinda like a widescreen GameBoy. Therefore, the player moves at 1px per frame when moving straight (this is top-down, like Zelda).

Unfortunately, diagonal movement is slower than straight movement. I've rounded this to a speed of 0.75, but the problem I keep running into is that this ends up being jerky/jittery because the sprite can't move between pixels.

I've done tons of research and have tried all kinds of solutions. Most just didn't work. Currently, I've got it "supersampling". The viewport is set to x4 (1280x720), while the camera is set to 320x160. This scales the game to create extra pixels in a sense, making 0.75 speed actually work.

The problem with this is that it's fairly intensive. I've barely got anything in the game, and when playing (through the GM "Play" button in the IDE), it will occasionally have a visible hitch in the frame rate (granted, this is on my laptop). Low FPS is 30, average is 60, and high is around 2000 when just walking around a test room.

Is there a solution to this? The old GameBoy games must have done it somehow. I just can't figure it out. Should I make all my assets x4? Then we run into resolution issues, since 720p doesn't scale well to 1080p. Is the IDE player just slow? I'm using GMS2 beta, but had the same problem in GM:S Pro. Can this be solved with surfaces somehow? I'd love to hear ideas.

Thanks!
 
Last edited:

RangerX

Member
Do you move your character by changing the X and Y position? If yes, did you try impulses of hspeed and vspeed? Another way to move around is also with "direction" and "speed" variables completely bypassing X, Y, hspeed and vspeed.
 

mrdaneeyul

Member
Yeah, I move by changing x and y. I ran a test with hspeed and vspeed, as well as with direction and speed, and it looks like those are just as jittery, sadly. Thanks for the suggestion anyway!

I wonder... is there a way to "supersample" individual sprites/objects? In other words, scale it up to x4 or whatever, then shrink it back down so it has more pixels? Maybe that's something that can be done with surfaces, though I have no experience with those.
 

RangerX

Member
If your game isn't far along, just take your graphical ressources and double them in size. Then double your view to 640x360 (double of what you said you have)
Basically, your source graphic will look the same but being double the resolution. This will help. I actually do that dirty trick with The Life Ruby.
 

mrdaneeyul

Member
That was actually going to be my next thought. I'm not very far along, so I should be able to get that working pretty quickly. Putting the view to 640x360 instead of 1280x720 without scaling resources already proved to increase FPS dramatically while still being a little jittery, though I was worried scaling the resources and then doing x2 would have the same intensive effect as doing x4. But it's really good to know it's working in yours. I'm excited to try it when I get home from work. I'll post back when it's done.
 

NightFrost

Member
Is the movement jittery only when moving diagonally, and disappears when you set diagonal movement speed to 1? That is, there will be no fractional coordinate values involved in movement.
 

mrdaneeyul

Member
NightFrost, yes. It's due to fractional coordinate values. I'd set the value to 1 normally, but that means the player moves noticeably faster when going diagonally.
 

NightFrost

Member
Then it is likely caused by staircase effect, and you should smooth out the draw position and see if that helps. (The x and y the code gives out are used only for draw.)
 

mrdaneeyul

Member
I tried a couple similar solutions a while back, and the problem is that I'm always moving at a 45 degree angle when moving diagonally. If I round x or y, I'm still going slow enough that it produces exactly the same effect: it will round up to 1, thus moving too quickly. And the floor() method stops movement altogether since it floors it to 0. :p
 
A

anomalous

Guest
Well, on this one I consulted external help.
http://gamedev.stackexchange.com/questions/87353/how-do-you-move-a-sprite-in-sub-pixel-increments

basically:
1. have different sprites for diagonal movement and draw it as needed
2. supersample (you do this), but can you supersample just the sprite and not the entire game window?
3. switch to bilinear filtering on the sprite when he's going diagonal

I would have tried #3 if I had this issue, largely because filtering is built in and has functions to control it. Not sure what they mean by #1, but its in the link. Hope it helps.
 

NightFrost

Member
Yes, the code I linked to tries to solve the diagonal movement by smoothing away the "vertical and horizontal step" when you travel diagonally, and always aligns your position in a manner that it steps into diagonal pixels. That's just the staircase removal though, and won't help in other problems in low pixels environments. For example if you set your to be 0.5 pixels, you are essentially moving 1 in 30 frames, or simulating 30fps so to speak, and that can look stuttery even when travelling in straight lines (assuming large enough pixels).
 

RangerX

Member
That was actually going to be my next thought. I'm not very far along, so I should be able to get that working pretty quickly. Putting the view to 640x360 instead of 1280x720 without scaling resources already proved to increase FPS dramatically while still being a little jittery, though I was worried scaling the resources and then doing x2 would have the same intensive effect as doing x4. But it's really good to know it's working in yours. I'm excited to try it when I get home from work. I'll post back when it's done.
So what happened with this? It should work...
 
have you tried rounding the x and y position of your sprite for the draw event like this?

draw_sprite(sprite_index, image_index, floor(x + 0.5), floor(y + 0.5));
 

Cerno~b

Member
Just some thoughts of a tired brain:
The game boy didn't mess around with subsampling and neither should you (you're doing a retro game, right?)

Here are some calculations:

Let's assume you run a game at 30 fps. And let's also assume your character moves at a pace of 3 pixels per second. That is equivalent to moving 3 px every 30 frames
Now if you take into account diagonal movement, you are currently moving 3 pixels to the right and 3 down and the the same 10 frames. That produces a different speed that you don't want.
The reason is that instead of moving 3 pixels per second diagonally, you are actually moving sqrt(9+9) ~= 4.25 px per second.

So since you can't decrease the distance (no subpixels allowed), you need to change the speed along the diagonal.
You want to move at 3 px per 30 frames (10 frames / px). Converting your diagonal motion in the same way gets you 30 frames / 4.25px ~= 7.05 frames / px for your diagonal motion.
So moving 1 px straight every 10 frames needs to be converted to moving 1px diagonally about every 7 frames. The minor discrepancy will likely not be noticed by the player.

Since your game will likely have different values, you can use the following formula:

Your choice:
Framerate (fr/s): f
Player Speed (px/s): v

The result:
Straight motion (number of frames per pixel horizontally or vertically): f / v
Diagonal motion (number of frames per pixel diagonally): f / (sqrt(2)*v)

Taking the example from above
Straight motion: 30/3 = 10 frames per pixel
Diagonal motion: 30 / (sqrt(2) * 3) ~= 7 frames per pixel

In order to pull this off, you need different update ticks for your character motion.
Since I'm fairly new to GameMaker, I don't know how would be the canon way of doing this.

One option would be to use a counter that is reset every time a motion key is pressed.
In straight mode, whenever the counter reaches 10, move one pixel straight.
In diagonal mode, whenever the counter reaches 7, move one pixel diagonally.

Obviously I just did that on paper, but if you want to try it in code, please let me know if it worked.
 

mrdaneeyul

Member
Well, on this one I consulted external help.
http://gamedev.stackexchange.com/questions/87353/how-do-you-move-a-sprite-in-sub-pixel-increments

basically:
1. have different sprites for diagonal movement and draw it as needed
2. supersample (you do this), but can you supersample just the sprite and not the entire game window?
3. switch to bilinear filtering on the sprite when he's going diagonal

I would have tried #3 if I had this issue, largely because filtering is built in and has functions to control it. Not sure what they mean by #1, but its in the link. Hope it helps.
This is one of the places I found in my early research, and I eventually went with #2. As for #3, is it possible to turn on bilinear filtering? I'm looking for a function that can do this (in GMS2), and I'm not seeing anything, either for a sprite or for the whole thing. There's some stuff with linear filtering, but I assume that's not quite the same thing. I seem to remember doing bilinear filtering in GMS1, and the results were... rather horrifying.

So what happened with this? It should work...
I just got home from work and had some errands to do. I'm still intending on trying it if Cerno~b's trick doesn't solve the issue. I'm pretty confident yours will work if nothing else will, but I'm hoping the math solution will fix it.

have you tried rounding the x and y position of your sprite for the draw event like this?

draw_sprite(sprite_index, image_index, floor(x + 0.5), floor(y + 0.5));
I haven't tried that. I'm skeptical that it will work. Say I start at origin, x = 0, y = 0, and then go down and to the right. That's +0.75 pixels down and +0.75 pixels right. So floor(0.75 + 0.5) = 1. Therefore, I'm still going down to x = 1 and y = 1. It would be smooth, sure, but it would be faster than the player's horizontal movement along just one of the axes... and if I was going to do that, I might as well just say x = 1, y = 1. :p

Just some thoughts of a tired brain:
The game boy didn't mess around with subsampling and neither should you (you're doing a retro game, right?)
I really like this train of thought throughout your comment. I'm doing a retro game, and I figured there has to be some way the old systems would do this. So I want to type this out to help me think through this.

I'm running at 60fps, and the code is set up to move 1 pixel per frame when moving on a single axis. That winds up being 60 pixels a second. If I round to 0.75 pixels per frame for the diagonal speed (in reality, it's somewhere around ~0.707, IIRC, but just for the sake of easy math...), that means I would end up moving 45 pixels a second, or basically, 3 out of every 4 frames.

I'm not well-versed in playing with GM's "ticks", but I'm sure there's several ways to skip every fourth frame when moving diagonally. Maybe just a frame counter variable in the player that every step gets counter++, and when it hits 4, it skips the diagonal movement and drops the counter back down to 0 (so the next frame it's set to 1). I think my main worry is how a "drop" to 45fps will appear. Will it be a visible difference? I guess the only way to find out is to try.

I'll report back--with either Cerno~b's solultion or RangerX's, unless someone knows how to turn bilinear filtering on and off for a single sprite.... in which case I'll try that too.
 

Cerno~b

Member
I really like this train of thought throughout your comment. I'm doing a retro game, and I figured there has to be some way the old systems would do this. So I want to type this out to help me think through this.

I'm running at 60fps, and the code is set up to move 1 pixel per frame when moving on a single axis. That winds up being 60 pixels a second. If I round to 0.75 pixels per frame for the diagonal speed (in reality, it's somewhere around ~0.707, IIRC, but just for the sake of easy math...), that means I would end up moving 45 pixels a second, or basically, 3 out of every 4 frames.

I'm not well-versed in playing with GM's "ticks", but I'm sure there's several ways to skip every fourth frame when moving diagonally. Maybe just a frame counter variable in the player that every step gets counter++, and when it hits 4, it skips the diagonal movement and drops the counter back down to 0 (so the next frame it's set to 1). I think my main worry is how a "drop" to 45fps will appear. Will it be a visible difference? I guess the only way to find out is to try.
Alright, so let's think this through:

So if you are running at 60 fps and use 1 px per frame that would mean your character runs at 60px per second.
According to my formulas you get 1px per frame for straight and 0.707px per frame for diagonal motion, the same as you calculated.

Unfortunately we cannot move less that 1 frame. But you are not forced to move pixel by pixel (unless you really want to move super smoothly).
I would recommend you try this:
For straight motion, move 1 pixel every frame (that gives you a speed of 60px/s)
For diagonal motion, move 2 pixels every third frame (that gives you a speed of 63px/s)

For the game ticks, I would do exactly what you described:

Keep some global variables:
Code:
global.motion = 0 // 0 is no motion, 1 is straight, 2 is diagonal
global.steps = 0
In your Steps script do something like this

Code:
var motion = 0/1/2 // determine the current state of buttons and assign a state
if motion != global.motion
{
    global.steps = 0
}
global.steps += 1

if global.motion == 1
{
    // move character 1 pixel straight
}
else if global.motion = 2
{
    if global.steps == 3
    {
        // move character 2 pixels diagonally
        global.steps = 0
    }
}
else
{
    // no motion
}
Interestingly the Game Boy ran at 60 fps, but I don't know whether games actually used that. I can't imagine how they were able to run 60 frames at 1px/frame and kept the same speed between straight and diagonal movement. I'd say just try running the game as I described and you will see whether your diagonal motion looks smooth. I'd be happy to see an exe with the result if you're willing to share.
 

mrdaneeyul

Member
Alright, so I tried it two different ways, but ultimately it's still jittery.

First was moving 1px three out of four frames. It was pretty straightforward to set up, and almost worked, but movement was pretty noticeably blurry compared to moving straight. After that, I tried the version you suggested, moving 2px every three frames, and that was even worse. :p

Interestingly the Game Boy ran at 60 fps, but I don't know whether games actually used that. I can't imagine how they were able to run 60 frames at 1px/frame and kept the same speed between straight and diagonal movement. I'd say just try running the game as I described and you will see whether your diagonal motion looks smooth. I'd be happy to see an exe with the result if you're willing to share.
I found it really fascinating when I originally learned the Game Boy ran at 60fps. I don't know if games actually used that, but it seems likely. I kinda wish I still had my Game Boy Color so I could dig out Oracle of Ages and see how smooth it was (emulators, I expect, handle frames differently). Maybe it just works because it was such a low resolution. Who knows. I wish this was a mystery we could figure out...

Anyway, I guess I'm gonna try RangerX's method. I'm 90% certain it will work, and "it works" is what I'm gonna go with in the end. :)
 

TheouAegis

Member
Create Event of any thing that moves diagonally:

hspd = 0 //you'd set this as expected
vspd = 0 //you'd set this as expected
hsf = 0; //don't touch this
vsf = 0; //don't touch this

Now instead of the classic x += hspd or y += vspd, you could instead do

hsf += hspd;
vsf += vspd;
var h = hsf & ~0, v = vsf & ~0;
x += h;
y += v;
hsf -= h;
vsf -= v;


The variables hsf and vsf would be holding the fractional parts of the pixel movement. The instance can only ever move in integers without any actual change in speed.
 

Mick

Member
The game I'm working on has a resolution of 240x135. I use fractional speeds for a lot, but I draw everything at floor(x), floor(y) and it looks fine. Scaling all graphics to 2x or 4x is kind of clumsy, since you need to resize everything and working with them afterwards is not as easy.

Movement will not be as smooth on a low resolution game though, that's not possible.
 
Last edited:

tafkatfos

Member
I had a similar issue. My player speed is 1 so when they go diagonal the speed appears to be 0.71, so when I set the speed to 1.42 then they didn't shake and it was smooth. Though this wasn't ideal as it looked bad with speedy movement going diagonal compared to up/down/left/right.

I still haven't fully fixed the issue (tried floor() round() etc) but I did make it a lot better by putting my objCamera follow player code in the end step. Don't know if your using a separate camera object or not so this might be of no use!

EDIT: The issue also isn't helped that I'm using paths to move as it's a mouse point and click type affair.
 
Last edited:

Cerno~b

Member
Hm, bummer.

I just had to try it myself.

So I tried running a test game at your resolution and standard motion behavior (1 px per frame for straight and diagonal movement)
It super smooth but I don't see a noticeable difference in straight and diagonal motion. I know it's mathematically there but for me it's barely perceivable.
I guess we have different standards, but it is nice to approach this from another person's viewpoint.
This seems like a very interesting topic and I would like to learn more about it.

Next I tried my setup.
In order to have similar levels of smoothness for straight and diagonal motion, I move three pixels straight for every 3 frames and I move 2 pixels diagonally for every 3 frames.
The result is that the game looks much choppier but I can measure that it takes me 180 frames from top to bottom of the screen when going straight up and it takes 270 frames to reach the top when going diagonally.
This is what I expected since we move at about the same speed no matter whether we go straight or diagonal, so it would take 1.5 times as long to reach the same lateral distance.
Anyway I can see that this is not an option and the idea was pretty clunky in hindsight.

Then I tried what I understand you initially tried:
When moving straight, move at 1px/frame
When moving diagonally, move at 0.7px/frame
This looks quite smooth but uses subpixels which is probably not noticeable but from a purist standpoint that's not the way to go.
Also I can perceive a very slight jitter.

Finally I tried something that looks pretty promising. It does not use subpixels, is very smooth and fairly straightforward to implement.
I just now realized that this is pretty much what TheouAegis proposed, so I won't take credit. Also his implementation seems to be much more concise.
The idea is to store your position as a float value and increment it by your float speed every frame. Then you round the position and use that to place your character.

Full code (can likely be implemented in a nicer way)

Put this in your Steps code:

Code:
var left = false
var right = false
var up = false
var down = false
var horz = false
var vert = false
if keyboard_check(vk_left)
{
    left = true
    horz = true
}
if keyboard_check(vk_right)
{
    right = true
    horz = true
}
if keyboard_check(vk_up)
{
    up = true
    vert = true
}
if keyboard_check(vk_down)
{
    down = true
    vert = true
}

var curmotion
if horz and vert
{
    curmotion = 2 // diagonal
}
else if horz or vert
{
    curmotion = 1 //straight
}
else
{
    curmotion = 0 //no motion
    motion = 0
}

if motion != curmotion
{
    motion = curmotion
    counter = 0
    xf = x
    yf = y
}

if motion == 1
{
    speedf = 1
}
else if motion == 2
{
    speedf = 0.7
}
else
{}

counter += 1

if motion != 0
{
    if left xf = (xf - speedf)
    if right xf = (xf + speedf)
    if up yf = (yf - speedf)
    if down yf = (yf + speedf)
  
    x = floor(xf)
    y = floor(yf)
}
The variables that end with "f" contain the float information and are resynced with the actual position whenever the player changes direction
While the player keeps the current direction, they are updated with float speeds and floored to get the actual positions.

I used the counter variable to verify that it takes 180 frames to reach the top straight and 270 frames to reach the top diagonally.
Please try it and let me know if it works for you.
 

mrdaneeyul

Member
So what happened with this? It should work...
While I think I may ultimately go a different route (see below), this does work! Scaling up graphics x2 so that we've got a base resolution of 640x360, then upscaling in-game to 1280x720 using the viewport, then using the camera to scale back down to 640x360 makes it smooth, and we've got a low of 80fps even on my crappy no-GPU laptop. So that's always an option when everything is said and done. It's a little bit of a pain to scale all the assets up, though.

Create Event of any thing that moves diagonally:

hspd = 0 //you'd set this as expected
vspd = 0 //you'd set this as expected
hsf = 0; //don't touch this
vsf = 0; //don't touch this

Now instead of the classic x += hspd or y += vspd, you could instead do

hsf += hspd;
vsf += vspd;
var h = hsf & ~0, v = vsf & ~0;
x += h;
y += v;
hsf -= h;
vsf -= v;


The variables hsf and vsf would be holding the fractional parts of the pixel movement. The instance can only ever move in integers without any actual change in speed.
Hmm... I tried a similar solution once before. Wouldn't this be the same as moving 1px every three frames if diagonal speed is 0.75? In frame one, we wouldn't move because the remainder is 0.75. In frame two, we have 1.5, so we would move with a remainder of 0.5. In frame three, we have 1.25, so we move with a remainder of 0.25. In frame four, we have 1, so we move with a remainder of 0. Then on frame five we start over again, not moving since we have 0.75. So this has the same effect as moving 1px, 1px, 1px, and then 0, or three out of every four frames like was mentioned above.

That said, with my discovery (below), I'm thinking this will most likely be the best option.

The game I'm working on has a resolution of 240x135. I use fractional speeds for a lot, but I draw everything at floor(x), floor(y) and it looks fine. Scaling all graphics to 2x or 4x is kind of clumsy, since you need to resize everything and working with them afterwards is not as easy.

Movement will not be as smooth on a low resolution game though, that's not possible.
The problem here is that if you floor(0.75), you get 0, so the player doesn't move. If you floor(0.75 + 0.5), you get 1, so the player moves too fast. I don't think floor() is my solution here.

I still haven't fully fixed the issue (tried floor() round() etc) but I did make it a lot better by putting my objCamera follow player code in the end step. Don't know if your using a separate camera object or not so this might be of no use!
Luckily the camera isn't a problem here, since it's currently fixed in each room, like Zelda. I had problems with this in GMS1 though, since I had rooms where the camera moved around, so the player and the camera ended up jittery.

So I tried running a test game at your resolution and standard motion behavior (1 px per frame for straight and diagonal movement)
It super smooth but I don't see a noticeable difference in straight and diagonal motion. I know it's mathematically there but for me it's barely perceivable.
I guess we have different standards, but it is nice to approach this from another person's viewpoint.
This seems like a very interesting topic and I would like to learn more about it.

Next I tried my setup.
In order to have similar levels of smoothness for straight and diagonal motion, I move three pixels straight for every 3 frames and I move 2 pixels diagonally for every 3 frames.
The result is that the game looks much choppier but I can measure that it takes me 180 frames from top to bottom of the screen when going straight up and it takes 270 frames to reach the top when going diagonally.
This is what I expected since we move at about the same speed no matter whether we go straight or diagonal, so it would take 1.5 times as long to reach the same lateral distance.
Anyway I can see that this is not an option and the idea was pretty clunky in hindsight.

Then I tried what I understand you initially tried:
When moving straight, move at 1px/frame
When moving diagonally, move at 0.7px/frame
This looks quite smooth but uses subpixels which is probably not noticeable but from a purist standpoint that's not the way to go.
Also I can perceive a very slight jitter.

Finally I tried something that looks pretty promising. It does not use subpixels, is very smooth and fairly straightforward to implement.
I just now realized that this is pretty much what TheouAegis proposed, so I won't take credit. Also his implementation seems to be much more concise.
The idea is to store your position as a float value and increment it by your float speed every frame. Then you round the position and use that to place your character.

Full code (can likely be implemented in a nicer way)

Put this in your Steps code:

Code:
var left = false
var right = false
var up = false
var down = false
var horz = false
var vert = false
if keyboard_check(vk_left)
{
    left = true
    horz = true
}
if keyboard_check(vk_right)
{
    right = true
    horz = true
}
if keyboard_check(vk_up)
{
    up = true
    vert = true
}
if keyboard_check(vk_down)
{
    down = true
    vert = true
}

var curmotion
if horz and vert
{
    curmotion = 2 // diagonal
}
else if horz or vert
{
    curmotion = 1 //straight
}
else
{
    curmotion = 0 //no motion
    motion = 0
}

if motion != curmotion
{
    motion = curmotion
    counter = 0
    xf = x
    yf = y
}

if motion == 1
{
    speedf = 1
}
else if motion == 2
{
    speedf = 0.7
}
else
{}

counter += 1

if motion != 0
{
    if left xf = (xf - speedf)
    if right xf = (xf + speedf)
    if up yf = (yf - speedf)
    if down yf = (yf + speedf)
 
    x = floor(xf)
    y = floor(yf)
}
The variables that end with "f" contain the float information and are resynced with the actual position whenever the player changes direction
While the player keeps the current direction, they are updated with float speeds and floored to get the actual positions.

I used the counter variable to verify that it takes 180 frames to reach the top straight and 270 frames to reach the top diagonally.
Please try it and let me know if it works for you.
So before I try that... I discovered something really interesting. I fired up an emulator to see exactly what the movement in Oracle of Ages looked like. Guess what? It's got a faint blur when moving diagonally, just like when we moved 1px every three out of four frames. It's really fascinating that I've never noticed that before, and it makes me feel better about "lowering" my standards, so to speak.

As I mentioned above, adding up a "remainder" should have more or less the same effect as using a frame counter to count every four frames. I'm going to try this next, likely in combination with x2 supersampling (instead of x4), but unfortunately it's time for me to head to work!
 
Last edited:

Mick

Member
The problem here is that if you floor(0.75), you get 0, so the player doesn't move. If you floor(0.75 + 0.5), you get 1, so the player moves too fast. I don't think floor() is my solution here.
You are misunderstanding this. Take the x coordinate as an example, if you increase x by 0.7 every step (hspeed = 0.7) and draw the object at floor(x) you will have this result for the first 9 steps:

Step x floor(x)
------------------------
1 0.0 0
2 0.7 0
3 1.4 1
4 2.1 2
5 2.8 2
6 3.5 3
7 4.2 4
8 4.9 4
9 5.6 5

Any way you do it, you want to draw the sprites at integer coordinates. As you can see from the table, the object will be drawn at integer coordinates even when the speed is set to a decimal value. For low resolution games, decimal speeds will never look as smooth as for high resolution games. Scaling graphics and using a higher resolution will give you smoother movement but will give you the inconvenience with scaling the graphic assets.
 
A

anomalous

Guest
This is one of the places I found in my early research, and I eventually went with #2. As for #3, is it possible to turn on bilinear filtering?
yes, texture_set_interpolation(true);

If you can detect exactly when the sprite is being drawn sub-pixel by way of its x/y coordinate, when that is detected set interpolation to true, draw the sprite, then set it back to false.
If its too much blur, you can also toy with rounding partially, like round at 0.2 rather than 0.5. So 0.8 and 1.2 become 1, but 1.21 stays.
Then apply interpolation on the sub-pixel steps of the character. This will reduce blur, but add a slight jitter on the rounding (but less jitter than a pure round/ceil/floor.) all trade-offs.
I think due to the ease of implementation, its worth a look.
 

Cerno~b

Member
@mrdaneeyul Forget that crap of moving 3 pixels every third frame. That was a bad idea I had that only works on paper. Instead do what TheouAegis, Mick and myself suggested last. I think we all mean the same thing. Also I tried it and looks near perfect. Smooth motion with no jittering and no subpixels. Don't lower your standards just yet ;)

You can simply copy paste my code and see for yourself, it should run just like that.

The blur thing is kind of interesting though.
 

Ariak

Member
hsf += hspd;
vsf += vspd;
var h = hsf & ~0, v = vsf & ~0;
x += h;
y += v;
hsf -= h;
vsf -= v;
This is the way to go. As already mentioned it will store the fractional values, until these first exceed 1 - at which point they are added onto the speeds for that one step.
0.77 => 0, (0.77+0.77)=> 1, (.44+0.75) => 1 etc etc.

For this to look smooth you'd ideally want 60fps. 30 will work, but it still feels a bit clunky. However, if the resolution is as tiny as yours you'll be hard pressed to completely eliminate the jitter - this is the best solution. The trick is that all of this is happening so fast that the player/eye won't notice. Of course the speed is technically "wrong" for a single step - but seen as a whole its butter smooth, statistically precise and pixel perfect. Whatsmore the performance impact of this fractional adjust is entirely neligble.

My game is running 960x540 @ 60 fps - 360° movement with angular correction [and terrible gif quality].
 
Last edited:

mrdaneeyul

Member
@mrdaneeyul Forget that crap of moving 3 pixels every third frame. That was a bad idea I had that only works on paper. Instead do what TheouAegis, Mick and myself suggested last.
I was a bit vague, but yes, this is what I was going to try next. As far as the "blur" in Oracle of Ages, I meant it does have a tiny jitter--the one that I was still trying to avoid. I figure if it has a little bit of one, I should try to calm down just a little. :)

You are misunderstanding this. Take the x coordinate as an example, if you increase x by 0.7 every step (hspeed = 0.7) and draw the object at floor(x) you will have this result for the first 9 steps:

Step x floor(x)
------------------------
1 0.0 0
2 0.7 0
3 1.4 1
4 2.1 2
5 2.8 2
6 3.5 3
7 4.2 4
8 4.9 4
9 5.6 5

Any way you do it, you want to draw the sprites at integer coordinates. As you can see from the table, the object will be drawn at integer coordinates even when the speed is set to a decimal value. For low resolution games, decimal speeds will never look as smooth as for high resolution games. Scaling graphics and using a higher resolution will give you smoother movement but will give you the inconvenience with scaling the graphic assets.
Oops, you're right, I was misunderstanding. For whatever reason, I was thinking floor on the speed, rather than on the coordinates. That would definitely explain why this didn't work when I tried it back in the day...

I'd have to think about how to do this. I suppose it would be flooring the sprite in the draw event, not the actual coordinates of the object.

yes, texture_set_interpolation(true);

If you can detect exactly when the sprite is being drawn sub-pixel by way of its x/y coordinate, when that is detected set interpolation to true, draw the sprite, then set it back to false.
If its too much blur, you can also toy with rounding partially, like round at 0.2 rather than 0.5. So 0.8 and 1.2 become 1, but 1.21 stays.
Then apply interpolation on the sub-pixel steps of the character. This will reduce blur, but add a slight jitter on the rounding (but less jitter than a pure round/ceil/floor.) all trade-offs.
I think due to the ease of implementation, its worth a look.
It looks like GMS2 has a new function for interpolation, gpu_set_texfilter(), and it's for all images on the screen. There is a gpu_set_texfilter_ext() for use with shaders, so that's a possibility, but I don't have any experience with shaders and whether or not that would even work. All my experience with filtering so far has made the game look pretty glitchy, so I'm not sure this is the route I want to go next.

This is the way to go. As already mentioned it will store the fractional values, until these first exceed 1 - at which point they are added onto the speeds for that one step.
0.77 => 0, (0.77+0.77)=> 1, (.44+0.75) => 1 etc etc.

For this to look smooth you'd ideally want 60fps. 30 will work, but it still feels a bit clunky. However, if the resolution is as tiny as yours you'll be hard pressed to completely eliminate the jitter - this is the best solution. The trick is that all of this is happening so fast that the player/eye won't notice. Of course the speed is technically "wrong" for a single step - but seen as a whole its butter smooth, statistically precise and pixel perfect. Whatsmore the performance impact of this fractional adjust is entirely neligble.

My game is running 960x540 @ 60 fps - 360° movement with angular correction [and terrible gif quality].
This is definitely the route I want to go, as you and others have mentioned. I am running at 60fps as well. I think that if this solution doesn't quite smooth it out completely, doing x2 supersampling (which isn't hard on fps, ~280 or so on my laptop) should make it nearly perfect.

Now that I'm done with work for the day, I'll get to it and report back. :)
 

mrdaneeyul

Member
Success! We're running at a cool 400-500 fps. Using the "remainder" method smoothed it out probably 45% or so, and then supersampling x2 cleared up another 50%. There's still a very tiny, nearly imperceptible difference between walking straight and walking diagonally, but I'm definitely happy with the results.

In the player object's create event:
Code:
hRemainder = 0;
vRemainder = 0;
Then in the player's walk state script, xSpeed and ySpeed are determined based on keys, direction, etc. After these are determined, I have:
Code:
hRemainder += xSpeed;
vRemainder += ySpeed;

var h = hRemainder & ~0, v = vRemainder & ~0;

x += xSpeed;
y += ySpeed;

hRemainder -= h;
vRemainder -= v;
From the debug messages, it looks like "& ~0" is essentially bitwise for the floor() function. Is this correct, @TheouAegis?

I was going to export to an .exe, but apparently I haven't downloaded the full version of the beta yet even though I've purchased it. So I won't bother with that unless @Cerno~b is still wanting it. EDIT: I was just an idiot and forgot to change the target platform. Heh.

Thanks everyone for the help! This has been a really interesting discussion, and I'm satisfied with the conclusion.
 
Last edited:

Cerno~b

Member
Wow, you're really fixed on perfection here. So if I understand correctly you are currently doing sub-pixels.
Great that it worked out fine. I learned a lot as well. Don't bother with the exe.
I rolled my own code do it without sub-pixels because I probably won't notice the difference either way.

Glad you found a satisfying solution.
 

mrdaneeyul

Member
Using this method is pretty much the same as moving 1px every three out of four frames, just without the frame counter. So no sub-pixels. :) (Unless you mean the supersampling, in which case, yes, but at almost no fps cost with tons of benefit.)

I'm a bit of a perfectionist, unfortunately. Tends to make me very, very slow at everything...
 

Cerno~b

Member
Using this method is pretty much the same as moving 1px every three out of four frames, just without the frame counter. So no sub-pixels. :) (Unless you mean the supersampling, in which case, yes, but at almost no fps cost with tons of benefit.)

I'm a bit of a perfectionist, unfortunately. Tends to make me very, very slow at everything...
Yeah, pretty much. If your target resolution is 320x180 then supersampling it to twice the amount will use subpixels with respect to the original resolution.
Everything will be in 640x360 but all pixels are in fact 2x2. That means that you can have a half-pixel shift in your animation.
For example, that one pixel to the right of your avatar's head could be misaligned with a background pixel by half a pixel, right?
 

mrdaneeyul

Member
Yeah, pretty much. If your target resolution is 320x180 then supersampling it to twice the amount will use subpixels with respect to the original resolution.
Everything will be in 640x360 but all pixels are in fact 2x2. That means that you can have a half-pixel shift in your animation.
For example, that one pixel to the right of your avatar's head could be misaligned with a background pixel by half a pixel, right?
That's correct, though it's not really perceptible unless you're looking for it. I think it's worth the trade-off for the smoothness, all told.
 

Cerno~b

Member
Yeah I guess. I'll take the purist route and avoid pixel offsets. I'm really happy that GM obeys pixel preciseness even if you're rotating and scaling, unlike other frameworks I tried which cheat like Stardew Valley does. It's a unique style that takes some liberties but I'll keep my pixels perfectly aligned if I can. ;)
 

mrdaneeyul

Member
Yeah I guess. I'll take the purist route and avoid pixel offsets. I'm really happy that GM obeys pixel preciseness even if you're rotating and scaling, unlike other frameworks I tried which cheat like Stardew Valley does. It's a unique style that takes some liberties but I'll keep my pixels perfectly aligned if I can. ;)
I 100% understand your mindset. I'm keeping that same mindset in other areas, but I did make the decision to cheat in a few places. Widescreen is one... the GBC wasn't widescreen, of course!

At any rate, I don't blame you at all for wanting to be purist and avoid pixel offsets. I've obsessively stuck to other purist areas (the music has been tons of fun, and I'm learning all about how that works). And it's super easy to change back if somewhere down the line I want to stick to avoiding pixel offsets.

Good luck with your project, and thanks for participating in the discussion! It's been a very informative one.
 

Mick

Member
Code:
hRemainder = 0;
vRemainder = 0;
Then in the player's walk state script, xSpeed and ySpeed are determined based on keys, direction, etc. After these are determined, I have:
Code:
hRemainder += xSpeed;
vRemainder += ySpeed;

var h = hRemainder & ~0, v = vRemainder & ~0;

x += xSpeed;
y += ySpeed;

hRemainder -= h;
vRemainder -= v;
Good that you got it working by rescaling, but in the code you posted, x and y are only affected by xSpeed and ySpeed. hRemainder, vRemainder, h and v are not affecting the x and y coordinates.
 

mrdaneeyul

Member
Good that you got it working by rescaling, but in the code you posted, x and y are only affected by xSpeed and ySpeed. hRemainder, vRemainder, h and v are not affecting the x and y coordinates.
*Bangs head against desk* I'm stupid. I'll just blame it on the fact that I was tired last night so I don't feel too bad. Geez. It should be:
Code:
hRemainder += xSpeed;
vRemainder += ySpeed;

var h = hRemainder & ~0, v = vRemainder & ~0;

x += h;
y += v;

hRemainder -= h;
vRemainder -= v;
So a couple weird things are happening now that I fix the code. I'm noticing that:
  1. My movement code before the fix just now did nothing new, and was still just as jittery as before. Placebo effect I guess?
  2. The supersampling was doing all the real smoothing work.
  3. The supersampling isn't causing massive frame drops anymore at x2. Or at x4. It's not causing much of an fps drop at all. (which is... incredibly confusing... since it caused one before.)
  4. The fixed code, while it does smooth things out at 320x180, actually doesn't work together well with supersampling. It has the same effect at any multiplication, more or less. Which makes sense, because we're avoiding subpixels altogether.
Apparently I learned nothing at all, and supersampling just started working. :p But seriously, I now have some good options where it's pretty easy to, down the line, switch to one or the other depending on how much of a purist I want to be (I did actually learn some neat stuff). Thanks, guys.
 
Last edited:
S

Sylveax

Guest
I'm just leaving a reply here to find this thread again, will definitly use some of that stuff :)
 
Top