1. Hey! Guest! The 34th GMC Jam will take place between August 22nd, 12:00 UTC (Thursday noon) and August 26th, 12:00 UTC (Monday noon). Why not join in! Click here to find out more!
    Dismiss Notice

GML Movement fraction for next step

Discussion in 'Programming' started by NeZvers, Apr 1, 2019.

  1. NeZvers

    NeZvers Member

    Joined:
    Mar 24, 2018
    Posts:
    320
    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
    }
    
    
     
  2. NightFrost

    NightFrost Member

    Joined:
    Jun 24, 2016
    Posts:
    1,871
    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.
     
    Bentley likes this.
  3. NeZvers

    NeZvers Member

    Joined:
    Mar 24, 2018
    Posts:
    320
    @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: Apr 1, 2019
  4. TheouAegis

    TheouAegis Member

    Joined:
    Jul 3, 2016
    Posts:
    6,659
    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
     
  5. NeZvers

    NeZvers Member

    Joined:
    Mar 24, 2018
    Posts:
    320
    @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.
     
  6. TheouAegis

    TheouAegis Member

    Joined:
    Jul 3, 2016
    Posts:
    6,659
    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.
     
    immortalx and NeZvers like this.
  7. NeZvers

    NeZvers Member

    Joined:
    Mar 24, 2018
    Posts:
    320
    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: Apr 2, 2019
  8. immortalx

    immortalx Member

    Joined:
    Sep 6, 2018
    Posts:
    296
    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
     
    NeZvers likes this.
  9. NeZvers

    NeZvers Member

    Joined:
    Mar 24, 2018
    Posts:
    320
    @immortalx, dude I didn't know about it! Got to check it closer.
     
    immortalx likes this.
  10. NightFrost

    NightFrost Member

    Joined:
    Jun 24, 2016
    Posts:
    1,871
    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.
     
    NeZvers likes this.
  11. NeZvers

    NeZvers Member

    Joined:
    Mar 24, 2018
    Posts:
    320
    @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: Apr 2, 2019
  12. Bentley

    Bentley Member

    Joined:
    Jun 18, 2017
    Posts:
    765
    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))".
     
  13. NeZvers

    NeZvers Member

    Joined:
    Mar 24, 2018
    Posts:
    320
    @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 likes this.
  14. Bentley

    Bentley Member

    Joined:
    Jun 18, 2017
    Posts:
    765
    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).
     
  15. NeZvers

    NeZvers Member

    Joined:
    Mar 24, 2018
    Posts:
    320
    @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: Apr 2, 2019
  16. NightFrost

    NightFrost Member

    Joined:
    Jun 24, 2016
    Posts:
    1,871
    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.
     
  17. TheouAegis

    TheouAegis Member

    Joined:
    Jul 3, 2016
    Posts:
    6,659
    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.
     
  18. NeZvers

    NeZvers Member

    Joined:
    Mar 24, 2018
    Posts:
    320
    @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.
     

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice