При создании игр частенько могут потребоваться интервалы времени, чтобы нужные нам команды выполнялись не мгновенно, а через какой-то промежуток времени. Например, как в предыдущей статье, можно сделать цикл, который выполняется бесконечно, но каждое его выполнение происходит через определённое количество миллисекунд.
Если же мы работаем с игровым движком типа Unity, то у нас появляется еще один вариант отсчёта времени. Движок добавляет такое понятие, как кадры. Каждый раз, когда изображение формируется и отрисовывается на экране, отсчитывается время одного кадра. Как только кадр полностью отрисовался, то начинает отрисовываться следующий кадр, в котором что-то на экране может поменяться, а может и не поменяться ничего.
Время отрисовки кадра тоже измеряется в миллисекундах, но эта величина непостоянная. Чёрный экран с курсором отрисуется например за 1 миллисекунду, а сложное трехмерное изображение с эффектами отрисуется за 20 миллисекунд. При этом, чтобы получить комфортные глазу 60 кадров в секунду, нужно уложиться в 16 миллисекунд для отрисовки каждого кадра.
Игра - это живая картинка. На экране что-то происходит - персонажи двигаются, снаряды летят, эффекты переливаются, камера поворачивается и так далее. Во многих случаях, нет смысла выполнять наш код чаще, чем изображение на экране обновиться, либо нужно привязать выполнение кода ко времени, а потому игровой движок нам предоставляет готовую функцию, которая будет выполнять записанный в неё код каждый кадр.
Update() - метод, который выполняется каждый кадр.
Также игровой движок Unity предоставляет нам метод Start(), который выполняется в момент запуска игры, либо в момент активации самого скрипта. Метод Start() выполняется раньше самого первого выполнения метода Update() и позволяет прописать какую-то исходную логику, с которой потом можно работать каждый кадр. Например, исходные параметры или ссылки.
Когда мы только создаём наш первый скрипт в Unity, то эти два метода прописаны по умолчанию.
Готовые решения в C#
Методы Start() и Update() по умолчанию внедряются в наш код путём добавления библиотеки UnityEngine с помощью оператора using в самом начале нашего скрипта. Иными словами мы используем пространство имён UnityEngine.
Подключение библиотек с помощью using позволяет использовать классы, методы и поля из другого скрипта (из другой программы). Можно сказать, что мы таким образом подключаем к нашему коду некий уже заготовленный функционал.
Это отличный способ не писать свою программу с абсолютного нуля и распространенная практика в программировании.
Использование Unity - это использование готового решения, чтобы не прописывать графический движок с нуля, например, на языке C#.
Язык программирования C#, как язык высокого уровня, это в свою очередь готовое решение, позволяющее не писать программу на языке низкого уровня и машинных кодах, а пользоваться удобными операторами и функциями облегчающими жизнь программисту.
Языки низкого уровня и компьютер - это тоже готовое решение, позволяющее нам не изобретать "велосипед". :) И даже они пользуются готовыми решения предоставленными природой и физикой.
При создании игры на Unity мы будем использовать многие готовые решения, предоставляемые нам игровым движком. Например, класс Time.
Время в игре и Time.deltaTime
Unity предоставляет нам полезную функцию Time.deltaTime, которая даёт нам значение времени в секундах между предыдущим кадром и текущим. Т.е. фактически время, за которое последний кадр был отрисован.
Поскольку время отрисовки кадров величина непостоянная, то и каждое выполнение метода Update() будет происходить через разные интервалы времени. Это оптимально для того, чтобы например к каждому следующем кадру успеть посчитать количество здоровья главного героя и вывести его на экран. Но вот при работе с движением будут проблемы.
Если кто помнит, то легендарная игра Doom 1993 года работала на разных процессорах с разной скоростью. Например, на моём 286-ом персонаж и враги передвигались довольно медленно, а у моего двоюродного брата на 386-ом, в том же Думе, все перемещалось чуть ли не в 2 раза быстрее. Такое наблюдалось во многих играх. В некоторые старые игры невозможно играть на новых компьютерах из-за того, что они работают нереально быстро и нужны специальные эмуляторы.
Разные процессоры и разные видеокарты будут выдавать различное количество кадров в секунду (FPS - Frame Per Second).
Чтобы движение в игре происходило с постоянной скоростью вне зависимости от мощности компьютера и количества кадров в секунду, мы должны использовать Time.deltaTime при расчётах времени, а соответственно и скорости.
Таким образом мы легко можем сделать таймер или секундомер, которые будут изменяться в соответствии с реальными секундами.
Чтобы посчитать расстояние, зная скорость, нам нужно умножить скорость на время. Время в нашем случае - это время отрисовки кадра. Пройденное расстояние тоже будет рассчитываться каждый кадр. Вне зависимости от количества кадров в секунду за 1 секунду расстояние будет 1 метр. Таким образом, скорость движения на любом устройстве будет одинакова. Справедливость! :)
К реальному времени мы можем в своей игре привязать изменение параметров персонажа, время перезарядки оружия или способности, время какого-нибудь события в игре, внутриигровое время, скорость движения монстров и много всего другого.
Главное не перегрузить метод Update() большим количеством затратных в плане ресурсов компьютера операций. Иначе каждый кадр в игре будет требовать много вычислительной мощности и это повлияет на производительность игры. Количество кадров (fps) в игре снизится. В основном fps в игре зависит от графики, но неоптимизированный код способен усугубить ситуацию. Особенно когда дело касается мобильных игр.