Continuando nossa série de artigos traduzidos do site lazyfoo, veremos agora como lidar com frame rates instáveis ou suportar múltiplos frame rates. Seja quando você quiser lidar com frame rates instáveis ou dar suporte para múltiplos frame rates, você pode configurar o seu movimento no tempo independente do fame rate.
//The dot that will move around on the screen class Dot { public: //The dimensions of the dot static const int DOT_WIDTH = 20; static const int DOT_HEIGHT = 20; //Maximum axis velocity of the dot static const int DOT_VEL = 640; //Initializes the variables Dot(); //Takes key presses and adjusts the dot's velocity void handleEvent( SDL_Event& e ); //Moves the dot void move( float timeStep ); //Shows the dot on the screen void render(); private: float mPosX, mPosY; float mVelX, mVelY; };
A classe dot retorna adaptada para movimento independente do frame. Observe como a velocidade agora é 640. Do jeito que fizemos a velocidade por frame anteriormente faria com que esse objeto voasse pela tela em um único frame. Nesse exemplo, iremos nos basear no tempo e a unidade padrão de tempo será 1 segundo. 640 pixels por segundo se traduz em um pouco mais do que 10 pixels por frame em uma aplicação que roda à 60 frame por segundo. Para que possamos move o objeto baseado no tempo do frame, a função que gera o movimento precisa saber quanto tempo se passa por cada frame. Por isso essa função agora recebe como parametro um passo de tempo, que é quanto tempo se passou desde a ultima atualização. Também oberve como a posição e a velocidade são do tipo float ao invés de inteiros. Se usarmos inteiros, o movimento seria sempre truncado para o inteiro mais próximo, o que causaria grandes discrepâncias de valores.
void Dot::move( float timeStep ) { //Move the dot left or right mPosX += mVelX * timeStep; //If the dot went too far to the left or right if( mPosX < 0 ) { mPosX = 0; } else if( mPosX > SCREEN_WIDTH - DOT_WIDTH ) { mPosX = SCREEN_WIDTH - DOT_WIDTH; } //Move the dot up or down mPosY += mVelY * timeStep; //If the dot went too far up or down if( mPosY < 0 ) { mPosY = 0; } else if( mPosY > SCREEN_HEIGHT - DOT_HEIGHT ) { mPosY = SCREEN_HEIGHT - DOT_HEIGHT; } }
Aqui temos a função que gera o movimento alterada para movimento baseado no tempo ao invés de movimento baseado no frame. Atualizamos a posição movendo o obejto por velocidade * tempo. Digamos que temos (para simplificar) uma velocidade de 600 * 1/60 pixels ou 10 pixels. Por causa do movimento não uniforme não podemos mover o objeto para trás quando ele vai para fora da tela, temos que corrigir o valor para um valor que esteja dentro da tela.
void Dot::render() { //Show the dot gDotTexture.render( (int)mPosX, (int)mPosY ); }
Para evitar que o compilador dispare alguma mensagem de erro, convertemos as posições para inteiros quando renderizamos o ponto.
//Main loop flag bool quit = false; //Event handler SDL_Event e; //The dot that will be moving around on the screen Dot dot; //Keeps track of time between steps LTimer stepTimer;
Para esse exemplo, desativamos vsync para mostrar que ele pode ser executado não importando o frame rate. Para podermos saber quando tempo se passou entre as renderizações, precisamos de um timer para manter um registro dos passos do tempo.
//While application is running while( !quit ) { //Handle events on queue while( SDL_PollEvent( &e ) != 0 ) { //User requests quit if( e.type == SDL_QUIT ) { quit = true; } //Handle input for the dot dot.handleEvent( e ); } //Calculate time step float timeStep = stepTimer.getTicks() / 1000.f; //Move for time step dot.move( timeStep ); //Restart step timer stepTimer.start(); //Clear screen SDL_SetRenderDrawColor( gRenderer, 0xFF, 0xFF, 0xFF, 0xFF ); SDL_RenderClear( gRenderer ); //Render dot dot.render(); //Update screen SDL_RenderPresent( gRenderer ); }
Quando movemos o ponto. primeiro obtemos o tempo do passo do tempo de forma que possamos saber quando tempo se passou desde a ultima vez que movemos o objeto. Depois que paramos de mover o objeto, reiniciamos o timer para que possamos saber quanto tempo se passou quando voltarmos a mover o objeto. Finalmente, renderizamos o objeto como fazemos normalmente. Na maioria dos artigos dessa série, as coisas são simplificadas para tornar tudo mais fácil de ser entendido. Para a maioria, se não todas, das aplicações usamos movimento baseado no tempo ao invés de baseado no frame. Mesmo quanto temos um frame rate fixo. só usamos um passo de tempo fixo. O negócio é que quando usamos movimento baseado no tempo. encontramos problemas com erros de ponto flutuante que requerem matemática vetorial para serem corrigidos, e esse assunto vai além do escopo desse artigo, e é por isso que movimento baseado no frame é usado na maioria dos outros artigos. Baixe os arquivo de mídia e de código fonte desse artigo aqui.