O que acontece quando o Time.time fica muito grande no Unity?

37

Como é dito aqui :

Time.time

A hora no início desse quadro (somente leitura). Este é o tempo em segundos desde o início do jogo.

E como eu sei, o tempo é armazenado em flutuação. Então, minha pergunta é o que acontecerá quando o valor do tempo se tornar muito grande? Pode transbordar? Vai perder precisão e causar bugs? O jogo vai travar ou o quê?

Yaroslav
fonte
6
Apenas por contexto, veja o Guinness World Record na maratona de videogame mais longa .
Theraot
5
@AlexandreVaillancourt Acho que há uma pergunta útil aqui se a interpretarmos como "Há problemas quando o Time.time fica muito grande?" em vez de focar literalmente apenas no "estouro". Editei a pergunta ao longo destas linhas para tentar responder aos seus comentários.
DMGregory
2
@DMGregory Mas a pergunta já foi respondida e aceita ... Concordo que suas edições contribuem para uma pergunta mais útil, um pouco tarde.
MichaelHouse
Tentei fazer a edição da frase para que a resposta aceita ainda seja a resposta correta para a versão editada. (Afinal, ele já fala sobre problemas de precisão) Eu não me ofenderia se fosse revertido.
DMGregory

Respostas:

62

Existe um risco real de perda de precisão ao usar um flutuador de precisão única .

Ele ainda manterá a precisão no segundo mais próximo por 97 dias . Mas em jogos, geralmente nos preocupamos com a precisão na ordem da duração de um quadro.

Um valor de tempo de flutuação de precisão única em segundos começa a perder a precisão de milissegundos após cerca de 9 horas .

Isso já não está fora do campo de possibilidade de uma única sessão de jogo ou de deixar o jogo rodando "AFK" enquanto você estiver no trabalho / na escola ou dormindo. (Esse é um dos motivos pelos quais uma maneira comum de testar um jogo é executá-lo durante a noite e verificar se ele ainda é reproduzido corretamente pela manhã)

Nos consoles modernos, onde os jogos geralmente são suspensos e retomados entre as sessões de jogo, em vez de serem encerrados por completo, não é inesperado que uma "sessão única" do ponto de vista do jogo exceda 9 horas de tempo de execução, mesmo quando jogadas em rajadas curtas.

Assumindo que o Unity deltaTimeé calculado a partir de uma fonte de tempo de maior precisão, confirmada pelos experimentos de Peter em outra resposta, contar com deltaTimetempos de escala de quadros é relativamente seguro (apenas tome cuidado para acumular durações muito longas somando deltaTimevalores - adicionando pequenos incrementos a um flutuador maior é uma receita clássica para perda de precisão, mesmo quando você compensa com algoritmos mais experientes ).

Como fixedDeltaTimemantém o mesmo valor definido, em vez de mudar dinamicamente de quadro para quadro, você também pode colocar um comportamento sensível ao tempo no FixedUpdate para obter garantias mais consistentes de consistência. deltaTimeretornará automaticamente o delta fixo apropriado nesses métodos. Embora possa haver uma frequência de batida entre as atualizações fixas e de quadros, você pode suavizar isso através da interpolação .

O que você deseja evitar é calcular as durações subtraindo um registro de data e hora de outro . Após horas de jogo, isso pode causar um cancelamento catastrófico, levando a muito menos precisão do que você obtém no início da corrida. Se a comparação de carimbos de data / hora for importante para seus sistemas, você poderá gerar seu próprio valor de tempo de maior resolução usando outros métodos, como em System.Diagnostics.Stopwatchvez de Time.time.

DMGregory
fonte
A Unreal também escolhe expor o tempo do jogo como um flutuador de precisão única, tanto em código quanto em projetos . Você pode contornar isso com registros de data e hora em tempo real e fazer sua própria dilatação de tempo, da mesma forma que faria no Unity. (Apenas observe que o tipo de carimbo de data e hora da Unreal se baseia na época de 32 bits ). Existem extensões para o C # no Unreal, se você preferir.
DMGregory
Para [ms], você começa a perder precisão em torno da faixa 16 484 - 32 768. Ou seja, você pode perder precisão após 4h30min , após 9h você certamente não pode confiar nisso.
xmedeko 11/04
Tecnicamente, entre 4,5 e 9 horas, você tem precisão entre 0,98 milissegundos - escolhi sinalizar o ponto em que o erro excede 1,00 milissegundo. Mas o ponto é bem aceito - a precisão diminui o tempo todo, portanto, o código que precisa de mais precisão pode começar a se comportar mal ainda mais cedo.
DMGregory
16

O valor máximo de flutuação é 3,40282347 * 10 ^ 38, que é igual a 10 ^ 31 anos quando medido em segundos (ou 10 ^ 28 anos quando medido em milissegundos). Confie em mim, não vai transbordar.

A única coisa que pode aparecer é imprecisão. Mas um único número de ponto flutuante de precisão tem uma precisão de 7-8 dígitos decimais. Se usá-lo para medir segundos, é preciso por cerca de 194 dias. Se medir milissegundos, é preciso apenas 4,5 horas. Portanto, depende completamente da precisão de que você precisa e talvez seja necessário encontrar maneiras alternativas, se precisar de precisão de milissegundos (o que você provavelmente não precisa).

LukeG
fonte
7
O Wolfram Alpha fornece uma ideia de como é grande o número de anos que vale a pena mencionar: são aproximadamente 20 magnitudes a mais que a idade atual do universo. Não vinte vezes mais alto, mas a idade atual mais vinte zeros.
Doppelgreener
11
@ Draco18s Depende do modo como a unidade mede deltaTime, mas se eles tirarem um tempo para cada quadro e subtraírem esses dois, suponho que a resposta seja um sim definitivo.
LukeG 30/05
7
Esta resposta não está certa. Você pode executar um experimento. Você pode incrementar a flutuação uma a uma no loop. Depois de atingir 10 000 000, irá parar de aumentar. O motivo é que você não pode adicionar números pequenos a grandes números corretamente quando estiver lidando com flutuação. A resposta certa foi dada por um @DMGregory
Seagull
2
@ Seagull Em nenhum lugar eu escrevo sobre incrementar. É um fato que o número máximo representável por um float é (aproximadamente) 3,4 * 10 ^ 38, de modo que exclui o transbordamento no sentido estrito (como significa o OP). Não vejo por que a resposta estaria incorreta como está?
LukeG 30/05
2
@ Seagull Acho que a resposta de LukeG está correta (mesmo antes das melhorias nas edições) - a resposta dele e a minha se referem apenas a diferentes classes de necessidades. Gosto de me aprofundar na precisão da flutuação e na limitação de casos, enquanto o LukeG analisa a questão um pouco mais amplamente, com menos ênfase nas necessidades exatas da precisão.
DMGregory
12

Quanta precisão o seu jogo precisa?

Um flutuador de 32 bits possui 24 bits de precisão. Em outras palavras, no tempo t, a precisão é de ± 2 -23 × t. (Isso não está exatamente correto, mas está próximo e os detalhes exatos não são muito interessantes.)

  • Se você precisar de 1ms de precisão, depois de 1ms ÷ 2 -23 = 8400 s = 2h 20m, um flutuador de 32 bits não será mais aceitável. Uma precisão de 1ms não é necessária para a maioria dos jogos, mas é considerada necessária para aplicações musicais.

  • Se o seu jogo rodar em um monitor de 144 Hz e você desejar gráficos com quadros precisos, depois de 1 ÷ 144 Hz ÷ 2 -23 = 58000 s = 16h, um flutuador de 32 bits não será mais aceitável. 16h é muito tempo para rodar um jogo, mas não é inconcebível. Aposto que uma porcentagem significativa de nós já rodou um jogo por 16 horas em um único trecho - mesmo que apenas porque deixamos o jogo rodando e dormimos um pouco. Nesse ponto, as animações e atualizações de física seriam reduzidas para menos de 144Hz.

Se o Unity Time.deltaTimefor calculado a partir de um relógio de maior precisão, você poderá usá-lo e ainda obter precisão total. Não sei se isso é verdade ou não.

Nota

Jogos e outros programas no Windows costumam ser usados GetTickCount()para descobrir a hora. Como ele usa um número inteiro de 32 bits para contar milissegundos, ele ocorre a cada 50 dias, se você deixar o computador por tanto tempo. Eu suspeito que muitos jogos travariam, travariam ou se comportariam mal de maneiras bizarras se você os estivesse jogando na marca de 50 dias.

Inteiros, como o Windows GetTickCount(), correm o risco de transbordar, enquanto os flutuadores, como o Unity Time.time, correm o risco de perder precisão.

Dietrich Epp
fonte
10

As outras respostas apenas especulam, então escrevi um projeto simples do Unity e o deixei rodar por um tempo. Depois de algumas horas, este é o resultado:

  • UnityEngine.Time.timeperde a precisão rapidamente. Como esperado, depois de executar o jogo por 4 horas, os valores saltam 1 milissegundo e a imprecisão aumenta depois disso.
  • UnityEngine.Time.deltaTimemantém sua precisão inicial de menos de milissegundos, mesmo depois que o Unity estiver em execução por horas. Portanto, é praticamente garantido que deltaTimeé derivado de um contador de alta resolução dependente da plataforma (que geralmente transborda após 10-1000 anos). Ainda existe um pequeno risco que deltaTimeainda perderá precisão em algumas plataformas.

A imprecisão do tempo é um problema para tudo o que depende UnityEngine.Time.time. Um exemplo específico é a rotação (por exemplo, de um moinho de vento), que geralmente é baseada UnityEngine.Mathf.Sin(UnityEngine.Time.time*rotationSpeed). Ainda mais um problema _Timeé explicitamente usado para animações por shaders. Quão alta a imprecisão precisa ser antes de começar a ser perceptível? A precisão de 20 a 30 ms (2 dias) deve ser o ponto em que as pessoas que estão cientes do problema e prestam muita atenção notarão. Entre 50 e 100 ms (5 a 10 dias), pessoas sensíveis que não conhecem o problema começarão a descobrir o problema. De 200 a 300 ms (20 a 30 dias), o problema será óbvio.

Até agora eu não testei a precisão de _SinTime, _CosTimee Unity_DeltaTime, por isso não posso comentar sobre isso.

Este é o script que eu usei para testar. Ele está anexado a um UI.Textelemento, as Configurações do Player do projeto permitem que o projeto seja executado em segundo plano, e o VSync nas Configurações de Qualidade é definido como Cada Segundo V em Branco:

public class Time : UnityEngine.MonoBehaviour {
    void Start () {
        LastTime = (double)UnityEngine.Time.time;
        StartTime =  System.DateTime.Now;
        LastPrintTime =  System.DateTime.Now;
    }
    double LastTime;
    System.DateTime StartTime;
    System.DateTime LastPrintTime;
    void Update () {
        double deltaTimeError = (double)UnityEngine.Time.deltaTime - ((double)UnityEngine.Time.time - LastTime);
        if (System.DateTime.Now - LastPrintTime  > System.TimeSpan.FromMilliseconds(1000))
        {
            GetComponent<UnityEngine.UI.Text>().text = "StartTime: " + StartTime.ToLongTimeString()
                + "\nCurrentTime: " + System.DateTime.Now.ToLongTimeString()
                + "\n\nTime.deltaTime: " + UnityEngine.Time.deltaTime.ToString("0.000000")
                + "\nTime.time - LastTime: " + ((float)(UnityEngine.Time.time - LastTime)).ToString("0.000000")
                + "\nAbsolute Error: " +  System.Math.Abs(deltaTimeError).ToString("0.000E0");
            LastPrintTime = System.DateTime.Now;
        }
        LastTime = UnityEngine.Time.time;       
    }
}

insira a descrição da imagem aquiinsira a descrição da imagem aquiinsira a descrição da imagem aqui insira a descrição da imagem aqui

Se você está se perguntando sobre o 0.033203valor, tenho certeza de que, na verdade 0.033203125, possui uma representação binária de ponto flutuante 00111101000010000000000000000000. Observe os muitos 0s na mantissa.

Soluções alternativas

A maioria dos gêneros não precisa de uma solução alternativa. A maioria dos jogadores nem sequer joga um jogo por 100 horas no total, portanto, investir dinheiro para corrigir um problema que as pessoas só notam após alguns dias hipotéticos de tempo de jogo contínuo não é economicamente viável. Infelizmente, não posso apresentar uma solução alternativa simples e universal que conserte a totalidade do problema sem trazer algumas desvantagens significativas.

Pedro
fonte