Почему 80 кадров в секунду на 4 пикселя на кадр запаздывают для меня?

так что я разрабатываю арканоид и движение отстает от меня.
я не знаю, почему он отстает, потому что я слышал, что 60 кадров в секунду — это глазная шапка.
любые предложения о том, что я должен делать, потому что я не хочу программировать игры на 100 кадров в секунду
потому что у меня меньше времени для обработки моих расчетов, если я могу запрограммировать их на 60 кадров в секунду?
вот мой код рамки кэппинга.

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

и вот мой весь код, если ваш любопытный

/*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;
}

0

Решение

Вы теряете микросекунды (возможно, миллисекунды) из-за округления, которое вызывает странное поведение. И. е. ваша логика не работает с постоянной скоростью.

Что вы можете сделать, это что-то вроде следующего (рассмотрите это псевдокод, потому что я немного устарел с 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)
}

Чем лучше разрешение по таймеру, тем плавнее будет ваша игра. Хотя эта логика должна сводить «лаг» к минимуму. Используя синхронизацию таким образом, вы также экономите «ожидание», чтобы получить конкретную частоту кадров. Вы все еще можете ограничить его, но вы также можете просто включить VSync, что будет лучшим подходом.

Редактировать:
То, что на самом деле делает этот код, довольно просто:

  • Во-первых, он добавляет время, прошедшее с последней итерации, к времени, которое мы игнорировали на последней итерации.
  • Это дает вам временное окно с момента последнего логического шага (логическим шагом может быть все, что связано с игровой логикой, например, перемещение игрока, перемещение пуль, добавление или удаление объектов и т. Д.).
  • Для каждых (например) 10 миллисекунд в этом временном интервале, выполните логические шаги один раз. Если прошло 50 мс, вы запускаете игровую логику 5 раз, чтобы наверстать упущенное. Если прошло менее 10 мс, вы вообще ничего не делаете.
  • Чтобы избежать блокировки более медленных компьютеров, которые не могут догнать (не могут обработать заданное количество итераций в секунду), существует максимальное количество итераций, которые нужно наверстать. Оператор по модулю (%) несколькими строками позже удаляет потерянные накладные расходы.
  • Чтобы избежать слишком быстрой работы компьютеров (из-за округления никогда не добавляется время), весь цикл ожидает 1 мс, поэтому всегда нужно добавить как минимум 1 мс.

Чтобы узнать, сколько времени прошло (в миллисекундах):

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

Таким образом, вы все равно можете потерять несколько микросекунд, но с вами все будет в порядке (лучше, чем ждать).

Изменить: я не смотрел на новый SDL, поэтому вам, возможно, придется использовать какой-то метод вместо функции стиля C. Просто посмотрите на документацию.


Вот пример, который не требует никаких дополнительных библиотек (это только Windows, не знаю, на какой системе вы работаете). Имейте в виду, что это просто какой-то быстрый код, который я собрал (прекрасно компилируется с использованием MinGW, возможно, и MSVC).

#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!
}
}
2

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]