GML Movement fraction for next step

N

NeZvers

Guest
I'm puzzled for some time how to solve this.
For the beginning, I'm using bitwise calculation as a challenge, but it's easily translatable to the traditional calculation (I'll provide explanation just in case).
Code:
/*
hspd[0] used for main speed
hspd[1] used to carry over the fraction
RUN speed is 2<<8 = 512
Acceleration is RUN / 30
*/

/// H-collision script start
//speeds are 256 times higher since not using floats
var hsp = (bit_abs(hspd[0]) >> 8) * bit_sign(hspd[0]); // hsp is used for collision and setting x
// >>8 work similar as  div 256 and floor rounding
// bit_abs and bit_sign is equal to their native gml counterpart abs() and sign()

// fraction seems to be calculated right
hspd[1] = hspd[0] - (hsp<<8); // save remainder for smooth walking
// <<8 work similar as *256

/// H-movement script start
hspd[0]+=hspd[1];
hspd[1]=0;
So the problem is that when fraction return is active my character keeps sliding the floor until hits a wall.
Just in case:
Code:
///h_move(hinput)
var Xin = argument[0];


if(!hurt && !dashing && Xin!=0){
    if(btns & dash){
        dashing = true; //turn off in dash state end
    }
    if(dashing){
        if(Xin>0){
            hspd[0] = DASH;
        }
        else{
            hspd[0] = -DASH;
        }
    }
}

hspd[0]+=hspd[1]; //return fraction
hspd[1]=0;

if(!dashing && Xin > 0) //go right
{
    facing = 1;
    if(grounded)
    {
        if(hspd[0]>=0) //same direction
        {
            hspd[0] += RUN;
        }
        else //changing direction
        {
            hspd[0] += SKID;
        }
    }
    else
    {
        hspd[0] += AIR;
    }
}
if(!dashing && Xin <0)
{
    facing = -1;
    if(grounded)
    {
        if(hspd[0]<=0) //same direction
        {
            hspd[0] -= RUN;
        }
        else //changing direction
        {
            hspd[0] -= SKID;
        }
    }
    else
    {
        hspd[0] -= AIR;
    }
}
if(!dashing && Xin == 0) //released direction
{
    if(grounded)
    {
        hspd[0] = bit_approach(hspd[0], 0, DRAG); //slow down
    }
    else
    {
        hspd[0] = bit_approach(hspd[0], 0, AIRS); //slow down
    }
}

if(!dashing){
    if(hspd[0] > MAX){hspd[0] = MAX;}   //clamp
    if(hspd[0] < -MAX){hspd[0] = -MAX;} //clamp
}
else{
    if(hspd[0] > DASH){hspd[0] = DASH;}     //clamp
    if(hspd[0] < -DASH){hspd[0] = -DASH;}   //clamp
}
 

NightFrost

Member
What is the issue that requires tracking fractions separately? They get rounded to integers when collisions and draws are done, so fractional values in coordinates are never an issue or throw off either collision position or draw position.
 
N

NeZvers

Guest
@NightFrost I want to achieve smooth movement. Carrying over fraction means it doesn't feel like stepped acceleration or deacceleration.
Even Shaun Spalding show this but it doesn't work for my project:

EDIT:
After taking another look at Shaun's tutorial I think I kinda worked it out, maybe you can tell me if I'm wrong.
Code:
//removed snipet at H-move script and did this in collision script begining
hspd[0]+= hspd[1];
var hsp = (bit_abs(hspd[0]) >> 8) * bit_sign(hspd[0]); //speeds are 256 times higher since can't use floats with bitwise
hspd[1] = hspd[0] - (hsp<<8); // save remainder for smooth walking
hspd[0] = hspd[0] - hspd[1]; // <- this was my culprit, I didn't removed it.
 
Last edited:

TheouAegis

Member
Why don't you just keep it in one single variable, instead of using values like 1.5 or 2.0 for the speed, use a value of $180 or $200. Then

var hsp=hspd>>8;
hsp-=(hsp & $80)<<1;
x+=hsp; //or whatever else you want to do
 
N

NeZvers

Guest
@TheouAegis, can you please elaborate on that!
I just began to get into bitwise so my knowledge is limited.
I don't use floating numbers (apart from getting acceleration speed by RUN / 30). RUN, JUMP, etc are shifted up (<<8) numbers that are used as constants. Then hspd[0] is manipulated by those constants which get shifted down (all that as workaround to not use floats below 1). All that is kinda preparation to get into assembler to make NES game (I got inspired from Lizard source and I'm using same constants values).

I don't know $ usage and with it how are you meaning the fraction carrying over.
 

TheouAegis

Member
NES games work on 8 bits, sure. SNES, Genesis, and many others work on 16 bits or more. The principle is the same, kinda.

If you're going to start burying your head in 6502 coding, then I guess we should start with the basics of moving in 8bit. What you've got going right now is close enough to coding in 8bit, though.

Movement in 8bit is at its core 4 bytes:
  1. Integral coordinate (x)
  2. Fractional coordinate (xf)
  3. Integral vector (h)
  4. Fractional vector (hf)
Movement at its core is still the same regardless of the system: x+=hspd; y+=vspd. The difference between systems is how byte overflows are handled. If you add [h,hf] to [x,xf], the math will be the same as if you were doing normal math in 5th grade:
Code:
 [x][xf]
+[h][hf]

 24.5
+ 1.5
=====
 26.0
The answer to the bottom equation is obviously 26.0, but how did it get to that point? The .5's are added together, which results in 1.0, but that 1 is not part of the fraction, so it gets carried over to the integer part, which results in 25+1, or 26. Most systems these days will take care of that automatically. The NES is not such a system, though, so simple addition and subtraction takes a bit more work.

In 8bit hexadecimal notation, the fraction 0.5 is $80, because $0100 times 0.5 is $80. If you add $80 to itself, the result is $0100, but that's a 16bit number, so on the NES it's invalid. What you would end up getting is $80+$80=$00+bit0, where bit0 is referring to bit 0 of the status flag, a.k.a. the carry bit. To get the $01 from $80+$80, you need to add bit0 to something.
Code:
CLC        //clear bit0 before adding
LDA $80    //now A == $80
ADC $80    //now A == $00, bit0 is now set
ADC $00    //now A == $01
If we used variables instead of hard numbers, it starts to be more practical. Let's assume here [h,hf] is [$01,$80] or 1.5pps, and we'll say x is $80 (middle of the screen) and xf is $C0 (or .75).
Code:
CLC
LDA hf    //now A is $80
ADC xf    //now A is $40, bit0 is set
STA xf    //now xf is $40
LDA h    //now A is $01
ADC x    //now A is $82 
STA x    //now x is $82
And if you wanted to add acceleration of, let's say $40, or 1/4pps, then the code would look like
Code:
CLC
LDA acc
ADC hf
STA hf
LDA h
ADC $00
STA h
LDA hf
ADC xf
STA xf
LDA h
ADC x
STA x
If you worked in 16bits, like on the SNES, then it's much simpler.
Code:
CLC
LDA acc
ADC h
STA h
CLC
ADC x
STA x
But then you'd only pass the upper byte of x to the Draw routine. But I'm getting off track here. ...Except I'm not.

Game Maker ain't an NES, so if you're going to restrain yourself with "8bit" coding, you should make things "easier" on yourself by coding in 16bits or more and then just be aware of what you were doing so you can switch your mindset from 16bit to 8bit when you start up on 6502. But with that said, let's go back to 8bit math.
Code:
xf += hf;
x += h;
That's not going to work in GM because GM lacks an explicit status flag, which is at the core of NES coding. Some NES coders even have a little too much fun with the status flag and its associated instructions. So if you're not going to work with 16bit code, then you're going to need to make a status flag, which is going to require extra scripts. Furthermore, since GM is 32bit (or 64bit, but I don't care which), even if you try to keep your numbers 8bit, the math involved is going to be 32bit, so you'll need to account for that.
Code:
bit0 = 0;
hf += acc;
bit0 = hf >> 4;
hf &= $ff;
h += bit0;
bit0 = 0;
xf += hf;
bit0 = xf >> 4;
xf &= $ff;
x += h + bit0;
Where that code ended is still technically a 32bit result. If x was $FF and we add 1 to it, then it becomes $0100, which is totally legal in Game Maker. That's not the case in NES, obviously. You can then add another variable which here I will call sc (for 'screen').
Code:
...
bit0 = x >> 4;
sc += bit0;
Coders get even craftier with that line of code, using it to toggle visibility in single-room games. But before you go deciding to do the whole sc.x.xf method, bear in mind that you would then be responsible for making your own views mechanic if you had a multi-screen room.
Code:
SEC    //set the carry
LDA x
SBC view_x
LDA sc
SBC view_sc 
STA draw_x
Coding in 6502 is very, very different from coding in GML, so I'd be wary of getting too far into one or the other. Studying 6502 can be fun, but actually using it is a whole other story.
 
N

NeZvers

Guest
I can't thank you enough! That was the piece I was lacking.
I'm not sure if I'll go full on 6502 but I'll do some testing for sure. Most probably I'll use Lizard source code (C++ to ASM - amazing piece to go through) as a framework or borrow something IF I'll actually get that far as developing actual NES game.

I already have almost full feature platformer "engine". ds_grid collision with bitwise (love it how on point it is), slopes, ledge grab. Didn't have a clue how to deal collision with moving platforms so for that I have an object bbox collision. And bunch of other stuff that was out of my initial bitwise physics idea (states, camera states, cutscenes).
Fraction movement was a tiny feature I wanted to put in.

I'm actually amazed by how many really knowledgable people use GM although probably could make their own engine/ framework for their games.
 
Last edited:
I

immortalx

Guest
OFF TOPIC
@NeZvers, @TheouAegis
You guys are the perfect people for getting a Uzebox as a birthday present... While games for it are written in plain C and not assembly, its low-level enough that I'm sure you'll love it (except if you're already aware of it). I've build one some time ago and it's a fantastic little piece of hardware that has this NES vibe.
Now I'll leave you crazy b@stards continue :D
 

NightFrost

Member
I want to achieve smooth movement. Carrying over fraction means it doesn't feel like stepped acceleration or deacceleration.
But you are still carrying over the fractional, in the other variable designed for that. Ignoring the fractional until it reaches >= 1 has the same effect as manually floor()ing the coordinate in collision and draw. The bit shuffle is necessary in assembly to represent noninteger speeds because it doesn't do fractions, but GML does handle fractions.
 
N

NeZvers

Guest
@NightFrost Maybe you are right. I'll make a test project to see if it makes a difference to have a fraction. I wanted to have a feeling of float number movement, at the moment I don't know what to think about what I have now.

@TheouAegis, if you didn't know there's NESmaker which is bunch of tools for developing nes + modular asm engine. All stuff is broken in scripts and you can make your own asm code to replace them, which are compiled together. Developers and community are slowly expanding it.
 
Last edited:

Bentley

Member
What is the issue that requires tracking fractions separately? They get rounded to integers when collisions and draws are done, so fractional values in coordinates are never an issue or throw off either collision position or draw position.
Good to know!

So if my x is at 0 and my hspd is 0.2, and I have "x += hspd" in my step event,
It will take the 3 frames (x = 0.6) for my x position to be drawn at 1?

If I'm understanding you so far, this would be redundant right? "draw_sprite(sprite, image, round(x), round(y))".
 
N

NeZvers

Guest
@Bentley, I just did a test and it turns out that your character gets drawn with round() (0.6 ==1) according to application_surface (pixels in the surface). If you have view scale-up objects move in smaller increments than game coordinates. BUT you need to deal with those fractions when it comes to collision or you'll have gaps between objects and walls and you'll have problems to get moving platforms to work.
 

Bentley

Member
@Bentley, I just did a test and it turns out that your character gets drawn with round() (0.6 ==1) according to application_surface (pixels in the surface). If you have view scale-up objects move in smaller increments than game coordinates. BUT you need to deal with those fractions when it comes to collision or you'll have gaps between objects and walls and you'll have problems to get moving platforms to work.
Yes, good point. In small rooms, a place_meeting check (x, y + 1) when your vspd is 0.5 or something will result in the instance noticeably "floating" above the ground because that place_meeting check is true (at least I think that's what happened).
 
N

NeZvers

Guest
@NightFrost I made a simple test for checking acceleration and deacceleration for fractional movement. Maybe you can add something to this test.
3x objects:
1) No fraction adjustment just as is;
Code:
if(keyboard_check(vk_right)){
    hspd+=acc;
    hspd=clamp(hspd,0,spd);
}
else{
    if(hspd>0){
        hspd-=acc;
    }
    if(hspd<=0){
        hspd=0;
        fraction=0;
    }
}

x+=hspd;
2) Remove excess fraction and add it back in next step;
Code:
if(keyboard_check(vk_right)){
    hspd+=acc;
    hspd=clamp(hspd,0,spd);
}
else{
    if(hspd>0){
        hspd-=acc;
    }
   if(hspd<=0){
        hspd=0;
        fraction=0;
    }
}

hspd+=fraction;
fraction= hspd - floor(hspd);
hspd-=fraction;

x+=hspd;
3) Use ghost x (xx variable) and have x=floor(xx);
Code:
if(keyboard_check(vk_right)){
    hspd+=acc;
    hspd=clamp(hspd,0,spd);
}
else{
    if(hspd>0){
        hspd-=acc;
    }
    if(hspd<=0){
        hspd=0;
        fraction=0;
    }
}
xx+=hspd;

x= floor(xx);

The fractional movement has a certain feel to it, but it's not as fluid as other examples. I wonder if it is possible to turn it like 3rd example since my game has basically floor type calculation.
 
Last edited:

NightFrost

Member
Collisions should be also going by rounded values. For exampe if you look at an instance's bbox_* values which tell you where the collision box is, they're always integers rounded from x/y position. However I recall from some testing, the commands that look at single pixels had a different scheme so you may want to test their behavior first.
 

TheouAegis

Member
Or you can take the fraction out at the end of the Step Event, perform your collision checks in the End Step event, then put the fraction back in at the Begin Step event. So collisions and drawing would all be handled as integers but position updates would be fractional.
 
N

NeZvers

Guest
@TheouAegis, I don't see how it is different from:
Code:
//do acceleration
hspd+=fraction;
fraction= hspd - floor(hspd);
hspd-=fraction;
//do collision
x+=hspd;
Other than fraction addition is swapped with acc/dcc + clamp, where I think post clamp should be better in case clamp is in fraction territory.
 
Top