Fiz algumas perguntas semelhantes nos últimos 8 meses sem muita alegria, por isso vou tornar a pergunta mais geral.
Eu tenho um jogo Android que é o OpenGL ES 2.0. dentro dele eu tenho o seguinte Game Loop:
Meu loop funciona em um princípio de etapa de tempo fixo (dt = 1 / ticksPerSecond )
loops=0;
while(System.currentTimeMillis() > nextGameTick && loops < maxFrameskip){
updateLogic(dt);
nextGameTick+=skipTicks;
timeCorrection += (1000d/ticksPerSecond) % 1;
nextGameTick+=timeCorrection;
timeCorrection %=1;
loops++;
}
render();
Minha integração funciona assim:
sprite.posX+=sprite.xVel*dt;
sprite.posXDrawAt=sprite.posX*width;
Agora, tudo funciona da maneira que eu gostaria. Posso especificar que eu gostaria que um objeto se movesse por uma certa distância (digamos, a largura da tela) em 2,5 segundos e isso será feito. Também devido ao salto de quadros que eu permito no loop do jogo, eu posso fazer isso em praticamente qualquer dispositivo e sempre levará 2,5 segundos.
Problema
No entanto, o problema é que, quando um quadro de renderização é ignorado, o gráfico é interrompido. É extremamente irritante. Se eu remover a capacidade de pular quadros, tudo ficará tranquilo como você gosta, mas será executado em velocidades diferentes em dispositivos diferentes. Portanto, não é uma opção.
Ainda não sei ao certo por que o quadro pula, mas gostaria de salientar que isso não tem nada a ver com desempenho insatisfatório . Retornei o código a 1 sprite minúsculo e sem lógica (além da lógica necessária para mover o sprite) e ainda recebo quadros ignorados. E isso está em um tablet Google Nexus 10 (e, como mencionado acima, eu preciso de pulos de quadros para manter a velocidade consistente em todos os dispositivos).
Portanto, a única outra opção que tenho é usar a interpolação (ou extrapolação), li todos os artigos existentes, mas nenhum realmente me ajudou a entender como funciona e todas as minhas tentativas de implementação falharam.
Usando um método, eu consegui fazer as coisas funcionarem sem problemas, mas era impraticável porque atrapalhava minha colisão. Posso prever o mesmo problema com qualquer método semelhante, porque a interpolação é passada para (e atuada dentro) o método de renderização - no momento da renderização. Portanto, se a Colisão corrigir a posição (o personagem está agora próximo à parede), o renderizador pode alterar sua posição e desenhá-lo na parede.
Então, eu estou realmente confuso. As pessoas disseram que você nunca deve alterar a posição de um objeto de dentro do método de renderização, mas todos os exemplos online mostram isso.
Então, eu estou pedindo um empurrão na direção certa, por favor, não vincule aos artigos populares sobre loop de jogos (deWitters, Corrija seu timestep, etc.), já que li várias vezes . Estou não pedir a ninguém para escrever meu código para mim. Apenas explique por favor em termos simples como a Interpolação realmente funciona com alguns exemplos. Depois, tentarei integrar quaisquer idéias ao meu código e farei perguntas mais específicas, se necessário - mais adiante. (Tenho certeza de que esse é um problema com o qual muitas pessoas lutam).
editar
Algumas informações adicionais - variáveis usadas no loop do jogo.
private long nextGameTick = System.currentTimeMillis();
//loop counter
private int loops;
//Amount of frames that we will allow app to skip before logic is affected
private final int maxFrameskip = 5;
//Game updates per second
final int ticksPerSecond = 60;
//Amount of time each update should take
private final int skipTicks = (1000 / ticksPerSecond);
float dt = 1f/ticksPerSecond;
private double timeCorrection;
fonte
Respostas:
Há duas coisas cruciais para que o movimento pareça suave: a primeira é obviamente que o que você renderiza precisa corresponder ao estado esperado no momento em que o quadro é apresentado ao usuário; o segundo é que você precisa apresentar quadros ao usuário em um intervalo relativamente fixo. Apresentar um quadro em T + 10ms, depois outro em T + 30ms e outro em T + 40ms, parecerá que o usuário está tremendo, mesmo que o que é realmente mostrado para esses tempos esteja correto de acordo com a simulação.
Parece que seu loop principal não possui nenhum mecanismo de bloqueio para garantir que você renderize apenas em intervalos regulares. Então, às vezes você pode fazer três atualizações entre renderizações, às vezes você pode fazer 4. Basicamente, seu loop será renderizado o mais rápido possível, assim que você tiver simulado tempo suficiente para enviar o estado da simulação à frente do horário atual, então renderize esse estado. Mas qualquer variabilidade em quanto tempo leva para atualizar ou renderizar, e o intervalo entre os quadros também varia. Você tem um timestep fixo para sua simulação, mas um timestep variável para sua renderização.
O que você provavelmente precisa é de uma espera antes da renderização, para garantir que você só comece a renderizar no início de um intervalo de renderização. Idealmente, isso deve ser adaptável: se você demorou muito para atualizar / renderizar e o início do intervalo já passou, renderize imediatamente, mas também aumente a duração do intervalo, até que você possa renderizar e atualizar consistentemente e ainda assim conseguir a próxima renderização antes do intervalo terminar. Se você tiver bastante tempo de sobra, poderá reduzir lentamente o intervalo (ou seja, aumentar a taxa de quadros) para renderizar mais rapidamente novamente.
Mas, e aqui está o kicker, se você não renderizar o quadro imediatamente após detectar que o estado da simulação foi atualizado para "agora", introduza o aliasing temporal. O quadro que está sendo apresentado ao usuário está sendo apresentado levemente na hora errada, e isso por si só parecerá uma gagueira.
Esse é o motivo do "intervalo de tempo parcial" que você verá mencionado nos artigos que leu. Está lá por uma boa razão, e isso porque, a menos que você fixe seu timestap de física em algum múltiplo integral fixo do seu timestap de renderização fixo, você simplesmente não poderá apresentar os quadros no momento certo. Você acaba apresentando-os muito cedo ou muito tarde. A única maneira de obter uma taxa de renderização fixa e ainda apresentar algo fisicamente correto é aceitar que, no momento em que o intervalo de renderização chegar, você provavelmente estará no meio do caminho entre duas de suas etapas fixas de física. Mas isso não significa que os objetos sejam modificados durante a renderização, apenas que a renderização precisa estabelecer temporariamente onde os objetos estão, para que possa renderizá-los em algum lugar entre onde eles estavam antes e onde estão após a atualização. Isso é importante - nunca mude o estado mundial para renderização, apenas as atualizações devem mudar o estado mundial.
Então, para colocá-lo em um loop de pseudocódigo, acho que você precisa de algo mais como:
Para que isso funcione, todos os objetos que estão sendo atualizados precisam preservar o conhecimento de onde eles estavam antes e onde estão agora, para que a renderização possa usar seu conhecimento de onde o objeto está.
Vamos traçar uma linha do tempo em milissegundos, dizendo que a renderização leva 3ms para ser concluída, a atualização leva 1ms, o tempo de atualização é fixado em 5ms e o tempo de renderização começa (e permanece) em 16ms [60Hz].
Há outra nuance aqui sobre a simulação muito adiantada, o que significa que as entradas do usuário podem ser ignoradas, mesmo que tenham ocorrido antes da renderização do quadro, mas não se preocupe com isso até ter certeza de que o loop está simulando sem problemas.
fonte
O que todos estão dizendo a você está correto. Nunca atualize a posição de simulação do seu sprite em sua lógica de renderização.
Pense assim, seu sprite tem 2 posições; onde a simulação diz que ele está na última atualização da simulação e onde o sprite é renderizado. São duas coordenadas completamente diferentes.
O sprite é renderizado em sua posição extrapolada. A posição extrapolada é calculada em cada quadro de renderização, usado para renderizar o sprite e depois jogado fora. É tudo o que há para isso.
Fora isso, você parece ter um bom entendimento. Espero que isto ajude.
fonte