Co się stanie, gdy Time.time stanie się bardzo duży w Unity?


37

Jak powiedziano tutaj :

Czas czas

Czas na początku tej ramki (tylko do odczytu). Jest to czas w sekundach od rozpoczęcia gry.

I jak wiem czas jest przechowywany w liczbach zmiennoprzecinkowych. Moje pytanie brzmi: co stanie się, gdy wartość czasu stanie się bardzo duża? Czy to może się przepełnić? Czy straci precyzję i spowoduje błędy? Czy gra po prostu się zawiesi czy co?



5
@AlexandreVaillancourt Myślę, że tutaj jest użyteczne pytanie, jeśli zinterpretujemy je jako „Czy występują problemy, gdy Time.time robi się bardzo duży?” w przeciwieństwie do skupiania się dosłownie na samym „przepełnieniu”. Zredagowałem pytanie zgodnie z tymi wskazówkami, aby odpowiedzieć na Twoje opinie.
DMGregory

2
@DMGregory Ale odpowiedź na to pytanie została już zaakceptowana ... Zgadzam się, że wprowadzone zmiany stanowią bardziej przydatne pytanie, nieco spóźnione.
MichaelHouse

Próbowałem sformułować zmianę, aby zaakceptowana odpowiedź była nadal poprawną odpowiedzią na edytowaną wersję. (W końcu już mówi o kwestiach precyzji) Nie obraziłbym się, gdyby został wycofany.
DMGregory

Odpowiedzi:


62

Istnieje rzeczywiste ryzyko utraty dokładności czasu przy użyciu pływaka o pojedynczej precyzji .

Nadal zachowa dokładność z dokładnością do sekundy przez 97 dni . Ale w grach często zależy nam na dokładności rzędu czasu trwania klatki.

Wartość pływalności pojedynczej precyzji w sekundach zaczyna tracić milisekundową dokładność po około 9 godzinach .

To już nie jest poza możliwością pojedynczej sesji gry lub pozostawienia gry z „AFK”, gdy jesteś w pracy / szkole lub śpisz. (Jest to jeden z powodów, dla których powszechnym sposobem testowania gry jest uruchamianie jej przez noc i sprawdzanie, czy nadal gra poprawnie rano)

Na nowoczesnych konsolach, w których gry są często zawieszane i wznawiane między sesjami gry, a nie zamykane całkowicie, nie jest niespodzianką, że „pojedyncza sesja” z perspektywy gry przekroczy 9 ​​godzin czasu działania, nawet w krótkich seriach.

Zakładając, że jedność deltaTimejest obliczana z deltaTimedokładniejszego źródła czasu, co potwierdzają eksperymenty Petera w innej odpowiedzi, poleganie na taktowaniu w skali klatki jest względnie bezpieczne (po prostu zachowaj ostrożność przy sumowaniu bardzo długich czasów trwania przez zsumowanie deltaTimewartości - dodając małe przyrosty do Większy pływak to klasyczny przepis na utratę precyzji, nawet jeśli kompensujesz zręczne algorytmy ).

Ponieważ fixedDeltaTimezachowuje tę samą wartość, którą ustawiłeś, zamiast dynamicznie zmieniać klatkę, możesz także ustawić zachowanie zależne od czasu w FixedUpdate, aby uzyskać silniejsze gwarancje spójności. deltaTimeautomatycznie zwróci odpowiednią stałą różnicę w tych metodach. Chociaż pomiędzy aktualizacjami ustalonymi i ramkami może występować częstotliwość uderzeń, można to wygładzić poprzez interpolację .

Czego chcesz uniknąć, obliczając czasy trwania, odejmując jeden znacznik czasu od drugiego . Po wielu godzinach gry może to spowodować katastrofalne anulowanie, co prowadzi do znacznie mniejszej precyzji niż na początku biegu. Jeśli porównywanie znaczników czasu jest ważne dla twojego systemu, możesz wygenerować własną wartość czasu w wyższej rozdzielczości przy użyciu innych metod, takich jak System.Diagnostics.Stopwatchzamiast Time.time.


Unreal decyduje się także na ujawnienie czasu gry jako zmiennoprzecinkowe pojedynczej precyzji, zarówno w kodzie, jak i planach . Możesz obejść to z sygnaturami czasowymi w czasie rzeczywistym i tworzyć własne rozszerzenie czasu, tak samo jak w Unity. (Uważaj tylko, że typ znacznika czasu Unreal jest oparty na 32-bitowej epoce ). Rozszerzenia istnieją dla C # w Unreal, jeśli tak wybierzesz.
DMGregory

Dla [ms] zaczynasz tracić precyzję wokół pasma 16 484 - 32 768. Oznacza to, że możesz stracić precyzję po 4h 30min , po 9h na pewno nie możesz temu ufać.
xmedeko

Technicznie od 4,5 do 9 godzin masz precyzję z dokładnością do 0,98 milisekund - zdecydowałem się oflagować punkt, w którym błąd przekracza 1,00 milisekundy. Ale cel jest słuszny - precyzja pogarsza się przez cały czas, więc kod, który wymaga większej precyzji, może zacząć źle funkcjonować nawet wcześniej.
DMGregory

16

Maksymalna wartość pływaka wynosi 3,40282347 * 10 ^ 38, co odpowiada 10 ^ 31 lat w przypadku pomiaru w sekundach (lub 10 ^ 28 lat w przypadku pomiaru w milisekundach). Zaufaj mi, to się nie przepełni.

Jedyne, co może się pojawić, to niedokładność. Ale liczba zmiennoprzecinkowa pojedynczej precyzji ma dokładność 7-8 cyfr dziesiętnych. Jeśli używasz go do pomiaru sekund, jest dokładny przez około 194 dni. Pomiar milisekund jest dokładny tylko przez 4,5 godziny. To zależy całkowicie od dokładności, której potrzebujesz, i być może będziesz musiał znaleźć alternatywne sposoby, jeśli chcesz być dokładny do milisekundy (czego prawdopodobnie nie).


7
Wolfram Alpha daje wyobrażenie o tym, jak wiele lat może być warte wspomnienia: jest to o około 20 wielkości wyższe niż obecny wiek wszechświata. Nie dwadzieścia razy wyższy, ale obecny wiek plus jeszcze dwadzieścia dodatkowych zer.
doppelgreener

1
@ Draco18s Zależy od sposobu, w jaki jedność mierzy deltaTime, ale jeśli wezmą punkt czasowy dla każdej klatki i odejmą te dwie, wydaje mi się, że odpowiedź jest zdecydowanie tak.
LukeG

7
Ta odpowiedź w ogóle nie jest poprawna. Możesz przeprowadzić eksperyment. Możesz zwiększać liczbę zmiennoprzecinkową jeden po drugim w pętli. Po osiągnięciu 10 000 000 przestanie rosnąć. Powodem jest to, że nie możesz poprawnie dodawać małych liczb do dużych liczb, gdy masz do czynienia z liczbą zmiennoprzecinkową. Prawidłowa odpowiedź została udzielona przez @DMGregory
Seagull

2
@Seagull Nigdzie nie piszę o zwiększaniu. Faktem jest, że maksymalna liczba reprezentowana przez liczbę zmiennoprzecinkową wynosi (około) 3,4 * 10 ^ 38, co wyklucza przepełnienie w ścisłym tego słowa znaczeniu (w rozumieniu OP). Nie rozumiem, dlaczego odpowiedź byłaby nieprawidłowa, podobnie jak stoi?
LukeG

2
@ Mewa Myślę, że odpowiedź LukeG jest poprawna (nawet przed wprowadzeniem poprawek w edycjach) - jego odpowiedź i moja odnoszą się tylko do różnych klas potrzeb. Lubię zastanawiać się nad precyzją zmiennoprzecinkową i ograniczaniem przypadków, podczas gdy LukeG patrzy na ten problem nieco szerzej, z mniejszym naciskiem na wymagające precyzji.
DMGregory

12

Ile precyzji potrzebuje Twoja gra?

32-bitowa liczba zmiennoprzecinkowa ma 24 bity precyzji. Innymi słowy, w chwili t dokładność wynosi ± 2–23 × t. (To nie jest dokładnie poprawne, ale jest blisko, a dokładne szczegóły nie są strasznie interesujące.)

  • Jeśli potrzebujesz dokładności 1ms, to po 1ms ÷ 2 -23 = 8400 s = 2h 20m, liczba zmiennoprzecinkowa 32-bitowa jest już nie do przyjęcia. Precyzja 1 ms nie jest konieczna w większości gier, ale jest uważana za niezbędną w zastosowaniach muzycznych.

  • Jeśli twoja gra działa na monitorze 144 Hz, a chcesz grafiki z dokładnością do klatek, to po 1 ÷ 144 Hz ÷ 2 -23 = 58000 s = 16h 32-bitowa liczba zmiennoprzecinkowa nie jest już akceptowana. 16h to długi czas na uruchomienie gry, ale nie jest to niemożliwe. Założę się, że znaczny procent z nas prowadził grę przez 16 godzin w jednym odcinku - nawet jeśli tylko dlatego, że zostawiliśmy grę uruchomioną i przespaliśmy się. W tym momencie aktualizacje animacji i fizyki zostałyby zredukowane do poniżej 144 Hz.

Jeśli Unity's Time.deltaTimejest obliczany na podstawie zegara o wyższej precyzji, możesz go użyć i nadal uzyskać pełną precyzję. Nie wiem czy to prawda czy nie.

Dygresja

Gry i inne programy w systemie Windows często wykorzystują GetTickCount()czas. Ponieważ używa 32-bitowej liczby całkowitej do zliczania milisekund, zawija się co około 50 dni, jeśli pozostawisz komputer na tak długo. Podejrzewam, że wiele gier zawiesiłoby się, zawiesiło lub źle działało w dziwny sposób, gdybyś grał w nie po 50 dniach.

Liczby całkowite, takie jak Windows GetTickCount(), mają ryzyko przepełnienia, podczas gdy zmiennoprzecinkowe, podobnie jak Unity Time.time, mogą stracić precyzję.


10

Pozostałe odpowiedzi tylko spekulują, więc napisałem prosty projekt Unity i pozwoliłem mu działać przez chwilę. Po kilku godzinach jest to wynik:

  • UnityEngine.Time.timedość szybko traci dokładność. Zgodnie z oczekiwaniami, po uruchomieniu gry przez 4 godziny wartości skaczą o 1 milisekundę, a następnie niedokładność wzrasta.
  • UnityEngine.Time.deltaTimezachowuje swoją początkową dokładność poniżej milisekund, nawet po kilku godzinach działania Unity. Jest więc praktycznie gwarantowane, że deltaTimepochodzi od zależnego od platformy licznika wysokiej rozdzielczości (który zwykle przepełnia się po 10-1000 latach). Istnieje niewielkie ryzyko, które deltaTimena niektórych platformach nadal straci precyzję.

Niedokładność Czasu jest problemem wszystkiego, od czego zależy UnityEngine.Time.time. Jednym konkretnym przykładem jest rotacja (np. Wiatraka), na której zwykle opiera się UnityEngine.Mathf.Sin(UnityEngine.Time.time*rotationSpeed). Jeszcze bardziej problem _Timepolega na jawnym używaniu animacji przez shadery. Jak wysoka musi być niedokładność, zanim zacznie być zauważalna? Dokładność 20-30 ms (2 dni) powinna być punktem, w którym zauważą ludzie, którzy są świadomi problemu i zwracają szczególną uwagę. Po 50-100 ms (5-10 dniach) wrażliwe osoby, które nie wiedzą o problemie, zaczną go rozgryzać. Od 200 do 300 ms (20-30 dni) problem będzie oczywisty.

Do tej pory nie zostały przetestowane dokładność _SinTime, _CosTimei Unity_DeltaTime, więc nie mogę wypowiedzieć się na temat tych.

To jest skrypt, którego użyłem do testowania. Jest on dołączony do UI.Textelementu, Ustawienia odtwarzacza projektu pozwalają na uruchomienie projektu w tle, a VSync w Ustawieniach jakości jest ustawiony na Co sekundę V Puste:

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;       
    }
}

wprowadź opis zdjęcia tutajwprowadź opis zdjęcia tutajwprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj

Jeśli zastanawiasz się nad 0.033203wartością, jestem pewien, że tak naprawdę 0.033203125ma binarną reprezentację zmiennoprzecinkową 00111101000010000000000000000000. Zwróć uwagę na wiele 0s w mantysie.

Obejścia

Większość gatunków nie wymaga obejścia. Większość graczy nawet nie zagra w grę przez 100 godzin, więc inwestowanie pieniędzy w celu rozwiązania problemu, który ludzie zauważą dopiero po hipotetycznym kilku dniach ciągłego grania, jest ekonomicznie nieopłacalne. Niestety nie mogę wymyślić prostego, uniwersalnego obejścia, które naprawia cały problem bez istotnych wad.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.