Дисклеймер
До прочтения этой статьи рекомендую прочитать первую часть.
План на сегодня
Сегодня мы создадим простеньких противников, который будут ходить туда-сюда. Научимся наносить им урон.
Создаем противника
Первое, что необходимо разъяснить - это то, что создавать бота мы будем с помощью наследования. Уверен, что если вы читаете эту статью, то вы базово знакомы с этой парадигмой ООП. Если же нет, то идите прочитайте пару статей по этой теме и возвращайтесь к прочтению.
Жмем ПКМ по блупринту нашего персонажа и выбираем вот этот вариант.
После этого нужно дать блупринту название. Я назвал его BP_HabrTPSBot.
Теперь нам необходимо создать так называемый AIController. В Unreal Engine есть 2 типа контроллеров. PlayerController и AIController. В дальнейшем их стоит воспринимать, как мозги, которые могут вселяться в разные тела (персонажей и Pawn объекты). PlayerController работает именно, как мозг игрока. А вот AIController - это мозг нашего противника, который будет говорить телу (персонажу), когда и что он должен делать. А сам персонаж будет определять, как он будет это делать.
Жмем ПКМ по пустому месту. И нажимаем BlueprintClass.
В списке ищем AIController и нажимаем Select.
Я дам ему название BP_HabrTPSBot_AI.
После этого нам необходимо открыть блупринт персонажа. И открыть вкладку Class Defaults.
В панели Details ищем переменную AIControllerClass. Устанавливаем туда наш новый контроллер, который мы создали в предыдущем пункте.
После этого просто DragNDrop перносим персонаж нашего бота в мир.
Учим бот ходить по случайным точкам
Для этого заходим в AIController. Заходим в EventGraph.
Нам нужен именно BeginPlay. Напомню, это ивент, который вызывается в тот момент, когда игра стартует (или когда персонаж появляется в мире).
Кликаем ПКМ и ищем вот такую ноду.
В нее нам нужно указать следующие вещи. Кого мы хотим отправить на точку, точку на которую мы хотим отправить его, остальные поля оставим пока без внимания.
В качестве того, кого мы будем отправлять на точку мы выберем Pawn (персонажа), которым управляет AIController. Мы можем его легко получить вот такой нодой.
Далее нам необходимо указать куда мы двигаемся.
Для того, чтобы выстроить навигацию, основанную на алгоритмах поиска кратчайших путей в Unreal Engine есть такая сущность, как NavMesh. Его необходимо добавить на карту, чтобы движок запек навигационную карту. Это нам небходимо, чтобы бот научился ходить к определенным точкам, которые находятся на этом самом NavMesh.
Открываем менюшку PlaceActors и ищем там NavMeshBoundsVolume. Перетаскиваем его на нашу карту. Теперь если мы нажмем латинскую кнопку P на клавиатуре, то мы увидим нам NavMesh.
Нам необходимо заскейлить NavMesh на размеры всей карты, чтобы персонаж понимал как ходить по всей карте, а не только по ее маленькому кусочку. Для этого просто меняем масштаб, как мы это уже умеем делать. По итогу мы должны увидеть нечто вот такое.
Чтобы отключить NavMesh - необходимо еще раз нажать на кнопку P на клавиатуре.
Возвращаемся к блупринту AIController. С помощью вот такой ноды найдем рандомную точку определенном радиусе от персонажа.
Остается всего-ничего. Зациклить хождение по точкам, а также добавить логику задержки на этих точках. Для этого на OnSuccess вешаем Delay (ноду, которая ждем определенное количество времени, а после этого продолжает вызовы всего, что на нее подвязано).
После чего нам необходимо прокинуть исполнение с ноды Delay но входного исполнительного пина ноды AIMoveTo. Также это необходимо сделать с пином OnFail, чтобы в случае ошибки бот нашел новую точку и начал движение к ней. В блупринтах нельзя втыкать выходящие пины с ноды в входящий пин этой же ноды, поэтому я воспользуюсь небольшой хитростью и сделаю это через еще одну ноду Sequence (она последовательно выполняет команды, которые на нее подвязаны, поэтому она не принесет никах дополнительных вызовов).
Теперь просто необходимо подключить это к ивенту BeginPlay и оно заработает.
Наносим урон
В UnrealEngine уже есть встроенная система урона, поэтому все будет достаточно просто.
Заходим внутрь нашего игрового персонажа и там, где мы делали LineTraceByChannel находим OutHit пин. Нажимаем по нему ПКМ и нажимаем SplitStructPin. Результат должен получиться следующим.
Далее необходимо найти ноду ApplyDamage и в качестве Actor, которому мы наносим Damage указать того Actor, в которого мы попали. В поле BaseDamage вписываем количество урона, которое мы хотим нанести.
Также нам необходимо включить канал Visibility у капсулы персонажа, по которой мы будем попадать. Для этого выделаем капсулу и в Details ставим следующие настройки.
Теперь нужно создать жизни. Для этого кликаем + в разделе Variables.
Новую переменную называем Health и делаем ее типа Float. Компилируем Blueprint и выставляем значение по умолчанию 100.0
Теперь нам нужно подвязать логику, которая будет отнимать у текущего запаса жизней урон и проверять умел ли наш персонаж после получения урона.
Для этого наводим на раздел Functions. Появляется выпадашка Override и там выбираем AnyDamage.
У нас появился ивент, который вызывается всякий раз, когда наш персонаж получает урон. Теперь нам необходимо отнять от текущего здоровья урон.
Если сделать DragNDrop переменной на EventGraph, то нам предложат 2 типа действий с переменной. Прочитать значение (get) и записать значение (set).
Нам нужно будет использовать оба. Делаем вычитание следующим образом.
Однако эта реализация не идеальна, потому что наше здоровье может уйти в отрицательное значение. Чтобы это предотвратить, добавим следующую конструкцию.
Таким образом значение Health никогда не будет уходить в отрицательные значения.
Далее нам необходимо добавить проверку на смерть персонажа. Делаем это при помощи следующей конструкции, используя ноду Branch (аналог IF в блупринтах).
В случае смерти мы будем просто уничтожать Actor.
Проверка функционала
Что дальше
Мы сделали самую базовую реализацию урона. Ее можно совершенствовать еще много как. Добавлять отображения попаданий, добавлять различные модификаторы урона. Более подробно мы рассматриваем это на курсе Технический Game Designer Unreal Engine, на котором я преподаю в Otus.