Frame Independent Movement

11

Eu li dois outros tópicos aqui sobre movimento: movimento baseado no tempo versus movimento baseado na taxa de quadros? , e quando devo usar uma etapa de tempo fixo ou variável?

mas acho que não tenho um entendimento básico do movimento independente de quadros, porque não entendo do que esses tópicos estão falando.

Estou acompanhando os tutoriais SDL do lazyfoo e deparei-me com a lição independente de estrutura. http://lazyfoo.net/SDL_tutorials/lesson32/index.php

Não tenho certeza do que a parte do movimento do código está tentando dizer, mas acho que é isso (por favor, corrija-me se estiver errado): Para ter um movimento independente do quadro, precisamos descobrir a que distância um objeto ( por exemplo, sprite) se move dentro de um determinado período de tempo, por exemplo, 1 segundo. Se o ponto se move a 200 pixels por segundo, preciso calcular quanto ele se move nesse segundo, multiplicando 200 pps por 1/1000 de segundo.

Isso está certo? A lição diz:

"velocidade em pixels por segundo * tempo desde o último quadro em segundos. Portanto, se o programa for executado a 200 quadros por segundo: 200 pps * 1/200 segundos = 1 pixel"

Mas ... pensei que estávamos multiplicando 200 pps por 1/1000 de segundo. O que é esse negócio com quadros por segundo?

Eu apreciaria se alguém pudesse me dar uma explicação um pouco mais detalhada de como o movimento independente de estrutura funciona.

Obrigado.

ADIÇÃO:

SDL_Rect posRect;
posRect.x = 0;
posRect.y = 0;

float y, yVel;
y = 0;
yVel = 0;

Uint32 startTicks = SDL_GetTicks();

bool quit = false;
SDL_Event gEvent;

while ( quit == false )
{
    while ( SDL_PollEvent( &gEvent ) )
    {
        if ( gEvent.type == SDL_QUIT )
            quit = true;
    }

    if ( y <= 580 )
    {
        yVel += DOT_VEL;
        y += (yVel * (SDL_GetTicks() - startTicks)/1000.f);
        posRect.y = (int)y;
    }

    startTicks = SDL_GetTicks();
    SDL_BlitSurface( bg, NULL, screen, NULL );
    SDL_BlitSurface( dot, NULL, screen, &posRect );
    SDL_Flip( screen );
}

Esse é o código que move um ponto na tela. Eu acho que tenho tudo correto até agora. Ele se move para baixo na tela, mas há uma coisa estranha que eu não consigo explicar. O ponto deve permanecer no valor y = 580 quando chegar a um valor superior ao valor y. No entanto, toda vez que executo o programa, o ponto acaba em um local diferente, o que significa um pouco mais que 580, portanto, o ponto está na metade ou mais da metade da tela (o ponto é 20 pixels, tela dimensões 800x600). Se eu fizer algo como clicar e segurar a barra de título do programa e soltar, o ponto desaparecerá da tela. Como é que é variável a cada vez? Quanto ao problema da barra de título, acho que é porque quando eu seguro a barra de título, o temporizador ainda está em execução e o tempo decorrido aumenta, resultando em uma distância maior, o ponto se move no próximo quadro. Isso está certo?

Camarão Crackers
fonte
Sua adição é realmente outra questão. Você deve fazer uma segunda pergunta em vez de adicioná-la à sua já existente. Porém, pode ser respondido facilmente: basta calcular o movimento y, por exemplo. yMovement = (yVel * (SDL_GetTicks() - startTicks)/1000.f);então faça:if(y + yMovement <= 580){ y += yMovement; } else { y = 580; }
bummzack

Respostas:

10

NOTA: Todas as frações são destinadas a serem flutuantes.

O movimento independente do quadro funciona baseando o movimento fora do tempo. Você obtém a quantidade de tempo gasto desde o último quadro (portanto, se houver 60 quadros em um segundo, cada quadro leva 1,0 / 60,0 segundos, se todos os quadros levam a mesma quantidade de tempo) e descobre quanto movimento esse traduz para.

Se você deseja que sua entidade mova uma certa quantidade de espaço por uma unidade de tempo especificada (diremos 100 pixels a cada segundo), você poderá descobrir quantos pixels você deve mover por quadro, multiplicando a quantidade de movimento por segundo (100 pixels) pela quantidade de tempo passado em segundos (1.0 / 60.0) para descobrir quanto movimento deve ocorrer no quadro atual.

Ele funciona para descobrir quanto movimento você deve executar por quadro usando a quantidade de tempo decorrido e uma velocidade definida com alguma unidade de tempo (segundos ou milissegundos são preferíveis). Portanto, seu cálculo pode parecer com:movementPerSecond * (timeElapsedInMilliseconds / 1000.0)

Espero que isso faça algum sentido para você.

Eu quero mover meu cara 200 pixels para a direita a cada segundo. Se o quadro atual for executado 50 milissegundos após o quadro anterior, devo mover minha pessoa uma fração da velocidade especificada anteriormente (que era de 200 pixels). Eu deveria movê-lo 50 / 1000th da distância, porque apenas 1/20 (50/1000 = 1/20) de tempo passou. Portanto, faria sentido movê-lo apenas 10 pixels (se mais 19 quadros ocorressem, a 50 milissegundos de distância um do outro, a quantidade total de movimento naquele segundo seria de 200 pixels, a quantidade que desejávamos).

A maneira como o movimento independente de quadro funciona é que os quadros geralmente ocorrem em etapas de tempo variáveis ​​(existe uma quantidade diferente de tempo entre os quadros subsequentes). Se movermos constantemente uma entidade a uma distância constante a cada quadro, o movimento será baseado na taxa de quadros. Se houver muito tempo entre os quadros, o jogo parecerá muito lento e se não houver muito tempo entre os quadros, o jogo parecerá estar indo rápido. (muito pouco tempo entre os quadros = muitos quadros = mais movimento) Para superar isso, usamos uma velocidade em termos de tempo e controlamos o tempo entre os quadros. Dessa forma, sabemos quanto tempo se passou desde a última atualização da posição e quanto mais devemos mover a entidade.

Quadros por segundo: é a quantidade de quadros que ocorre por segundo. Normalmente, uma taxa de quadros é quantas vezes o jogo é desenhado / renderizado por segundo ou quantas vezes o loop do jogo é concluído por segundo.

Etapa de tempo variável do verso fixo: refere-se à quantidade de tempo entre os quadros. Normalmente, o tempo entre os quadros não será constante. Certos sistemas / núcleos como a física precisarão de alguma unidade de tempo para simular / executar alguma coisa. Geralmente, os sistemas de física são mais estáveis ​​/ escaláveis ​​se o intervalo de tempo for fixo. A diferença entre as etapas de tempo fixo / variável está nos nomes. Etapas de tempo fixo são o que parecem: etapas de tempo que ocorrem em uma taxa fixa de tempo. Etapas de tempo variável são etapas de tempo que ocorrem em taxas de tempo variáveis ​​/ diferentes.

Michael Coleman
fonte
No exemplo que você dá, 50 milissegundos é o tempo de cada quadro, correto? E isso foi calculado por 1000 / FPS? E então o movimento que você precisa fazer para cada quadro é de pixels por segundo * 50/1000?
ShrimpCrackers
hm, percebi que os milissegundos para cada período de tempo provavelmente seriam variáveis, não é? Algo como getTicks () - startTicks sempre seria diferente e não constante.
ShrimpCrackers
@ Omnion: Se você especificar a distância em "pixels por segundo", não poderá usar milissegundos ... deve ser 1,0 / 60,0 e não 1000/60, o que resultaria em algo completamente diferente.
bummzack
@ShrimpCrackers sim, o tempo decorrido muda. Imagine um PC mais antigo que não é capaz de renderizar 60 fps. Você ainda deseja que o jogo seja executado na mesma velocidade (mas não nos mesmos fps) em uma máquina dessas.
bummzack
então, no tutorial lazyfoo, o que 1000 significa em deltaticks / 1000.f? FPS? 1000 milissegundos? Estou um pouco confuso agora. Parece que o FPS é necessário para determinar o tempo necessário para cada quadro, mas que na verdade não é calculado para o movimento.
ShrimpCrackers
7

Na dinâmica de quadros, seu código para (por exemplo) mover uma entidade ficaria assim:

x = x + speedPerFrame

Se você quiser ser independente de quadros, pode ser assim:

x = x + speedPerSecond * secondsElapsedSinceLastFrame
Wouter Lievens
fonte
obrigado, isso faz sentido. Eu tenho outra pergunta acima.
ShrimpCrackers
1

Em relação à pergunta adicional.

Seu ponto para em locais diferentes a cada vez, porque você não verifica o limite (y> 580) quando o move. Você só para de atualizá-lo mais uma vez nos últimos 580.

No último quadro antes de cruzar 580, você pode começar de 579 ou 570 ou 100. Você também pode avançar 1 pixel ou 1000 pixels, dependendo de quanto tempo o último quadro demorou para ser executado.

Basta alterar sua condição IF para algo assim e você deve ficar bem.

if ( y <= 580 )
{
    yVel += DOT_VEL;
    y += (yVel * (SDL_GetTicks() - startTicks)/1000.f);
    if( y > 580 )
    {
        y = 580;
    }
    posRect.y = (int)y;
}
Tim O'Neil
fonte