Что происходит, когда Time.time становится очень большим в Unity?

37

Как сказано здесь :

Time.time

Время в начале этого кадра (только чтение). Это время в секундах с начала игры.

И, как я знаю, время сохраняется в float. Итак, мой вопрос: что произойдет, когда значение времени станет очень большим? Это может переполниться? Это потеряет точность и вызовет ошибки? Будет ли игра просто вылетать или как?

Ярослав
источник
6
Просто для контекста, смотрите Книгу рекордов Гиннеса на самом длинном марафоне видеоигр .
Theraot
5
@AlexandreVaillancourt Я думаю, что здесь есть полезный вопрос, если мы интерпретируем его как «Есть ли проблемы, когда Time.time становится очень большим?» в отличие от буквального сосредоточения только на «переполнении». Я отредактировал вопрос в том же духе, чтобы попытаться ответить на ваши отзывы.
DMGregory
2
@DMGregory Но на вопрос был получен ответ, и он уже принят ... Я согласен, что ваши правки внесут более полезный вопрос, но немного поздно.
MichaelHouse
Я попытался сформулировать редактирование так, чтобы принятый ответ оставался правильным ответом для отредактированной версии. (В конце концов, это уже говорит о проблемах точности), но я бы не обиделся, если бы это откатилось.
DMGregory

Ответы:

62

Существует реальный риск потери точности времени при использовании поплавка одинарной точности. .

Это все еще сохранит точность с точностью до секунды в течение 97 дней . Но в играх мы часто заботимся о точности порядка длительности кадра.

Значение времени с плавающей запятой одинарной точности в секундах начинает терять миллисекундную точность примерно через 9 часов .

Это уже не за пределами возможного для одиночной игровой сессии или оставления игры под управлением «АФК», пока вы на работе / в школе или спите. (Это одна из причин, по которой обычный способ проверить игру - это запустить ее на ночь и убедиться, что она по-прежнему играет правильно по утрам)

На современных консолях, где игры часто приостанавливаются и возобновляются между игровыми сессиями, а не закрываются полностью, неудивительно, что «один сеанс» с игровой точки зрения может превышать 9 часов времени выполнения даже при игре в короткие серии.

Предполагая, что Unity deltaTimeвычисляется из источника времени с более высокой точностью, что подтверждается экспериментами Питера в другом ответе, полагаться на deltaTimeтайминги в масштабе кадра относительно безопасно (просто будьте осторожны с накоплением очень больших длительностей путем суммирования deltaTimeзначений - добавляя небольшие приращения к большее число с плавающей запятой - это классический рецепт потери точности, даже если вы компенсируете хитрые алгоритмы ).

Так как fixedDeltaTimeсохраняет то же значение, которое вы установили, вместо того, чтобы динамически переходить от кадра к кадру, вы также можете поместить поведение, зависящее от времени, в FixedUpdate, чтобы получить более строгие гарантии согласованности. deltaTimeавтоматически вернет соответствующую фиксированную дельту в этих методах. Несмотря на то, что между обновлениями фиксированных и кадровых кадров может быть частота биений, вы можете сгладить ее с помощью интерполяции .

Чего вы хотите избежать, так это вычислять длительности, вычитая одну временную метку из другой . После нескольких часов игры это может привести к катастрофической отмене, что приведет к гораздо меньшей точности, чем в начале пробега. Если сравнение временных меток важно для ваших систем, вы можете сгенерировать собственное значение времени с более высоким разрешением, используя другие методы, например, System.Diagnostics.Stopwatchвместо Time.time.

Д.М.Григорий
источник
Unreal также выбирает выставлять игровое время как плавающее число одинарной точности, как в коде, так и в чертежах . Вы можете обойти это с метками времени в реальном времени и делать свое собственное замедление времени, так же, как в Unity. (Просто обратите внимание, что тип метки времени в Unreal основан на 32-битной эпохе ). Расширения для C # существуют в Unreal, если вы того пожелаете.
DMGregory
За [мс] вы начинаете терять точность в диапазоне 16 484 - 32 768. То есть вы можете потерять точность через 4 часа 30 минут , а через 9 часов вы наверняка не сможете этому доверять.
xmedeko
Технически между 4,5-9 часами у вас есть точность с точностью до 0,98 миллисекунд - я решил отметить точку, в которой ошибка превышает 1,00 миллисекунды. Но суть хорошо принята - точность постоянно снижается, поэтому код, который нуждается в большей точности, может начать плохо себя вести даже раньше.
DMGregory
16

Максимальное значение с плавающей запятой составляет 3.40282347 * 10 ^ 38, что равно 10 ^ 31 годам при измерении в секундах (или 10 ^ 28 лет при измерении в миллисекундах). Поверь мне, это не переполнится.

Единственное, что может появиться - неточность. Но число с плавающей запятой одинарной точности имеет точность 7-8 десятичных цифр. Если использовать его для измерения секунд, он точен в течение 194 дней. Если измерять миллисекунды, то с точностью до 4,5 часов. Таким образом, это полностью зависит от точности, которая вам нужна, и вам может понадобиться найти альтернативные способы, если вам нужно быть с точностью до миллисекунды (что вы, вероятно, не делаете).

LukeG
источник
7
Вольфрам Альфа дает представление о том, какое большое количество лет можно упомянуть: это примерно на 20 величин больше, чем нынешний возраст вселенной. Не в двадцать раз выше, но текущий возраст плюс еще двадцать нулей.
doppelgreener
1
@ Draco18s Зависит от того, как единицы измерения измеряют deltaTime, но если они берут момент времени для каждого кадра и вычитают эти два, я полагаю, что ответ определенно да.
LukeG
7
Этот ответ не является правильным на всех. Вы можете запустить эксперимент. Вы можете увеличивать float один за другим в цикле. После достижения 10 000 000 он прекратит увеличиваться. Причина в том, что вы не можете правильно добавлять маленькие числа к большим, когда имеете дело с плавающей точкой. Правильный ответ дал @DMGregory
Seagull
2
@Seagull Нигде я не пишу об увеличении. Фактом является то, что максимальное число, представимое числом с плавающей точкой (примерно) 3,4 * 10 ^ 38, так что исключается переполнение в строгом смысле (как это подразумевается в OP). Я не понимаю, почему ответ будет неверным, как есть?
LukeG
2
@Seagull Я думаю, что ответ LukeG является правильным (даже до внесения изменений в редакции) - мой ответ и мой просто относятся к различным классам потребностей. Мне нравится выговаривать о точности вычислений и ограничении случаев, в то время как LukeG смотрит на проблему более широко, с меньшим акцентом на требования точной точности.
DMGregory
12

Какая точность нужна вашей игре?

32-разрядное число с плавающей запятой имеет 24-битную точность. Другими словами, в момент времени t точность составляет ± 2 -23 × t. (Это не совсем правильно, но это близко, и точные детали не очень интересны.)

  • Если вам нужна точность в 1 мс, то через 1 мс ÷ 2 -23 = 8400 с = 2 ч 20 м 32-разрядное значение с плавающей запятой больше не допустимо. Точность 1 мс не является необходимой для большинства игр, но считается необходимой для музыкальных приложений.

  • Если ваша игра работает на мониторе с частотой 144 Гц и вам нужна графика с точностью до кадра, то через 1 ÷ 144 Гц ÷ 2 -23 = 58000 с = 16 ч 32-разрядное плавание больше не допустимо. 16 часов - это долгое время для запуска игры, но это немыслимо. Могу поспорить, что значительная часть из нас запускает игру в течение 16 часов за один раз - даже если только потому, что мы оставили игру запущенной и немного поспали. В этот момент обновления анимации и физики будут снижены до частоты ниже 144 Гц.

Если Единство Time.deltaTime рассчитывается по часам с более высокой точностью, то вы можете использовать это и при этом получить полную точность. Я не знаю, правда ли это или нет.

Примечание

Игры и другие программы на Windows часто используют GetTickCount() чтобы выяснить время. Поскольку он использует 32-разрядное целое число для подсчета миллисекунд, он оборачивается примерно каждые 50 дней, если вы оставляете свой компьютер в таком состоянии. Я подозреваю, что многие игры будут зависать, зависать или работать неправильно, если вы играете в них с 50-дневной отметкой.

Целые числа, такие как Windows GetTickCount(), рискуют переполниться, в то время как числа с плавающей точкой, как и Unity Time.time, могут потерять точность.

Дитрих Эпп
источник
10

Другие ответы только размышляют, поэтому я написал простой проект Unity и позволил ему какое-то время работать. Через пару часов это результат:

  • UnityEngine.Time.timeтеряет точность довольно быстро. Как и ожидалось, после запуска игры в течение 4 часов значения подскочат на 1 миллисекунду, и после этого погрешность возрастет.
  • UnityEngine.Time.deltaTimeсохраняет свою первоначальную точность до миллисекунды даже после нескольких часов работы Unity. Таким образом, практически гарантируется, что deltaTimeон получен из зависящего от платформы счетчика высокого разрешения (который обычно переполняется через 10-1000 лет). Существует небольшой риск, который deltaTimeвсе еще будет терять точность на некоторых платформах.

Неточность времени является проблемой для всего, от чего зависит UnityEngine.Time.time. Одним конкретным примером является вращение (например, ветряная мельница), которое обычно основано на UnityEngine.Mathf.Sin(UnityEngine.Time.time*rotationSpeed). Еще одна проблема заключается в том, _Timeчто явно используется для анимации шейдерами. Насколько высока должна быть погрешность, прежде чем она станет заметной? Точность 20-30 мс (2 дня) должна быть той точкой, на которую заметят люди, которые знают об этой проблеме и будут внимательно следить за ней. Через 50-100 мс (5-10 дней) чувствительные люди, которые не знают о проблеме, начнут ее выяснять. С 200-300 мс (20-30 дней) проблема станет очевидной.

До сих пор я не проверял точность _SinTime, _CosTimeи Unity_DeltaTime, таким образом , я не могу комментировать это.

Это скрипт, который я использовал для тестирования. Он привязан к UI.Textэлементу, настройки проигрывателя проекта позволяют запускать проект в фоновом режиме, а для VSync в настройках качества задано значение «Каждая секунда V Пусто»:

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

enter image description hereenter image description hereenter image description here enter image description here

Если вы задаетесь вопросом о 0.033203значении, я уверен, что на самом деле 0.033203125это двоичное представление с плавающей запятой 00111101000010000000000000000000. Обратите внимание на множество 0символов в мантиссе.

обходные

Most genres do not need a workaround. Most players won't even play a game for 100 hours total, so investing money to fix a problem people will only notice after a hypothetical few days of continuous playtime is economically not viable. Unfortunately I cannot come up with a simple universal workaround that fixes the entirety of the problem without carrying along some significant drawbacks.

Peter
источник