gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

Coding > Fixed point?

#105685 - QuantumDoja - Tue Oct 10, 2006 10:43 pm

Hi, im trying to make my sprite move in a fairly mario style manner, ive been reading all over about the benefits of fixed point and gravity, heres where i currently am......I think I have all the peices but cant bolt them together:

ScottLininger wrote
Quote:
Example: If your world is 100 pixels x 100 pixels wide, you keep track of it internally as if it were 800x800. If you want your character to appear at pixel position (10,20), this value would actually be stored in your character variables as position (80,160). At display time, you divide your coordinates by 8 to determine where to draw the sprite.


ok, so if my world is 100 by 100 (800 by 800) and i want to move forward I do this:

when the "right" button is pressed, the .xacceleration property gets set to a value, then the xvelocity is increased by the acceleration value, if the "right" button is held over x frames the acceleration property is increased (arent acceleration & velocity the same??)

at a certain frame i then call the following:
Player.x = Player.x + Player.xvelocity

if i let go of the "right" button the acceleration & xvelocity are still positive values, indicating the sprite is still moving, so there needs to be a deceleration function to handle this.

I think this is where I am with my demo attached.
demo: http://www.quantumlogic.co.uk/GBA/Main.rar

please press and hold a direction, the player should run in that direction and then when you release slide.

IMPORTANT NOTE:
The thing im trying to get at with the fixed point stuff is this:

when running the demo press jump, the sprite will continue to jump, whilst its doing this press left or right, the angle is sharp its not smooth eg:

y
|.................../
|................./
|.............../
|............./
|............|
|............|
|............|
|............|______________x

I was expecting my sprites movement to have a more rounded roll-off

..and this is where i think the fixed point math comes in if my fixed point level is 800 by 800 do i move my sprite as if it was on the 800 by 800 level, so i could move it 16 pixels for example then at sprite update time get the integer version?? - im getting really confused as I know updating like this:
x = 1, y = 1
x = 2, y = 2
will always give mea straight line, so i need to update like
x = 1, y = 0.5
x = 2, y = 0.75

Tepples wrote:
Quote:
The way most 2D games seem to do platform physics is to use Euler integration. Do the following in each frame for each sprite:

1. Add a constant to Y velocity. (Adjust this constant to reflect gravity and the scale of your game world.)
2. Add X velocity to X displacement and add Y velocity to Y displacement.
3. Perform collision tests and push the character away from walls, floors, and ceilings as appropriate.
4. Draw the sprite at position (X displacement - X camera origin, Y displacement - Y camera origin).


and this equation also seems to use floating points??? how do i use this:
Quote:
s = ut + (1/2)at? ( That is, s=(u*t)+((1/2)*a*t*t) )

s = distance (from ground, for instance)
u = starting speed
t = time
a = acceleration ( -9.81 for gravity )


Please help!!!!
_________________
Chris Davis

#105687 - Mucca - Wed Oct 11, 2006 12:54 am

Thats not really fixed point math, and trying to get away without doing proper fixed point math by trying to shoehorn such things as logic positions and level dimensions into a fixed-point friendly format will only create problems.

I posted some simple fixed point math code in the Kirby revolution thread in Coding on page 3 or 4 I think. It should be useful.

Apart from that, two things:

Firstly, its usually good enough to have a non-changing acceleration. If the button is not pressed, change it to a deceleration. Velocity is what changes.

Secondly, I really think you would be served well by introducing the concept of time. Those formulas you gave only work with respect to time. It appears your current method involves waiting x number of frames, then performing the integration ( position += velocity; velocity += acceleration ). This has obvious drawbacks - not every frame will be 1/60th of a second, thus you will get slowdown. And it makes it more difficult to come up with consistent and realistic values for velocity and acceleration, as your units will be pixels per n/60 seconds where n is how often you do the update.

However if you set up a timer, measure the time passed since the last update, and feed this into your equations, things become much more stable.

My advice - set up two timers, one that overflows every second (Prescalar setting 2), the other to be updated every time the first timer overflows. The timers are 16 bits, so this fits perfectly with a 16.16 fixed point number, representing the time in seconds since your program started.

Ah what the heck, here's some code...
Code:

// define timer addresses
#define REG_TIMER_2_VAL      0x04000108
#define REG_TIMER_2_CNT      0x0400010A
#define REG_TIMER_3_VAL      0x0400010C
#define REG_TIMER_3_CNT      0x0400010E


// define how many bits are used for fraction
#define FP_FRACTION_BITS    16

// for extra readability, make a typedef
typedef s32  FP;

// "construct" a fixed point from a normal integer
FP fix( s32 val )
{
    FP fp = val << FP_FRACTION_BITS ;
    return fp;
}

// "construct" from a float
FP fix( float f )
{
    FP fp = (FP)(f * ( 1 << FP_FRACTION_BITS));
    return fp;
}

// multiply two fixed point numbers
FP fix_mul( FP fp1, FP fp2 )
{
    s64 tmp = (s64)fp1 * (s64)fp2;
    FP prod  = (FP)(tmp >>  FP_FRACTION_BITS);
    return prod;
}

// divide one fixed point number by another
FP fix_div( FP dividend, FP divisor )
{
    s64 tmp = (s64)(dividend << 32);
    FP quotient = (FP)(( tmp / divisor ) >> (32 - FP_FRACTION_BITS));
    return quotient;
}
// round fixed point to nearest integer
FP fix_rnd( FP fp )
{
   if( fp >= 0 )
   {
      if( fp & (1 << (FP_FRACTION_BITS - 1)) )
      {
         // if fraction is greater or equal to half
         return ((fp & ~((1 << FP_FRACTION_BITS)-1)) + (1 << FP_FRACTION_BITS);
      }
      else
      {
         return (fp & ~((1 << FP_FRACTION_BITS)-1));
      }
   }
   else
   {
      FP pos = -fp;
      FP val = fp & ~((1 << FP_FRACTION_BITS)-1);
      
      if( pos & (1 << (FP_FRACTION_BITS - 1)) )
      {
         return (val - (1<<FP_FRACTION_BITS));
      }
      else
      {
         return val;
      }
   }
}
// return integer from fixed point
s32 fix_to_int( FP fp )
{   
   return (fp >> FP_FRACTION_BITS);
}


// get seconds elapsed since timer started
FP getTime()
{
   // read the values from the timers,
   // and combine into a 16:16 fixed point value
   return (FP)(*((volatile u16*)REG_TIMER_2_VAL) | ((*((volatile u16*)REG_TIMER_3_VAL))<<16));
}

void setUpTimers()
{   
   *((volatile u16*)REG_TIMER_2_CNT) = 2 | 0x80;   // set timer 2 pre-scalar
                                                   // to 2 so that it is updated
                                                   // every 256 cycles, thus
                                                   // overflows every second
                                                   // combine enable flag
   *((volatile u16*)REG_TIMER_3_CNT) = 4 | 0x80;   // timer 3, prescalar = 0,
                                                   // connect = 1, enable = 1
}

typedef struct
{   
   FP   x;      // x position
   FP   y;      // y position
   FP   vx;      // x velocity
   FP   vy;      // y velocity
   FP   ax;      // x acceleration
   FP   ay;      // y acceleration
}   Player;

int main()
{
   .
   setUpTimers();
   .
   
   FP   previousTime = getTime();
   FP   frameTime;
   
   Player player;
   
   player.x = fix(120);
   player.y = fix(80);
   player.vx = 0;
   player.vy = 0;
   player.ax = 0;
   player.ay = 0;
   
   while(true)
   {
      FP time = getTime();
      frameTime = time - previousTime;
      previousTime = time;
      .
      .
      .
      // Set acceleration according to input
                  // eg
                  //    player.ay = fix( 9.8f );
                  // actually 9.8 will be a crap value as its meters per second squared
                  // but we need pixels per second squared. Just try out some
                  // values and see how you get on
      ...   
      // Update position according to velocity and time - (d = vt)
      player.x += fix_mul(player.vx, frameTime);
      player.y += fix_mul(player.vx, frameTime);
      
      // Update velocity according to acceleration and time - (v = u + at)
      player.vx += fix_mul(player.ax, frameTime);
      player.vy += fix_mul(player.ay, frameTime);
         
      .
      // Wait for vblank
      
      // Calculate screen position by rounding off fixed point position
      // and converting it back to integer
      s32 spriteX = fix_to_int( fix_rnd( player.x) );
      s32 spriteY = fix_to_int( fix_rnd( player.y) );
      
      // draw sprite at (spriteX, spriteY)
      
   }


}


Disclaimer: this code is not tested or compiled.

#105689 - sajiimori - Wed Oct 11, 2006 1:38 am

Mucca, most GBA games do run at a fixed framerate. On all of my games so far, dropping a frame has caused the game logic to run slower.

The idea is that low framerate is a bug that needs to be fixed, not accepted behavior that needs to be accounted for.

It depends on the game, of course.

#105695 - Dwedit - Wed Oct 11, 2006 3:20 am

You can always cheat and use tweening to run the game logic every other frame, yet have smooth 60FPS animation.
_________________
"We are merely sprites that dance at the beck and call of our button pressing overlord."

#105707 - Mucca - Wed Oct 11, 2006 10:21 am

None of the games Ive written so far have attempted to run at full frame rate. They fluctuate, often in the low fifties or high forties, a lot of the time at 60, but sometimes dropping into the thirties. In my experience, as long as the frame rate stays above 35 or 40, the player wont notice it. The fluctuations do not cause slowdown as everything is time based, and the programmers dont have to worry about cycle counts until the situation affects the user-experience. Heavy frames are often hard to predict, and its mostly only possible to avoid them through lamentable design decisions which limit the player's experience further.

#105734 - Sausage Boy - Wed Oct 11, 2006 3:51 pm

Perhaps it runs slow because you're always mucking around with the time. ;)

Also, I'd say the fixed point you described in your first post QuantumDoja is enough, no need to over-complicate it. The reason to use fixed point is usually to allow sub-pixel movement, and that's what you do, it's fine.
_________________
"no offense, but this is the gayest game ever"

#105755 - sajiimori - Wed Oct 11, 2006 6:58 pm

All my games have been locked to 30fps. The two main benefits (off the top of my head) are that the code is simpler and battery usage is lower. Besides that, it has been argued that changes in framerate are often more noticable than a consistently moderate framerate.

#105756 - QuantumDoja - Wed Oct 11, 2006 7:00 pm

Hi, thanks for the info im using your code to update the position, im finding that i can set a y velocity and the player will fall faster and faster - I hope this is correct??

what bugs me know is how to slow down! for instance if I move right I have an acceleration of say: 5.0f and in the code to check if im pressing any buttons i can decrease the acceleration...but this makes no effect on the velocity - again is this correct?
_________________
Chris Davis

#105758 - Sausage Boy - Wed Oct 11, 2006 7:09 pm

If you know about Newton's first law, you'll know that it's just like in real life :P. One way of handling it would be to take air resistance into account.
_________________
"no offense, but this is the gayest game ever"

#105760 - sajiimori - Wed Oct 11, 2006 7:27 pm

Code:
Vector2 applyFriction(const Vector2* v)
{
  // Choose your own value.
  const friction = fix(0.8);

  Vector2 result;

  result.x = fix_mul(v->x, friction);
  result.y = fix_mul(v->y, friction);

  return result;
}

#105761 - Mucca - Wed Oct 11, 2006 7:35 pm

If your character is moving right (ie the velocity is positive eg +60), to slow down you will need a negative acceleration (eg -80).

If your character is moving left (ie the velocity is negative), to slow down you will need a positive acceleration.

If your character needs to jump, this is achieved by applying simply setting the y velocity to some initial jump velocity (eg -100). The y acceleration is then set to a gravity type value (eg 90). Play around with the values until it looks good.

If you set a y velocity, but y acceleration is zero, the player should fall at a constant rate. If you set a y acceleration, the player will fall faster and faster. Its probably a good idea to cap the y velocity at some point

#105764 - sajiimori - Wed Oct 11, 2006 7:58 pm

That's a very complicated way of looking at basic game physics. This is simpler:
Code:
void updatePlayerPhysics()
{
  // Apply gravity.
  if(!onGround())
    player.vel.y -= fix(0.1);

  player.pos.x += player.vel.x;
  player.pos.y += player.vel.y;

  // Defined in my last post.
  player.vel = applyFriction(&player.vel);
}
There's no need to store acceleration values or check signs.

#105768 - keldon - Wed Oct 11, 2006 8:10 pm

If you stick to how physics works then it can be a lot easier to tackle these problems. Acceleration is reset after each frame. When you jump you need only add to the frames acceleration. Gravity is constant acceleration.

Here is a really good example of physics (click here). Download that and run the jar, load up experiment 1 and observe. The red lines are the velocity vectors; notice how the heavier mass falls at the same speed, yet has a higher velocity and has the same amount of acceleration applied. I was just about to say that the source is included in the rar but it isn't; but I'm sure that gravity was achieved by simply providing a constant acceleration to each mass.

Here is a nice explanation of physics.

Most of the SodaLab is implemented with about 30 [active] lines of code, doing nothing but implementing the laws of physics.

#105788 - thegamefreak0134 - Wed Oct 11, 2006 10:07 pm

I think I have a quick solution to your buggage.

Think of acceleration as a velocity modifier. Thus, if you have an acceleration of 5, velocity is increased by 5 every frame. (assuming, for the sake of simplicity, that you have a fixed framerate.) So to controll your character, do something to this effect every frame:

Code:

if button Right = pressed { x accel = 1} ;
if button Left = pressed { x accel = -1} ;
if button Up = pressed { y accel = -1} ;
if button Down = pressed { y accel = 1} ;

x velocity = x velocity + x accell;
y velocity = y velocity + y accell;


This poses a slight problem, considering that after several frames the velocity would be something like 73, causing the character to fly to the ends of levels. So, you put in a check:

Code:

if x velocity > 5 {x velocity = 5}
if x velocity < -5 {x velocity = -5}
if y velocity > 5 {y velocity = 5}
if y velocity < -5 {y velocity = -5}


Then, every frame you add the x velocity to the characters X position in the level and add the Y velocity to the characters Y position in the level. The important thing to remember here is that, unless you need a sudden stop or start effect, you ignore the velocity and let the acceleration dictate what the velocity does.

I realise that this example does not account for say bringing the character to a stop on a button release or something like that, but the theorey should still work. Someone correct me if I am wrong... Which is possible knowing me.

-thegamefreak
_________________
What if the hokey-pokey really is what it's all about?

[url=http:/www.darknovagames.com/index.php?action=recruit&clanid=1]Support Zeta on DarkNova![/url]

#105797 - keldon - Wed Oct 11, 2006 11:17 pm

thegamefreak, acceleration is reset at the beginning/end of your calculations so if you are jumping you can simply add [for example] 5 to the y axis acceleration and it will be fine. If your model has gravity and friction then it will actually work perfectly fine.

Sajimori's code is what you will need to get by.

#106049 - QuantumDoja - Sat Oct 14, 2006 6:10 pm

Thank you to everyone who has responded to this post...I thought Id share with you what ive got now and where im up to:

http://www.quantumlogic.co.uk/GBA/Main2.rar - click to download the *.gba file, my code now looks like this:

Code:

while(levelComplete == 0) {
         
         FP time = getTime();
         frameTime = time - previousTime;
         previousTime = time;

         addFrictionAndGravity();
         storeTempPos();
         platformDetect();

         if (keyDown(KEY_B) && PlayerSprite.jumping != 1) {
            dir = 4;
            PlayerSprite.jumping == 1;
            PlayerSprite.air = 1;
            //PlayerSprite.pos.y -= fix_float(12.8f);
            PlayerSprite.vel.y = fix_float(-80.8f);
            PlayerSprite.acc.y = fix_float(4.8f);
            last_key = dir;
         }

         if (keyDown(KEY_LEFT)) {
            dir = 0;
            PlayerSprite.acc.x = fix_float(-30.75f);
            last_key = 0;
         }
         else if (keyDown(KEY_RIGHT))
            {
               dir = 2;
               PlayerSprite.acc.x = fix_float(30.75f);
               last_key = 2;
            }
         else if (PlayerSprite.air != 1) {

         }


         PlayerSprite.pos.x += fix_mul(PlayerSprite.vel.x, frameTime);
         PlayerSprite.pos.y += fix_mul(PlayerSprite.vel.y, frameTime);

         PlayerSprite.vel.x += fix_mul(PlayerSprite.acc.x, frameTime);
         PlayerSprite.vel.y += fix_mul(PlayerSprite.acc.y, frameTime);


         SpriteAnimation();
         WaitForVsync();   

         s32 spriteX = fix_to_int( fix_rnd( PlayerSprite.pos.x) );
         s32 spriteY = fix_to_int( fix_rnd( PlayerSprite.pos.y) );
         MoveSprite(&sprites[0],spriteX,spriteY);
         //PlotPixel(spriteX,spriteY,254);

         CopyOAM();
         UpdateBackground(&bg0);
         UpdateBackground(&bg1);
         UpdateBackground(&bg2);
         UpdateBackground(&bg3);


      }


Im still struggling with the fixed point math so my numbers may be well out.

Code:
void addFrictionAndGravity() {

   PlayerSprite.vel.y += fix_float(8.4f);


Code:
void storeTempPos() {
   PlayerSprite.temppos.x = PlayerSprite.pos.x;
   PlayerSprite.temppos.y = PlayerSprite.pos.y;
}


my character still slides and im struggling to get him to stop!!!
_________________
Chris Davis

#106083 - sajiimori - Sun Oct 15, 2006 12:32 am

I'd suggest removing PlayerSprite.acc and PlayerSprite.temppos. That kind of redundant state just makes things more complicated.

I'd also suggest removing variable framerate because almost no GBA games use it, and it also overcomplicates things.

Careful here:
Code:
PlayerSprite.jumping == 1;

#106116 - QuantumDoja - Sun Oct 15, 2006 1:55 pm

thanks for this one
Code:
PlayerSprite.jumping == 1;
, i didn't spot it, made so much difference to the jumping!
_________________
Chris Davis

#106117 - QuantumDoja - Sun Oct 15, 2006 2:11 pm

for those interested... heres what I have now....what do you think?

A Big thank you to everyone who posted on this thread you have all been really helpfull!

http://www.quantumlogic.co.uk/GBA/Main3.rar :-)
_________________
Chris Davis