user1680034
user1680034

Reputation: 93

Why does 80 fps on 4 pixels per frame lag for me?

so im developing arkanoid and the movement lags for me.
i dont know why its lagging because i heard that 60 fps is eye cap.
any suggestions on what i should do because i dont want to program games on 100 fps
because i have less time to handle my calculations if i can program them on 60 fps?
here is my capping frame code.

//Cap the frame rate                
if( fps.get_ticks() < 1000 / FRAMES_PER_SECOND )   
{   
    SDL_Delay( ( 1000 / FRAMES_PER_SECOND ) - fps.get_ticks() );   
}

and here is my whole code if your curious

/*This source code copyrighted by Lazy Foo' Productions (2004-2012)
and may not be redistributed without written permission.*/

//The headers
#include "SDL/SDL.h"
#include "SDL/SDL_image.h"
#include <string>

//The screen attributes
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
const int SCREEN_BPP = 32;

//The frame rate
const int FRAMES_PER_SECOND = 40;

//The dimensions of the dot
const int DOT_WIDTH = 20;
const int DOT_HEIGHT = 20;

//The surfaces
SDL_Surface *dot = NULL;
SDL_Surface *screen = NULL;

//The event structure
SDL_Event event;

//The dot that will move around on the screen
class Dot
{
    private:
    //The X and Y offsets of the dot
    int x, y;

    //The velocity of the dot
    int xVel, yVel;

    public:
    //Initializes the variables
    Dot();

    //Takes key presses and adjusts the dot's velocity
    void handle_input();

    //Moves the dot
    void move();

    //Shows the dot on the screen
    void show();
};

//The timer
class Timer
{
    private:
    //The clock time when the timer started
    int startTicks;

    //The ticks stored when the timer was paused
    int pausedTicks;

    //The timer status
    bool paused;
    bool started;

    public:
    //Initializes variables
    Timer();

    //The various clock actions
    void start();
    void stop();
    void pause();
    void unpause();

    //Gets the timer's time
    int get_ticks();

    //Checks the status of the timer
    bool is_started();
    bool is_paused();
};

SDL_Surface *load_image( std::string filename )
{
    //The image that's loaded
    SDL_Surface* loadedImage = NULL;

    //The optimized surface that will be used
    SDL_Surface* optimizedImage = NULL;

    //Load the image
    loadedImage = IMG_Load( filename.c_str() );

    //If the image loaded
    if( loadedImage != NULL )
    {
        //Create an optimized surface
        optimizedImage = SDL_DisplayFormat( loadedImage );

        //Free the old surface
        SDL_FreeSurface( loadedImage );

        //If the surface was optimized
        if( optimizedImage != NULL )
        {
            //Color key surface
            SDL_SetColorKey( optimizedImage, SDL_SRCCOLORKEY, SDL_MapRGB(     optimizedImage->format, 0, 0xFF, 0xFF ) );
        }
    }

    //Return the optimized surface
    return optimizedImage;
}

void apply_surface( int x, int y, SDL_Surface* source, SDL_Surface* destination,                 SDL_Rect* clip = NULL )
{
    //Holds offsets
    SDL_Rect offset;

    //Get offsets
    offset.x = x;
    offset.y = y;

    //Blit
    SDL_BlitSurface( source, clip, destination, &offset );
}

bool init()
{
    //Initialize all SDL subsystems
    if( SDL_Init( SDL_INIT_EVERYTHING ) == -1 )
    {
        return false;
    }

    //Set up the screen
    screen = SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_SWSURFACE         );

    //If there was an error in setting up the screen
    if( screen == NULL )
    {
        return false;
    }

    //Set the window caption
    SDL_WM_SetCaption( "Move the Dot", NULL );

    //If everything initialized fine
    return true;
}

bool load_files()
{
    //Load the dot image
    dot = load_image( "dot.bmp" );

    //If there was a problem in loading the dot
    if( dot == NULL )
    {
       return false;
    }

    //If everything loaded fine
    return true;
}

void clean_up()
{
    //Free the surface
    SDL_FreeSurface( dot );

    //Quit SDL
    SDL_Quit();
}

Dot::Dot()
{
    //Initialize the offsets
    x = 0;
    y = 0;

    //Initialize the velocity
    xVel = 0;
    yVel = 0;
}

void Dot::handle_input()
{
    //If a key was pressed
    if( event.type == SDL_KEYDOWN )
    {
        //Adjust the velocity
        switch( event.key.keysym.sym )
        {
            case SDLK_UP: yVel -= DOT_HEIGHT / 2; break;
            case SDLK_DOWN: yVel += DOT_HEIGHT / 2; break;
            case SDLK_LEFT: xVel -= DOT_WIDTH / 2; break;
            case SDLK_RIGHT: xVel += DOT_WIDTH / 2; break;
        }
    }
    //If a key was released
    else if( event.type == SDL_KEYUP )
    {
        //Adjust the velocity
        switch( event.key.keysym.sym )
        {
            case SDLK_UP: yVel += DOT_HEIGHT / 2; break;
           case SDLK_DOWN: yVel -= DOT_HEIGHT / 2; break;
            case SDLK_LEFT: xVel += DOT_WIDTH / 2; break;
            case SDLK_RIGHT: xVel -= DOT_WIDTH / 2; break;
        }
    }
}

void Dot::move()
{
    //Move the dot left or right
    x += xVel;

    //If the dot went too far to the left or right
    if( ( x < 0 ) || ( x + DOT_WIDTH > SCREEN_WIDTH ) )
    {
        //move back
        x -= xVel;
    }

    //Move the dot up or down
    y += yVel;

    //If the dot went too far up or down
    if( ( y < 0 ) || ( y + DOT_HEIGHT > SCREEN_HEIGHT ) )
    {
        //move back
        y -= yVel;
    }
}

void Dot::show()
{
    //Show the dot
    apply_surface( x, y, dot, screen );
}

Timer::Timer()
{
    //Initialize the variables
    startTicks = 0;
    pausedTicks = 0;
    paused = false;
    started = false;
}

void Timer::start()
{
    //Start the timer
    started = true;

    //Unpause the timer
    paused = false;

    //Get the current clock time
    startTicks = SDL_GetTicks();
}

void Timer::stop()
{
    //Stop the timer
    started = false;

    //Unpause the timer
    paused = false;
}

void Timer::pause()
{
    //If the timer is running and isn't already paused
    if( ( started == true ) && ( paused == false ) )
    {
        //Pause the timer
        paused = true;

        //Calculate the paused ticks
        pausedTicks = SDL_GetTicks() - startTicks;
    }
}

void Timer::unpause()
{
    //If the timer is paused
    if( paused == true )
    {
        //Unpause the timer
        paused = false;

        //Reset the starting ticks
        startTicks = SDL_GetTicks() - pausedTicks;

        //Reset the paused ticks
        pausedTicks = 0;
    }
}

int Timer::get_ticks()
{
    //If the timer is running
    if( started == true )
    {
        //If the timer is paused
        if( paused == true )
        {
            //Return the number of ticks when the timer was paused
            return pausedTicks;
        }
        else
        {
            //Return the current time minus the start time
            return SDL_GetTicks() - startTicks;
        }
    }

   //If the timer isn't running
    return 0;
}

bool Timer::is_started()
{
    return started;
}

bool Timer::is_paused()
{
    return paused;
}

int main( int argc, char* args[] )
{
    //Quit flag
   bool quit = false;

    //The dot that will be used
    Dot myDot;

    //The frame rate regulator
    Timer fps;

    //Initialize
    if( init() == false )
    {
        return 1;
    }    

    //Load the files
    if( load_files() == false )
    {
        return 1;
    }

    //While the user hasn't quit
    while( quit == false )
    {
        //Start the frame timer
       fps.start();

       //While there's events to handle
        while( SDL_PollEvent( &event ) )
        {
            //Handle events for the dot
            myDot.handle_input();

            //If the user has Xed out the window
            if( event.type == SDL_QUIT )
            {
                //Quit the program
                quit = true;
            }
        }

        //Move the dot
        myDot.move();

        //Fill the screen white
        SDL_FillRect( screen, &screen->clip_rect, SDL_MapRGB( screen->format, 0xFF, 0xFF, 0xFF ) );

        //Show the dot on the screen
        myDot.show();

        //Update the screen
        if( SDL_Flip( screen ) == -1 )
        {
           return 1;
        }

        //Cap the frame rate
        if( fps.get_ticks() < 1000 / FRAMES_PER_SECOND )
        {
            SDL_Delay( ( 1000 / FRAMES_PER_SECOND ) - fps.get_ticks() );
        }
    }

    //Clean up
    clean_up();

    return 0;
}

Upvotes: 0

Views: 790

Answers (1)

Mario
Mario

Reputation: 36487

You're losing microseconds (possibly milliseconds) due to rounding, which causes the odd behaviour. I. e. your logic doesn't run at a constant rate.

What you can do is something like the following (consider it pseudo code, because I'm a bit outdated with SDL):

unsigned int lag = 0;

while (running) { // main loop
    // do event handling here
    lag += passed_time;
    reset_timer();

    // This loop is a bit more complicated:
    // - Do up to 'max_runs' logic steps
    // - Do this while the remaining time that passed since last frame is bigger than the time for one frame ('frame_time')
    // - Every loop, increase the number of runs done by 1 and sub the time for one frame
    // - For a 100 Hz simulation (100 logic steps per second), 'frame_time' would be 10.
    // - For a 60 Hz simulation (60 logic steps per second), 'frame_time' would be 17.
    for (int runs = 0; runs < max_runs && lag > frame_time; ++runs, lag -= frame_time) {
        // do game logic here
    }
    // Drop additional logic steps we couldn't process
    lag = lag % frame_time;

    // do render stuff here

    sleep(1); // Sleep for 1 millisecond to avoid having a 'passed_time' of 0 on very fast systems (depends on your timer resolution)
}

The better your timer resolution, the smoother your game will run. Although this logic should keep the "lag" to a minimum. Using timing this way you also save up "waiting" to get a specific framerate. You can still limit it, but you could as well just enable VSync, which would be the better approach.

Edit: What this code actually does is pretty simple:

  • First, it adds the time that passed since last iteration to the time we ignored in last iteration.
  • This gives you a time window since the last logic step (logic step could be anything related to game logic, like moving the player, moving bullets, adding or removing objects, etc.).
  • For every (e.g.) 10 milliseconds in this timeframe, run the logic steps once. If 50 ms passed, you run the game logic 5 times to catch up. If less than 10 ms passed, you don't do anything at all.
  • To avoid locking up slower computers that can't catch up (unable to process the given number of iterations per second), there's a maximum number of iterations to catch up. The modulo operator (%) a few lines later removes that overhead that got dropped.
  • To avoid computers that are too fast doing nothing (due to rounding there's never time added up) the whole loop waits for 1 ms, so there's always at least 1 ms to add.

To get the time passed (in milliseconds):

unsigned int timestamp = SDL_GetTicks(); // on startup or right before entering your main loop

// when you need the time passed:
unsigned int time_now = SDL_GetTicks(); // get current time
unsigned int time_passed = time_now - timestamp; // get time difference/time passed
timestamp = time_now; // essentially reset the timer

You might still lose some microseconds that way, but you should be fine (better than waiting).

Edit: I haven't had a look at the new SDL, so you might have to use some method instead of the C style function. Just have a look at the documentation.


Here's a example that doesn't require any additional libraries (it's Windows only, not sure which system you're working on). Keep in mind that it's just some quick code I've put together (compiles fine using MinGW, MSVC probably, too).

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <cstdio>

__int64 frequency = 0;
__int64 last_time = 0;

double lag = 0;
double stat_lag = 0;

unsigned int render_steps = 0;
unsigned int logic_steps = 0;

const unsigned char target_updates_per_second = 100;
const unsigned char target_frames_per_second = 60;

// reset or initialize the timer
void reset_timer(__int64 *timer) {
    QueryPerformanceFrequency((PLARGE_INTEGER)&frequency);
    QueryPerformanceCounter((PLARGE_INTEGER)timer);
}

// get the time since last query and reset the timer for next iteration
double get_delta(__int64 *timer) {
    __int64 temp; // stores the new timestamp
    __int64 delta; // stores the difference
    QueryPerformanceCounter((PLARGE_INTEGER)&temp);
    delta = temp - *timer;
    *timer = temp;
    return (double)delta / frequency;
}

int main(int argc, char *argv[]) {
    reset_timer(&last_time);

    const unsigned int logic_frame_time = 1000 / target_updates_per_second;
    const unsigned int render_frame_time = 1000 / target_frames_per_second;

    while (1) {
        double delta = 1000 * get_delta(&last_time); // get the time that passed since last iteration (multiplied with 1000 for milliseconds)

        lag += delta; // here we add the time passed to the lag counter/timer
        stat_lag += delta; // here we do the same for the fps/ups counter

        for (char runs = 0; runs < 10 && lag > logic_frame_time; ++runs, lag -= logic_frame_time) {
            ++logic_steps; // here game logic would happen

            Sleep(0); // increase this number to simulate more load during logic steps
        }

        // lag = lag % logic_frame_time;
        // i made it the manual way to keep the double value, which is more precise here
        while (lag > logic_frame_time)
            lag -= logic_frame_time;

        if (stat_lag > 1000) { // if more than 1 second passed
            printf("updates per second: %u\nframes per second: %u\n\n", logic_steps, render_steps);

            // reset the stats
            logic_steps = 0;
            render_steps = 0;

            // stat_lag = stat_lag % 1000;
            // as above, doing it the manual way for double
            while (stat_lag > 1000)
                stat_lag -= 1000;
        }

        ++render_steps; // here rendering would happen

        // here, vsync would slow down processing, lowering the number of frames per second!
        //Sleep(...);

        Sleep(1); // avoid 0 milliseconds passing; this also causes rendering to be limited to 1000 fps!
    }
}

Upvotes: 2

Related Questions