
В пятом дневнике мы расскажем, как создаем игровые механики. В первом дневнике Вы уже видели часть из них.
Большая часть механик написана с помощью системы визуального программирования Blueprint движка Unreal Engine.

Как работает Автомобильная заправочная станция

Игрок приезжает на заправку, подъезжает к нужной ему заправочной колонке, в зависимости от типа топлива и стороны лючка топливного бака, срабатывает событие Overlap.

- Событие OnComponentBeginOverlap вызывается, когда игрок попал в триггер заправочной колонки, запускается дальнейшая логика.
- Макрос CarTagsRefuel получает данные от машины, какой у неё тип топлива, и с какой стороны у неё располагается лючок топливного бака.
- Макрос DataRefuelColumn получает данные от макроса CarTagsRefuel, и сравнивает их с заданными параметрами заправочной колонки.
- Функция SendingDataGasStation формирует и отправляет все данные в логику заправки.
- После формирования данных инициализируется скрипт заправки.
Инициализация скрипта пройдена и скрипт подписался на событие RefuelStatus через ноду BindEvent to RefuelStatus.
Теперь, когда данное событие вызовется из другого любого места, сработает событие в нашем скрипте. Это называется Event Dispatcher.

- Как только сработало событие, на которое подписан скрипт, вызывается пост-событие Refuel и запускается цепочка проверок.
- Сперва проходит проверка состояния переменной RefuelButtonHovered (нажата кнопка или нет) через ноду Branch. Эта нода проверяет, удовлетворяет ли проверяемое значение каким-либо заданным условиям, в данном случае проходит проверка на то, является ли значение переменной RefuelButtonHovered ИСТИНОЙ или ЛОЖЬЮ. Если кнопка нажата, проверка проходит успешно, т.к. переменная возвращает значение True (ИСТИНА), и цепочка продолжается.
Проверка через макрос CheckStatusCar, внутри которой проверяется следующее:

- Первые две ноды Branch проверяют, заглушен ли двигатель и установлен ли стояночный тормоз. Если проверка проходит, макрос пропускает ход скрипта дальше.
- В случае если условия не выполняются, то макрос просто ждёт выполнения действий через Delay (нода временной задержки).
Далее идет проверка и выполнение действий через макрос TankFilling, внутри которого проверяется следующее:

- Инициализируется переменная подтверждения заправки и выставляется в значение False (ЛОЖЬ). На данном этапе наш скрипт уже готов к завершению заправки.
- После, текущий уровень топлива записывается в переменную BackupFuelLevel и хранится там до окончания заправки.
- В переменной FuelLevelConverted ранее уровень заполненности бака сформировался в десятичную структуру, т.е. уровень бака у нас измеряется не в литрах, а в долях от 0 до 1. После этого через ноду Branch проверяется, наполнился ли бак до 1 целой.
- Далее скрипт через ноду Branch снова проверяет, нажата ли по-прежнему кнопка заправки.
После предыдущих проверок запускается ряд исполнительных блоков, в которых происходят чистые математические операции с интервалом вычислений, равным 0.1 секунды.

- Блок AddLitreInProgressBar анимирует уровень заполненности бака внутри кнопки "Заправка". Текущее значение заполненности бака суммируется с силой подачи топлива.
- Блок DisplayFuelLitre отправляет в пользовательский интерфейс количество заправленного топлива.
- Блок AddFuelInCar добавляет топливо в бак автомобиля. Каждые 0.1 секунды в значение уровня бака отправляется по 0.1 литру топлива.
- Блок FilledFuel вычисляет сколько игрок залил топлива в бак. Из текущей заполненности бака отнимается значение ранее заданной переменной BackupFuelLevel.
- Блок SumLitre формирует и распределяет данные о заправке, отправляет данные в логику заправки и в логику автомобиля.
Как только игрок отпускает кнопку заправки, у него есть возможность завершить процесс заправки и подтвердить количество заправленного топлива. При нажатии кнопки завершения выполняется логика подтверждения.

- В ранее заданную переменную BackupFuelLevel записывается новое значение уровня бака.
- Закрывается пользовательский интерфейс.
- Выдаётся пользовательское сообщение об успешной заправке.

Как работает Станция технического обслуживания

Когда игрок заезжает в зону СТО, инициализируется скрипт логики СТО.

- Функция SetClassWidgetRepairStation создаёт ссылку на класс пользовательского интерфейса внутри СТО.
- Функция SetClassWidgetRepairStationStatus создаёт ссылку на класс статуса автомобиля.
- Скрипт подписывается на событие RepairEnded для получения события выхода игрока из СТО.
Игрок заехал в ремонтный бокс, запускается логика сбора и формирования данных для использования в дальнейших цепочках логики.

- Функция SizeModeWidget запускает режим полного доступа к функциям СТО. Затем отображаем на экране курсор мыши, за это отвечает переменная ShowMouseCursor
Вызов события AddDetailList запускает цепочку логики, которая формирует и сортирует детали автомобиля в пользовательский интерфейс


- Через конструкцию ForEachLoop идёт перебор всех значений из массива PartsList. Это массив, который содержит в себе список всех деталей автомобиля.
- Далее каждая деталь разбивается на отдельные значения, которые в свою очередь отправляются по нужным им путям в соответствующую переменную.
- Функция CreateWidget создает элемент интерфейса, в который передаются полученные ранее значения из списка деталей.
- Внутри функции SorterDetailList полученные детали сортируются по разным функциональным блокам: "Кузов", "Ходовая часть", "Электрика", "Двигатель".
- Функция SortResourceDetail сортирует список деталей по степени их износа.
- Для дальнейших расчётов по каждой конкретной детали переменная VehiclePart записывает в себя данные об имени детали, степени её износа и продолжительности ремонта.
Вызов события AddDetailImage формирует блок визуального отображения статуса автомобиля.

- Через конструкцию ForEachLoop снова идёт перебор всех значений из массива PartsList.
- Далее из данных о детали мы получаем её изображение через конструкцию Break Structure_PartDetail.
- Через функцию AddChild добавляется новое место в блоке пользовательского интерфейса, в которое отправляется изображение.
- Получаем переменную DetailImageBrush из созданного места в блоке интерфейса, в которую отправляем ранее полученное изображение.
- Функция SetColorDetail задаёт цвет отправленному изображению, в зависимости от его износа.
Вызов события ActivateModeUI активирует режим взаимодействия с СТО.

- Функция SetInputModeUIOnly задаёт условие, при котором игрок может взаимодействовать только с пользовательским интерфейсом.
- Функция StopMovementImmediately останавливает физическую симуляцию автомобиля игрока.
- Функция SetViewTargetWithBlend меняет камеру наблюдения за игроком. Текущую камеру отключает и переключается на камеру СТО.
В событии SendDataRepair вызывается функция SendingDataRepair, которая отправляет данные об игроке, машине и деталях в пользовательский интерфейс.

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

- Событие ButtonRepairPressed_Event срабатывает, если игрок зажал кнопку выбора конкретной детали. В свою очередь, это событие вызывает логику ремонта одной детали. Внутри логики проверяется выбранная деталь и ремонтируется, значение износа сбрасывается.
- Событие PartSelected_Event срабатывает, если игрок нажал на конкретную деталь один раз. Выбранная деталь запишется в чек лист для последующего ремонта.
- Событие ButtonRepairDetails_Event срабатывает, если игрок нажал на кнопку "Починить выбранные детали". Это событие проверяет, какие детали были выбраны игроком при срабатывании события PartSelected_Event и выполняет функцию, аналогичную функции починки одной детали.
- Событие RepairFullDetails_Event срабатывает, если игрок нажал на кнопку "Починить все детали". Логика выполнит проверку всех деталей и найдёт те, ресурс которых ниже 90%. Далее все эти детали отправляются в функцию ремонта.
После завершения ремонта игрок может покинуть СТО. В этом случае скрипт выполнит ряд завершающей логики:
- Закроет режим СТО. Игрок больше не может взаимодействовать с пользовательским интерфейсом СТО.
- Очистит временные переменные.
- Очистит списки деталей и данные о деталях.

Как работает Автомойка

Система инициализации скрипта на автомойке идентична остальным интерактивным объектам. Как только игрок подъезжает к мойке и нажимает на кнопку подтверждения взаимодействия - ворота открываются и загораются сигнальные лампы. На данном этапе мойка ожидает попадания автомобиля в моечный бокс.
Мы реализовали систему трассировки лучей в автомойке вместо заготовленных для каждого автомобиля анимаций. Это позволяет унифицировать логику работы вне зависимости от типа и формы автомобиля. По сути, при такой реализации логика корректно работает с абсолютно любым объектом, который попадает в моечный бокс. Невидимые лучи отслеживают форму кузова и управляют движением всех механизмов.

- рассчитывает длину луча, который будет отслеживать форму кузова, а также проверяет радиус щетки, чтобы геометрия щетки не пересекались с геометрией автомобиля.МакросEstimationVectorTrace
- "выстреливает" луч для отслеживания точки его пересечения с геометрией кузова автомобиля, после чего отправляет координаты, в которых произошло пересечение, дальше.ФункцияLineTraceByChannel
- получает полученные координаты пересечения и перемещает в эту точку щетку.ФункцияSetWorldLocation
Трассировка лучей используется в логике всех движимых механизмов моечного комплекса:
- Моечные щетки
- Моечная машина
- Привод с моечными форсунками
Также, на автомойке есть возможность пропустить процесс мойки автомобиля, для этого мы добавили возможность выпить кофе.

- срабатывает, когда игрок нажмёт на кнопку "Выпить кофе".СобытиеClickedButtonCoffee_Event
- выполнит логику один раз, без возможности нажать кнопку повторно в течение текущего сеанса мойки.ОператорDoOnce
- рассчитывает возможность использования пропуска мойки.МакросCalculateCoffee
- , выполнение которого запускает процесс пропуска мойки. Экран затемняется, щетки возвращаются в исходную позицию, отключается моечный бокс. Получив одобрение от макроса, вызывается событиеForceEnded
После завершения мойки автомобиля сбрасывается степень загрязнение кузова, открываются ворота. Игрок теперь может продолжить игру, покинув моечный бокс.

Как работает Гараж

Гараж - единственное место, где при въезде в зону игрок должен выполнить полную остановку транспорта, а именно:
- поставить машину на ручной тормоз;
- выключить фары;
- заглушить двигатель;
- отстегнуться.
Это реализовано следующим образом:

- получает ссылку на машину, а также проверяет ее начальное состояние. При въезде в гараж у игрока появляется виджет с перечнем необходимых действий.Blueprint
- Когда у машины меняется состояние, она через интерфейс передает, какой параметр у нее изменился и на что.
- После чего в виджете отмечается новое состояние машины и проверяется, все ли условия выполнил игрок.
- И если все условия выполнены - запускается основная логика гаража.

Здесь машина игрока добавляется в массив машин гаража (под нулевым индексом), а сам игрок получает возможность использовать функционал гаража.
Кровать — позволяет перемотать время, вызывая функцию в отдельном классе, контролирующем время на уровне.

- AddTime — функция в классе LevelTime, прибавляющая время к текущему состоянию, которое игрок указал в виджете.

В следующем выпуске мы не будем много разговаривать и подготовим для Вас набор скриншотов проекта за всё время его развития.
Дневники разработки выходят раз в две недели. Не пропускайте!
С какой-то стороны даже обидно. Стараются ребята, но даже если не брать в расчет оптимизацию, игра выглядит прям очень неприятно. Все ресурсы, как команды, так и ПК, уходят на детализацию салона.
У команды есть группа в вк с активной обратной связью https://vk.com/avtosimulator. Думаю, если обозначать заранее какие-то нюансы, то получится их избежать или исправить. А так, очевидно, что команда небольшая и основной упор сейчас идет на реализацию функционала, физики и геймплейных фич. Наверняка внешний аспект и производительность будет еще дорабатываться.