Você deseja separar as taxas de atualização (lógica) e desenhar (renderizar).
Suas atualizações produzirão a posição de todos os objetos no mundo a serem desenhados.
Vou abordar duas possibilidades diferentes aqui, a que você solicitou, extrapolação e também outro método, a interpolação.
1
Extrapolação é onde calcularemos a posição (prevista) do objeto no próximo quadro e, em seguida, interpolaremos entre a posição atual dos objetos e a posição em que o objeto estará no próximo quadro.
Para fazer isso, cada objeto a ser desenhado deve ter um velocity
e associado position
. Para encontrar a posição em que o objeto estará no próximo quadro, basta adicionar velocity * draw_timestep
a posição atual do objeto, para encontrar a posição prevista do próximo quadro. draw_timestep
é a quantidade de tempo que passou desde o tick de renderização anterior (também conhecido como call de draw anterior).
Se você deixar assim, verá que os objetos "piscam" quando a posição prevista não corresponde à posição real no próximo quadro. Para remover a tremulação, você pode armazenar a posição prevista e o lerp entre a posição prevista anteriormente e a nova posição prevista em cada etapa do sorteio, usando o tempo decorrido desde a atualização anterior como o fator lerp. Isso ainda resultará em mau comportamento quando objetos em movimento rápido mudarem repentinamente de local, e você pode querer lidar com esse caso especial. Tudo o que foi dito neste parágrafo são as razões pelas quais você não deseja usar a extrapolação.
2)
Interpolação é onde armazenamos o estado das duas últimas atualizações e interpolamos entre elas com base na quantidade de tempo atual que se passou desde a atualização antes da última. Nesta configuração, cada objeto deve ter um position
e previous_position
. Nesse caso, nosso desenho representará, na pior das hipóteses, uma marca de atualização atrás do estado do jogo atual e, na melhor das hipóteses, exatamente no mesmo estado que a marca de atualização atual.
Na minha opinião, você provavelmente deseja interpolação como a descrevi, pois é o mais fácil de implementar e desenhar uma pequena fração de segundo (por exemplo, 1/60 segundo) atrás do seu estado atualizado atual é bom.
Editar:
Caso o exposto acima não seja suficiente para permitir a execução de uma implementação, aqui está um exemplo de como executar o método de interpolação que descrevi. Não cobrirei extrapolação, porque não consigo pensar em nenhum cenário do mundo real em que você deva preferir.
Quando você cria um objeto desenhável, ele armazena as propriedades necessárias para serem desenhadas (ou seja, as informações de estado necessárias para desenhá-lo).
Neste exemplo, armazenaremos a posição e a rotação. Você também pode armazenar outras propriedades, como cor ou posição da coordenada da textura (ou seja, se uma textura rolar).
Para impedir que os dados sejam modificados enquanto o thread de renderização o desenha (ou seja, a localização de um objeto é alterada enquanto o thread de renderização é desenhado, mas todos os outros ainda não foram atualizados), precisamos implementar algum tipo de buffer duplo.
Um objeto armazena duas cópias dele previous_state
. Vou colocá-los em uma matriz e me referir a eles como previous_state[0]
e previous_state[1]
. Da mesma forma, precisa de duas cópias current_state
.
Para rastrear qual cópia do buffer duplo é usada, armazenamos uma variável state_index
, que está disponível para os threads de atualização e de desenho.
O thread de atualização primeiro calcula todas as propriedades de um objeto usando seus próprios dados (qualquer estrutura de dados que você desejar). Em seguida, ele copia current_state[state_index]
a previous_state[state_index]
, e copia os novos dados relevantes para o desenho, position
e rotation
em current_state[state_index]
. Para fazer isso state_index = 1 - state_index
, inverter a cópia atualmente usada do buffer duplo.
Tudo no parágrafo acima deve ser feito com uma trava retirada current_state
. A atualização e os threads de desenho eliminam esse bloqueio. O bloqueio é retirado apenas durante a cópia das informações do estado, o que é rápido.
No encadeamento de renderização, você faz uma interpolação linear na posição e rotação da seguinte maneira:
current_position = Lerp(previous_state[state_index].position, current_state[state_index].position, elapsed/update_tick_length)
Onde elapsed
é a quantidade de tempo que passou no encadeamento de renderização, desde o último tick de atualização, e update_tick_length
o tempo que sua taxa fixa de atualização leva por tick (por exemplo, em atualizações de 20FPS update_tick_length = 0.05
).
Se você não sabe qual é a Lerp
função acima, consulte o artigo da wikipedia sobre o assunto: Interpolação linear . No entanto, se você não sabe o que é leitura, provavelmente não está pronto para implementar atualização / desenho desacoplado com desenho interpolado.