====== Steady Ticking ====== Почти все видео-игры (и, следовательно, игровые движку) имеют в общем случае технику итерирования игрового процесса. Игра по сути является циклом. Это цикл так и называется - //игровой цикл//. В каждой итерации игрового цикла игра должна приготовить и актуализировать своё состояние, в частности, проработать рендеринг, оценить производительность и нарисовать графику. В рамках Unreal Engine эти итерации называются //тиками (ticks)//. Это что-то наподобие сердца игры. Как и сердцебиение человека, временные интервалы тиков различаются; то есть изменяется время, необходимое для того, чтобы обработать и отрисовать новый кадр. Вы можете неожиданно получить кадр, вычислительная или графическая нагрузка на котором будет достаточно высокая, и время тика увеличится. {{ :en:toolworks:docs:apparatus:variable-ticking.svg |Variable Delta Times}} Чтобы игровая логика актуализировала состояние игры, этот меняющийся //временной интервал (delta time)// подаётся ей на вход как вещественное значение, чтобы построить новый "шаг" на основе предыдущего. В Unreal Engine это делается при помощи ноды-события ''Tick'', а конкретно, при помощи её аргумента ''Delta Seconds'': {{ :en:toolworks:docs:apparatus:ue-tick-event.png?nolink }} Техника рабочая, например, когда вы играете в игру с заранее вычисленной анимацией, где результат всегда определён во времени. К несчастью, так бывает не всегда, так как игры в большинстве своём интерактивны и нагрузка меняется от кадра к кадру. Если применить переменное время кадра к такому процессу, как просчитывание коллизий, то их устойчивость будет значительно падать, если время тика увеличится. Рассмотрим следующий пример. Пусть у нас есть шар с симуляцией физики. Шар сдвигается со своей позиции в зависимости от своей скорости (которая определяется на основе гравитации и других действующих сил). Когда время тика стабильно и достаточно мало, всё работает, как ожидалось. Но как только один из кадров "даёт сбой", система сразу же становится крайне нестабильной и мяч с лёгкостью может проскочить сквозь пол, что приводит к совершенно некорректному поведению. {{ :en:toolworks:docs:apparatus:collision-detection-instability.svg |}} Наша цель - стабилизировать время кадра и нормализовать вычисления. {{ :en:toolworks:docs:apparatus:stable-collision-detection.svg |}} В физике Unreal Engine есть способ стабилизировать время кадра. Это достигается ограничением максимального времени тика: {{ :en:toolworks:docs:apparatus:ue-max-physics-delta-time.png?nolink |}} Если текущий кадр занимает большее время, чем заданное значение, физика движка получит это значение как свой ''delta time'', для вычисления текущего шага. Это, в свою очередь, вызовет в замедление игрового времени. Что-то наподобие slow-mo эффекта, а симуляция не потеряет своей стабильности, то есть, стабильность будет не ниже, чем предоставленное допустимое значение. Другой способ достичь стабильности физики - использовать такую технику Unreal Engine, как [[https://docs.unrealengine.com/en-US/InteractiveExperiences/Physics/Substepping/index.html|sub-stepping]]. Для большей информации по этой теме можете посмотреть официальную документацию по ссылке. Apparatus предоставляет схожую sub-stepping-гу концепцию, которую мы называем //"Steady Ticking"//. Каждый [[ru:toolworks:docs:apparatus:mechanism|Mechanism]] реализовывает специальное событие ''Steady Tick'': {{ :en:toolworks:docs:apparatus:steady-tick-event.png?nolink |}} Устойчивые тики имеют фиксированный временной интервал и выполняются параллельно основным тикам (''event-tick''), у которых этот интервал может меняться. Слово "параллельно" совершенно не означает мульти-поточность, напротив, подчёркивает логику вызовов. {{ :en:toolworks:docs:apparatus:steady-and-variable-ticking.svg |}} На картинке выше вы могли заметить, что может произойти несколько устойчивых тиков за один переменный тик. Также, как и несколько переменных тиков могло произойти за один устойчивый. Такие различия могут привести к нежелательным резким перемещениям объектов (или выполнения другой логики). Чтобы решить этот вопрос нужна интерполяция. В течение переменного "родного" тика мы интерполируемся между предыдущим устойчивым тиком и следующим. Сам пользователь может выполнять необходимую интерполяцию, но Apparatus уже предоставляет требуемый функциональный базис, чтобы это сделать. Для начала рассмотрим ноду ''Steady Frame Ratio'' (соотношение устойчивых кадров): {{ :en:toolworks:docs:apparatus:steady-frame-ratio-node.png?nolink |}} Эта нода возвращает отношение текущего переменного тика к активному устойчивому. Для лучшего понимания, просто посмотрите эту схему: {{ :en:toolworks:docs:apparatus:steady-frame-ratio.svg |}} Это отношение, являясь числом из отрезка ''[0.0, 1.0]'', может быть использовано для интерполяции анимации на основе её предыдущей и следующей состояний. Чтобы этого достичь, ваши механики, выполняемые устойчивыми тиками, должны подготовить оба эти состояния в течении своего времени выполнения. Обычный же (переменный) тик может использовать ноду [[ue>BlueprintAPI/Math/Float/Lerp/index.html|Lerp]], чтобы обеспечить непосредственно гладкость и неразрывность. Иногда бывает утомительно (или довольно трудно) управлять обеими предыдущим и следующим состояниями в некоторых вспомогательных переменных. Вы, возможно, захотите использовать текущее состояние объекта, вместо предыдущего, - например, можно использовать ''transform''-вектор для вычисления следующего шага. Поэтому мы предлагаем вам метод, который удобно использовать и при таком подходе, - нода ''Steady Future Factor''. {{ :en:toolworks:docs:apparatus:steady-future-factor-node.png?nolink |}} Эта нода несколько похожа на ''Steady Frame Ratio'', по крайней мере тем, что возвращает единственное число с плавающей точкой из отрезка ''[0.0, 1.0]''. Но отличается она, на самом деле, причиной, по которой выходные значения лежат именно в таком промежутке. Проиллюстрируем это на схеме: {{ :en:toolworks:docs:apparatus:steady-future-factor.svg |}} Как вы можете видеть, последнее изменение (было оно в устойчивом или обычном тике) отслеживается и запоминается в качестве стартовой точки для отношения. Вторая часть такая же, как и для узла Steady Frame Factor, и фактически представляет собой временную метку следующего будущего устойчивого кадра/*не понял предложения, прогнал через Google-translate: The second part is the same as for the Steady Frame Factor node and actually represents a timestamp of the next future steady frame.*/. Используя ноду ''Steady Future Factor'', вы можете использовать текущее актуальное состояние как базу для [[ue>BlueprintAPI/Math/Float/Lerp/index.html|Lerp]]'а со следующим состоянием. Просто будьте внимательны к своим вычислениям и убедитесь в том, что в реально поддерживаете трек того, что вы хотите интерполировать. Обе эти ноды могут быть использованы прямо в tick-событиях ваших механик. Однако, мы имплементировали специальное событие для механик, так называемое ''Presentation Tick''-событие, которая естественным образом связывает вышеперечисленные pure-функции: {{ :en:toolworks:docs:apparatus:presentation-tick-event.png?nolink |}} Это событие наступает после нормальных переменных тиков (а значит, и после тиков устойчивых) и подразумевает использование для вычисления визуального отклика механик. Интерполяция механик, очевидно, тоже может быть вычислена. Посмотрите на этот всеобъемлющий пример механики интерполяции 3d-положения объекта (выдержка из нашего [[https://github.com/toolworks/ApparatusPlatformer|примера-платформера]]): {{ :en:toolworks:docs:apparatus:presentation-mechanic-sample.png?nolink |}} Самая важная ''Apply Offset'' функция выглядит так: {{ :en:toolworks:docs:apparatus:apply-offset-sample-implementation.png?direct |}} Завершая тему статьи, следовало бы сказать о том, что описанные техники вовсе не являются обязательными к применению. Вы можете выполнять свои механики так, как захотите, и настраивать движок так, чтобы он обрабатывал только определённые FPS. Мы же, тем не менее, предлагаем вам путь для достижения стабильной частоты кадров геймплея и также рекомендуем использовать его для поддержания стабильности.