SOLVED what is the purpose of fraction dumping?

D

DesFrog

Guest
i am totally new to programming let alone gamemaker studio 2
i am learning from shaun spalding's videos too.
and i came across this code in his video. i think i know what the code does (it is to subtract decimal part of the number??).
but i dunno why it is necessary.

GML:
hsp += hsp_frac; //why add first?
vsp += vsp_frac;
hsp_frac = frac(hsp);
vsp_frac = frac(vsp);
hsp -= hsp_frac;
vsp -= vsp_frac;
 

GMWolf

aka fel666
I think the idea here is to keep hsp as an integer, but keep track of the fractional part we discarded.

We want to keep hsp and VSP and integer so that X and Y are integers, which makes a few things easier (like collisions) and also can fix some rendering issues.

By first adding the fractional part, we restore the actual hsp value. (Hsp before that was changed so it's not necessarily an integer)
Then we calculate tha fractional part of that new value.
And then we substract to get an integer again.


I find this code confusing. It would be much easier to just keep track of the actual velocity, and keep an error.
When you add velocity to position, only add the integer part, and add the fractional part to the error. When the error reaches 1, add 1 to the position.

GML:
x += floor(hsp);
xerror += fract(hsp);
if (xerror >= 1)
{
  x += 1;
  xerror -= 1;
}
That's much easier to understand IMO.
This has the effect of rounding down the X value. If you wanted to round up or down, then you could set the threshold to 0.5 so your error ranges from -0.5 to 0.5 rather than 0 to 1. That way your maximum absolute error is just 0.5 not 1.
 
Last edited:
D

DesFrog

Guest

this is the video where i found this code.

GML:
==========Create==============
hsp = 0;
vsp = 0;

hsp_frac = 0;
vsp_frac = 0;

hsp_acc = 1;
hsp_fric_ground = 0.50;
hsp_fric_air = 0.15;
hsp_walk = 4;

vsp_jump = -6;
vsp_max = 10;

onground = false;

grv = .3;

jumpbuffer =0;


============Step==============
key_left = keyboard_check_direct(vk_left);
key_right = keyboard_check_direct(vk_right);
key_jump = keyboard_check_pressed(vk_space);
key_jump_held = keyboard_check(vk_space);

var dir = key_right - key_left;
hsp += dir*hsp_acc;
if(dir ==0)
{
    var hsp_fric_final = hsp_fric_ground;
    if (!onground) hsp_fric_final = hsp_fric_air;
    hsp = Approach(hsp,0,hsp_fric_final);
}
hsp = clamp(hsp, -hsp_walk,hsp_walk);
//variable jump
if(vsp <0) && (!key_jump_held) vsp += grv;

//Calc vertical movemetn
vsp += grv;

//Ground Jump
if (jumpbuffer >0)
{
    jumpbuffer --;
    if(key_jump)
    {
        jumpbuffer = 0;
        vsp = vsp_jump;
        vsp_frac =0;
    }
}
vsp = clamp(vsp,-vsp_max, vsp_max)

//dump fractions and get final integer speeds
hsp += hsp_frac; //why add first?
vsp += vsp_frac;
hsp_frac = frac(hsp);
vsp_frac = frac(vsp);
hsp -= hsp_frac;  //소수점 이하를 뺀다
vsp -= vsp_frac;

//Horizontal Collision
if(place_meeting(x+hsp, y, oWall))
{
    var onepixel = sign(hsp);
    while (!place_meeting(x+onepixel, y, oWall)) x += onepixel;
    hsp = 0;
    hsp_frac = 0;
}
//Horizontal Move
x += hsp;

//Vertical Collision
if (place_meeting(x,y+vsp, oWall))
{
    var onepixel = sign(vsp);
    while(!place_meeting(x, y+onepixel, oWall)) y += onepixel;
    vsp = 0;
    vsp_frac = 0;
}
//Vertical Move
y += vsp;

onground = place_meeting(x, y+1, oWall);
if (onground) jumpbuffer = 6;

//Adjust sprite
image_speed =1;
if(hsp != 0)
{
    image_xscale = sign(hsp);
}
//animation
if(!onground)
{
    sprite_index = sPlayerA;
    image_speed =0;
    image_index = (vsp >0);
}
else
{
    if(hsp!= 0) sprite_index = sPlayerR; else sprite_index = sPlayer;
}
 
D

DesFrog

Guest
and i am sorry, i have to make my questions more clear
 

NightFrost

Member
Yes, the idea is to keep your coordinate positions as integers by having the speeds you add to them be always integers. You're wondering why the code adds the fractions to speeds first. That's because the speeds generated during the step may have fractions in them already; so it lets the fractions accumulate and then saves the new values back. If your game is top-down with diagonal movement, they will have fractions (for example if your speed is 1, diagonal movement is about 0.7 to both axes). If your game is a platformer, your gravity will very likely be fractional to allow for fine-tune of jump height.

Most of the time the whole thing can be ignored however. Because there are no partial pixels, GMS resolves the actual sprite draw position by automatically rounding the coordinates in the draw. Collision commands do this too, to align themselves to the perceived, rounded locations of the sprites. If nothing else requires the integer positions, the fraction shuffle becomes a waste of time, and you'd be able to get them anyway by taking a round of the coordinate.

Some people however do their low-res pixel games with the intent of preserving the pixel grid, as it would appear on old 8/16-bit computers. Meaning, the pixels, although large, would remain in perfect grid positions. In that, when you do certain scaling techniques, fractions become a liability and need to be removed before draw. But when simulating lower resolutions this will make movement look stuttery and jumpy, as even a one-pixel move is a big leap on screen. (I don't care for pixel alignment nor do I like twitchy movement, so I never use fraction storing techniques.)
 
D

DesFrog

Guest
thanks a lot! i understand it now ;)
you guys are so cool
 

TheouAegis

Member
I rarely notice the "stuttery and jumpy" movement Nightfrost mentioned, although a lot of people apparently do. I'm so used to playing 8bit and 16bit games that my brain has no problem interpreting pixel scaling.

Fraction dumping is subjectively valuable for "pixel-perfect" games due to how GM handles pixel size and position. Pixels will get stretched or shrunk during the scaling process, causing graphical defects some people find off-putting. If you remove the fractions and restrict yourself to certain screen resolutions, then you can get pixel-perfect upscaling. Then again, if you aren't scaling at perfect integers, then fraction dumping won't benefit your game at all.

Fraction dumping can greatly influence collisions if your game makes heavy use of fractions. Game Maker Studio uses round() on x and y, then calculates bounding box coordinates by adding the differences between the sprite's offsets and bounding box values as defined by the sprite properties. When the Draw event comes around, the coordinates are multiplied by the render scaling and then rounded. What this means is you effectively are moving half a pixel too early each time. At a low enough resolution, an x-coordinate of 111.60 is effectively the same as 112.50, which in turn can technically throw off collision detection. For example, if your hspd is 2/3, your collisions will technically be detected 1 frame early -- even though the hspd doesn't get rounded, an hspd greater than 0.5 for all intents and purposes may as well be 1.

Another reason for fraction dumping is to actually simulate 8bit and 16bit games. Something being in 8bits didn't restrict it to integer numbers from 0 to 255 (or -128 to 127), it could still have fractions, and most games on those systems did. Code efficiency was very important in the old days due to hardware limitations, and one of the easiest ways to streamline code was to drop the fractions whenever possible. Thus, instead of comparing full x coordinates, including the fractions, they'd compare just the integers, which also corresponded to what you'd see on the screen. Nowadays that's pretty much unnecessary, since modern systems can handle reals much more efficiently.
 

Mk.2

Member
I rarely notice the "stuttery and jumpy" movement Nightfrost mentioned, although a lot of people apparently do. I'm so used to playing 8bit and 16bit games that my brain has no problem interpreting pixel scaling.
It's strange. Sometimes when using it myself, I'll think "but [insert SNES game] didn't look like that". Then after going back and checking out that game, it absolutely did look like that and it has never been a problem for me any time I played it.

It's worth it for pixel art games, especially those with lower resolutions, just because it can look really off when there is a collision where pixels are very visibily partially overlapping.
 

Slyddar

Member
Seems solved, but will add my 2c worth.

I use that method for tile collisions, considering you need to perform those collisions as integers. It just allows clean integer collisions for your tile checks, and then those fractions get applied again next step. It can affect movement jerkiness though, as by default using it will make you move in integer values, which can make smooth stopping difficult I've fine tuned it though, allowing decimal movement to occur with tile collisions, and only removing the decimals if they will cause a collision that step.
 

NightFrost

Member
It's worth it for pixel art games, especially those with lower resolutions, just because it can look really off when there is a collision where pixels are very visibily partially overlapping.
Depending on the method used. If your view and app(lication) surface are of the same size and app surface is get drawn out stretched to display device size, then everything happens in the room coordinate system. Draws are getting rounded room's coordinate values and collision checks are rounding to those same values. As pixels are always perfectly aligned due to automated rounding, there's no danger of partial overlaps: it is the picture drawn on app surface that is getting stretched to display, so fractional instance coordinates are not relevant.

However if your view is sized to your pixel precision (say 320x160) but your app surface is set to display size, decimals can become a problem to put it slightly. The draws automatically multiply coordinates by the scaling factor between the view and the app surface (to use fancy words, a linear transformation happens between the coordinate spaces) and only after that the values get rounded to integers. Collision checks however happen in rounded room space coordinates, meaning they are not aligning with draw position anymore.

For example if your scaling factor is 6 and the x-position of instance is 5.4, collisions are being checked at integer position 5, which visually would be 5*6=30 on the app surface. The instance's sprite gets drawn at 5.4*6=32.4 which gets rounded to 32. So there's a gap of 2 display pixels or 2/6th of an upscaled pixel between where collision happens and where the sprite is drawn. So there's two options to get around that. One option is to align everything to pixels by storing fractions, as per the topic of thread. Which kind of makes the whole upscaling process pointless and you should use identically sized view and app surface instead. The other option is to write a collision handler that is aware of fractions.

It is kind of funny, YYG gives us the tool to smoothly upscale from a small view, but fails to follow through by giving us collision functions that are capable of working in that space.
 
Top