Há uma longa história de como chegamos a esta convenção comum, com muitos desafios fascinantes ao longo do caminho, então tentarei motivá-la em etapas:
1. Problema: os dispositivos funcionam em velocidades diferentes
Você já tentou jogar um jogo DOS antigo em um PC moderno, e ele é executado de maneira incrivelmente rápida - apenas um borrão?
Muitos jogos antigos tinham um loop de atualização muito ingênuo - eles coletavam entradas, atualizavam o estado do jogo e renderizavam o mais rápido que o hardware permitia, sem levar em conta quanto tempo havia passado. O que significa que, assim que o hardware muda, a jogabilidade muda.
Geralmente, queremos que nossos jogadores tenham uma experiência consistente e uma sensação de jogo em vários dispositivos (desde que atendam a algumas especificações mínimas), estejam usando o telefone do ano passado ou o modelo mais novo, um desktop de jogo de última geração ou um laptop de nível intermediário.
Em particular, para jogos competitivos (para vários jogadores ou através de tabelas de classificação), não queremos que jogadores rodando em um dispositivo específico tenham vantagem sobre os outros, porque podem correr mais rápido ou ter mais tempo para reagir.
A solução infalível aqui é bloquear a taxa na qual fazemos atualizações de estado de jogo. Dessa forma, podemos garantir que os resultados serão sempre os mesmos.
2. Então, por que não bloquear a taxa de quadros (por exemplo, usando o VSync) e ainda executar as atualizações e a renderização do estado da jogabilidade no lockstep?
Isso pode funcionar, mas nem sempre é agradável ao público. Houve muito tempo em que correr a 30 fps sólidos era considerado o padrão-ouro para jogos. Agora, os jogadores esperam rotineiramente 60 fps como a barra mínima, especialmente em jogos de ação multiplayer, e alguns títulos mais antigos agora parecem visivelmente agitados conforme nossas expectativas mudam. Há também um grupo vocal de jogadores de PC, em particular, que se opõem a framerate bloqueios. Eles pagaram muito pelo seu hardware de ponta e querem poder usar esse músculo computacional para obter a renderização mais suave e com maior fidelidade possível.
Em VR, em particular, a taxa de quadros é o rei, e o padrão continua subindo. No início do recente ressurgimento da RV, os jogos costumavam rodar em torno de 60 fps. Agora 90 é mais padrão, e harware como o PSVR está começando a suportar 120. Isso pode continuar a aumentar ainda. Portanto, se um jogo de VR limitar sua taxa de quadros ao que é possível e aceito hoje, é provável que seja deixado para trás à medida que o hardware e as expectativas se desenvolverem ainda mais.
(Como regra geral, tenha cuidado ao saber que "os jogadores não conseguem perceber nada mais rápido que XXX", pois geralmente é baseado em um tipo específico de "percepção", como reconhecer um quadro em sequência. A percepção da continuidade do movimento geralmente é muito mais sensível.)
A última questão aqui é que um jogo usando uma taxa de quadros bloqueada também precisa ser conservador - se você chegar a um momento no jogo em que está atualizando e exibindo um número incomumente alto de objetos, não quer perder seu quadro prazo e causar uma gagueira perceptível ou um engate. Portanto, você precisa definir seu orçamento de conteúdo baixo o suficiente para deixar espaço livre ou investir em recursos de ajuste dinâmico de qualidade mais complicados para evitar atrelar toda a experiência de jogo ao pior desempenho possível em hardware com especificações mínimas.
Isso pode ser especialmente problemático se os problemas de desempenho aparecerem mais tarde no desenvolvimento, quando todos os seus sistemas existentes forem construídos e ajustados, assumindo uma taxa de quadros de renderização em etapas que agora você nem sempre consegue. A dissociação das taxas de atualização e renderização oferece mais flexibilidade para lidar com a variabilidade de desempenho.
3. A atualização em um intervalo de tempo fixo não tem os mesmos problemas que (2)?
Eu acho que essa é a essência da pergunta original: se dissociarmos nossas atualizações e, às vezes, renderizarmos dois quadros sem atualizações de estado do jogo, então não será o mesmo que renderização de passo a passo em uma taxa de quadros mais baixa, já que não há alterações visíveis a tela?
Na verdade, existem várias maneiras diferentes de os jogos usarem o desacoplamento dessas atualizações:
a) A taxa de atualização pode ser mais rápida que a taxa de quadros renderizada
Como tyjkenn observa em outra resposta, a física em particular é frequentemente aumentada com uma frequência maior do que a renderização, o que ajuda a minimizar erros de integração e fornece colisões mais precisas. Portanto, em vez de ter 0 ou 1 atualizações entre os quadros renderizados, você pode ter 5, 10 ou 50.
Agora, o jogador que renderiza a 120 fps pode obter 2 atualizações por quadro, enquanto o jogador com renderização de hardware de especificação mais baixa a 30 fps recebe 8 atualizações por quadro, e ambos os jogos rodam na mesma velocidade de jogo-ticks-per-realtime-second speed. O melhor hardware faz com que pareça mais suave, mas não altera radicalmente o funcionamento da jogabilidade.
Há um risco aqui de que, se a taxa de atualização for incompatível com a taxa de quadros, você poderá obter uma "frequência de batida" entre os dois . Por exemplo. na maioria dos quadros, temos tempo suficiente para 4 atualizações de estado do jogo e um pouco de sobra; então, de vez em quando, economizamos o suficiente para fazer 5 atualizações em um quadro, dando um pequeno salto ou gaguejando no movimento. Isso pode ser resolvido por ...
b) Interpolar (ou extrapolar) o estado do jogo entre as atualizações
Aqui, geralmente deixamos o estado do jogo viver um timestamp fixo no futuro e armazenamos informações suficientes dos 2 estados mais recentes para que possamos render um ponto arbitrário entre eles. Então, quando estivermos prontos para mostrar um novo quadro na tela, combinamos com o momento apropriado apenas para fins de exibição (por exemplo, não modificamos o estado de jogo subjacente aqui)
Quando bem feito, o movimento fica suave e até ajuda a mascarar alguma flutuação na taxa de quadros, desde que não caiamos muito .
c) Adicionando suavidade às alterações de estado que não são de jogo
Mesmo sem interpolar o estado da jogabilidade, ainda podemos obter vitórias de suavidade.
Alterações puramente visuais, como animação de personagens, sistemas de partículas ou efeitos visuais e elementos da interface do usuário como o HUD, geralmente são atualizados separadamente do tempo fixo do estado da jogabilidade. Isso significa que, se estamos marcando nosso estado de jogo várias vezes por quadro, não estamos pagando o custo com cada tick - apenas no passe de renderização final. Em vez disso, escalamos a velocidade de reprodução desses efeitos para coincidir com o comprimento do quadro, para que sejam reproduzidos da maneira mais suave que a taxa de quadros de renderização permitir, sem afetar a velocidade ou a equidade do jogo, conforme discutido em (1).
O movimento da câmera também pode fazer isso - especialmente em VR, às vezes, mostramos o mesmo quadro mais de uma vez, mas o reprojetamos para levar em conta o movimento da cabeça do jogador no meio , para que possamos melhorar a latência e o conforto percebidos, mesmo que possamos nativamente processa tudo tão rápido. Alguns sistemas de streaming de jogos (nos quais o jogo está rodando em um servidor e o jogador executa apenas um thin client) também usam uma versão disso.
4. Por que não usar esse estilo (c) para tudo? Se funcionar para animação e interface do usuário, não podemos simplesmente escalar nossas atualizações de estado de jogo para corresponder à taxa de quadros atual?
Sim * isso é possível, mas não, não é simples.
Essa resposta já é um pouco longa, então não vou entrar em todos os detalhes sangrentos, apenas um resumo rápido:
Multiplicar por deltaTime
trabalhos para ajustar as atualizações de comprimento variável para alterações lineares (por exemplo, movimento com velocidade constante, contagem regressiva de um temporizador ou progresso ao longo de uma linha do tempo de animação)
Infelizmente, muitos aspectos dos jogos não são lineares . Mesmo algo tão simples quanto a gravidade exige técnicas de integração mais sofisticadas ou sub-passos de alta resolução para evitar resultados divergentes em diferentes taxas de quadros. A entrada e o controle do player são, por si só, uma enorme fonte de não linearidade.
Em particular, os resultados da detecção e resolução de colisões discretas dependem da taxa de atualização, levando a erros de tunelamento e tremulação se os quadros ficarem muito longos. Portanto, uma taxa de quadros variável nos obriga a usar métodos de detecção de colisão contínua mais complexos / caros em mais de nosso conteúdo ou tolerar a variabilidade em nossa física. Até a detecção contínua de colisões enfrenta desafios quando objetos se movem em arcos, exigindo intervalos de tempo mais curtos ...
Portanto, no caso geral de um jogo de média complexidade, manter um comportamento consistente e equitativo por meio de deltaTime
dimensionamento é algo entre muito difícil e intensivo em manutenção, até totalmente inviável.
A padronização de uma taxa de atualização permite garantir um comportamento mais consistente em várias condições , geralmente com código mais simples.
Manter essa taxa de atualização separada da renderização nos dá flexibilidade para controlar a suavidade e o desempenho da experiência sem alterar a lógica do jogo .
Mesmo assim , nunca obtemos uma independência de quadros verdadeiramente "perfeita", mas, como muitas abordagens nos jogos, nos fornece um método controlável para discarmos em direção ao "suficientemente bom" para as necessidades de um determinado jogo. É por isso que é comumente ensinado como um ponto de partida útil.