Вы находитесь на странице: 1из 37

GAME DEVELOPMENT

Разработка игр на Unreal Engine

Харьковский филиал КА «ШАГ»


https://itstep.kh.ua
ТЕМА: TOWER DEFENSE

Автонаведение турели
В играх типа Tower Defense игрок не управляет турелями, он их лишь
устанавливает, меняет, удаляет и т.д. Сами турели поворачиваются в сторону врагов
и ведут по ним огонь в автоматическом режиме. На этом занятии мы займемся
реализацией такого автоматизма!

Для начала мы обозначим все цели для наших турелей, чтобы они могли
отличать, по кому нужно вести огонь. Для этого мы создадим отдельный BP,
который будет, как бы, меткой того, что объект игрового мира является целью для
турелей.

Перейдите в папку Blueprints и добавьте новый BP типа SceneComponent:

Назовите созданный BP «TurretTargetComponent»:

С т р а н и ц а 2 | 37
ТЕМА: TOWER DEFENSE

Откройте созданный BP для редактирования. Когда цель турели погибает, то


нужно как-то отметить, что этот объект больше не является целью и по нему не
нужно вести огонь. Для этого мы создадим переменную.

Добавьте на панели Variables новую переменную и назовите ее «IsDead»:

Тип переменной должен быть Boolean, поэтому, если переменная таковой не


является задайте тип переменной на панели Details:

Откомпилируйте TurretTargetComponent, сохраните его и закройте окно


редактора. Теперь добавим этот компонент в базовый BP для врага.

Откройте Enemy_Base_BP и добавьте новый компонент типа Turret Target


Component:

С т р а н и ц а 3 | 37
ТЕМА: TOWER DEFENSE

Все наши турели будут поворачиваться абсолютно одинаково. Поэтому мы


реализуем алгоритм в базовой турели.

Откройте Turret_Base_BP для редактирования. Сначала мы добавим


несколько вспомогательных функций, которые нам понадобятся для реализации
автонаведения.

Добавьте на панели Functions новую функцию и дайте ей название


«GetTurretTargetComponent». Эта функция будет «вытягивать» компонент
TurretTargetComponent из игрового объекта.

Выделите входной нод функции Get Turret Target Component и справа на


панели Details установите флажок в CheckBox-е напротив параметра Pure:

Установив флажок для этого параметра, мы указываем, что наша функция


будет «бедной», т.е. она будет лишь вспомогательной в цепочке нодов и будет
служить лишь для вычисления каких-то входных параметров для других нодов. При
этом сама «бедная» функция не будет соединяться в цепочку с остальными нодами.

С т р а н и ц а 4 | 37
ТЕМА: TOWER DEFENSE

Теперь настроим входные/выходные параметры нашей функции. Выделить


входной нод функции Get Turret Target Component и на панели Details добавьте
новый параметр в Inputs. Назовите его «CurrentTargetActor»:

Измените тип созданного параметра на Actor Object Reference:

Теперь добавьте новый элемент в Outputs. Назовите его


«TurretTargetComponent»:

Измените тип созданного параметра на Turret Target Component Object


Refernce:

С т р а н и ц а 5 | 37
ТЕМА: TOWER DEFENSE

В результате настройки входных/выходных параметров у нас должен


получиться такой граф функции:

Теперь приступим к самой реализации функции. Разорвите связь между


входным и выходным нодами функции и переместите нод Return Node немножко
ниже:

Первым делом мы вытянем компонент TurretTargetComponent для Actorа-а,


который функция получает, как входной параметр.

Потяните за выход Curret Target Actor входного нода функции на пустое


место и в выпадающем меню выберите нод Get Components by Class:

С т р а н и ц а 6 | 37
ТЕМА: TOWER DEFENSE

Этот нод позволяет получить все компоненты определенного класса, которые


входят в Actor-а, присоединяемого ко входу.

Установите в параметре Component Class этого нода значение Turret Target


Component:

Нод Get Components By Class возвращает в качестве выходного параметра


массив найденных компонентов (множество различных компонентов в одном
объекте). Нам необходимо их поочередно перебрать, пройтись по этому массиву.
Для этого мы будем использовать цикл, как это делается в программировании на
любом языке.

Потяните за выход Return Value нода Get Components by Class и в


выпадающем меню выберите нод For Each Loop:

Нод For Each Loop позволяет перебрать все объекты массива Array. Пока
мы находимся в цикле, выполняется цепочка нодов Loop Body. Как только в цикле
будут перебраны все элементы, будет совершен переход на цепочку Completed.
Текущий объект, который просматривается циклом можно получить благодаря
выходному параметру Array Element:

С т р а н и ц а 7 | 37
ТЕМА: TOWER DEFENSE

Прежде чем настроить цикл, нам необходимо добавить локальную


переменную для нашей функции. Эта переменная будет существовать только в
рамках функции и за пределами ее видна не будет.

Найдите панель Local Variables, которая находится немножко ниже панели


Variables:

Добавьте на ней новую переменную и назовите ее «Target»:

Измените тип переменной на Turret Target Component Object Reference:

С т р а н и ц а 8 | 37
ТЕМА: TOWER DEFENSE

Теперь вернемся к работе с циклом For Each Loop. В теле цикла (Loop Body)
мы будем проверять, является ли рассматриваемый компонент валидным
(существует ли он в принципе, или в нем пусто) и, если это так, то будем записывать
его в локальную переменную Target.

Для того, чтобы реализовать такое поведение, добавьте в цепочку Loop Body
такую последовательность нодов:

Для каждого просматриваемого в цикле компонента будет вызываться


функция IsValid, которая возвращает True, если объект валидный и False, если он не
валидный. Результат вычисления функции IsValid подается на вход нода Branch,
который, если результат равен True записывает просматриваемый компонент в
локальную переменную Target при помощи нода SET для этой переменной.

Когда наш цикл закончится, мы просто вернем из функции компонент,


который записан в переменной Target.

С т р а н и ц а 9 | 37
ТЕМА: TOWER DEFENSE

Для этого присоедините в цепочку Completed нод Return Node и


присоедините к его входу переменную Target:

Не забудьте соединить в цепочку входной нод функции и нод цикла For Each
Loop:

Ну вот и все, функция GetTurretTargetComponent реализована!

Откомпилируйте и сохраните Turret_Base_BP.

Прежде чем реализовывать основной алгоритм автонаведения турели нам


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

На панели Functions добавьте новую функцию и дайте ей название


«FindFirstTarget»:

С т р а н и ц а 10 | 37
ТЕМА: TOWER DEFENSE

Для начала настроим созданную функцию. Она также, как и предыдущая,


будет «бедной». Выделите входной нод Find First Target и справа, на панели
Details, поставьте флажок в CheckBox-е напротив параметра Pure:

Входных параметров (Inputs) у нашей функции не будет. У нее будет лишь


один выходной параметр.

Добавьте в выходных параметрах Outputs параметр и назовите его


«NewTarget». Измените тип этого параметра на Actor Object Reference:

Теперь приступим к самой реализации функции. Первым делом наша


функция будет вытаскивать все игровые объекты, которые на данный момент
попадают в область видимости турели. Как вы помните, эта область задается
компонентом с названием LineOfSightBox, который является частью турели.

Вызовите для компонента LineOfSightBox нод Get Overlapping Actors:

С т р а н и ц а 11 | 37
ТЕМА: TOWER DEFENSE

В качестве значения входного параметра Class Filter установите класс


Enemy Base BP:

Таким образом, мы будем перебирать не все игровые объекты подряд, а лишь


те объекты, которые являются врагами.

Нод Get Overlapping Actors возвратит нам массив объектов типа


Emeny_Base_BP, которые на данный момент пересекают LineOfSightBox. Теперь,
как и в случае с функцией GetTurretTargetComponent, нам необходимо пройтись по
массиву и перебрать все его элементы. Для этого мы еще раз воспользуемся уже
знакомым нам нодом, For Each Loop.

Потяните за выход Overlapping Actors нода Get Overlapping Actors и в


выпадающем меню выберите нод For Each Loop:

С т р а н и ц а 12 | 37
ТЕМА: TOWER DEFENSE

Прежде чем работать с циклом, как и в случае с предыдущей функцией, мы


добавим локальную переменную.

На панели Local Variables добавьте новую локальную переменную и


назовите ее «Target»:

Установите для нее тип Actor Object Reference:

В теле цикла, для каждого объекта мы будем получать его


TurretTargetComponent и проверять его на валидность. Кроме этого, мы будем
проверять, мертвая ли на данный момент эта цель или нет.

Для того, чтобы реализовать такой алгоритм, добавьте в Loop Body


следующую цепочку нодов:

С т р а н и ц а 13 | 37
ТЕМА: TOWER DEFENSE

В конце, если проверка значения переменной IsDead возвращает False, то мы


записывает просматриваемый объект в переменную Target при помощи нода SET:

По окончанию цикла мы вернем из функции значение переменной Target.


Для этого присоедините к цепочке Completed нод Return Node и передайте в него
переменную Target:

С т р а н и ц а 14 | 37
ТЕМА: TOWER DEFENSE

Кроме этого, не забудьте соединить входной нод функции Find First Target
и нод цикла For Each Loop:

Вот и все! Еще одна вспомогательная функция создана!

Откомпилируйте и сохраните Turret_Base_BP.

Теперь мы приступим к реализации основного алгоритма автонаведения


турели на врагов. Для начала нам необходимо добавить переменную, которая будет
хранить в себе текущую цель турели.

Добавьте на панели Variables новую переменную и назовите ее


«CurrentTarget»:

С т р а н и ц а 15 | 37
ТЕМА: TOWER DEFENSE

Измените тип переменной на Actor Object Reference:

Турель должна постоянно автоматически настраивать свой угол поворота,


поэтому весь алгоритм мы реализуем, как обработку события Event Tick.

Перейдите во вкладку Event Graph и найдите там нод события Event Tick.
Сначала мы установим значение переменной Delta Time, в которой у нас будет
храниться время, прошедшее с момента последнего срабатывания события Event
Tick. Новое значение мы возьмем из нода события Event Tick, получив его из
выхода Delta Seconds:

С т р а н и ц а 16 | 37
ТЕМА: TOWER DEFENSE

После этого мы проверим текущее значение переменной CurrentTarget при


помощи уже знакомого нам нода валидации IsValid и нода Branch:

Если цель валидна (т.е., если она задана), мы выполним еще одну проверку.
Мы получим компонент TurretTargetComponent и узнаем, жива ли все еще цель
или она уже была убита турелью.

Добавьте в цепочку True нода Branch такую последовательность нодов:

Если цель не мертва, то нам необходимо поворачиваться за ней. Для этого


присоедините к цепочке False последнего нода Branch нод SetWorldRotation (цель
– Turret Top):

С т р а н и ц а 17 | 37
ТЕМА: TOWER DEFENSE

Теперь необходимо вычислить новый угол поворота турели, который


задается параметром New Rotation. Добавьте слева внизу, немножко в стороне от
основного графа, нод Find Look at Rotation:

Данный нод позволяет вычислить необходимый угол поворота между


точками Start и Target, которые подаются на нод, как входные параметры.

Стартовой точкой (входной параметр Start) у нас будет компонент Turret Top,
т.е. непосредственно наша турель.

Присоедините ко входу Start нод GetWorldLocation, для которого, в


качестве входного параметра подсоедините компонент Turret Top:

С т р а н и ц а 18 | 37
ТЕМА: TOWER DEFENSE

Таким образом, среда UE4, вычислит текущее местоположение нашей турели


и результат этих вычислений мы подадим на нод Find Look at Rotation, как
стартовую точку.

Конечной точкой (Target) будет наша цель, т.е. враг. Противник, по


которому, в данный момент ведет огонь турель, хранится в переменной
CurrentTarget.

Добавьте в граф нод GetTurretTargetComponent и передайте в него, в


качестве входного параметра, переменную CurrentTarget:

Теперь добавьте нод GetWorldLocation и подключите к нему компонент,


возвращаемый нодом Get Turret Target Component. Результат вычисления
местоположения подключите к ноду Find Look at Rotation ко входу Target:

Если мы теперь просто зададим новый угол поворота турели, то она


моментально его примет и, соответственно, моментально повернется. Это будет
выглядеть довольно некрасиво. Поэтому сейчас мы сделаем так, чтобы турель
поменяла угол поворота плавно. Для этого мы воспользуемся нодом RInterp To.

Добавьте рядом с нодом Find Look at Rotation нод RInterp To:

С т р а н и ц а 19 | 37
ТЕМА: TOWER DEFENSE

Новый угол поворота мы получаем в результате выполнения нода Find Look


at Rotation. Соедините выход Return Value нода Find Look at Rotation со входом
Target нода RInterp To:

Теперь нам нужно вычислить стартовый угол поворота (Current). Для этого
мы просто считаем текущий угол поворота турели.

Добавьте нод GetWorldRotation, для которого, в качестве входного


параметра Target, подключите компонент TurretTop. Соедините выход этого нода
со входом Current нода RInterp To:

Теперь необходимо задать для нода RInterp To еще два параметра – Delta
Time и Initial Speed. Для этих целей у нас есть две переменные – DeltaTime и

С т р а н и ц а 20 | 37
ТЕМА: TOWER DEFENSE

RotationSpeed. Подключите эти переменные, соответственно, ко входам Delta Time


и Initial Speed:

Выход нода RInterp To необходимо подключить ко входу New Rotation нода


Set World Rotation:

Когда мы начинали реализовывать основной алгоритм автонаведения, мы


оставили пустой цепочку, которая должна выполняться тогда, когда цель турели
мертвая (цепочка True нода Branch, к которому подключается переменная IsDead):

С т р а н и ц а 21 | 37
ТЕМА: TOWER DEFENSE

Сейчас мы это исправим! Сначала мы добавим в эту цепочку нод установки


значения для переменной CurrentTarget. В этот нод мы ничего не подключим, т.е.
при помощи такого трюка мы, фактически, сделаем эту переменную пустой и у
турели цели не будет:

После этого добавьте такую последовательность нодов в цепочку:

Суть этой последовательности нодов заключается в том, чтобы попытаться


найти следующую цель для турели (для этого мы в прошлой части занятия написали
функцию FindFirstTarget). При этом, попытка найти эту цель осуществляется
только один раз, что гарантирует нод Do Once.

Все, обработка события Event Tick реализована! Теперь, для того, чтобы
полностью реализовать алгоритм автонаведения турели, нам необходимо обработать
еще одно событие – пересечение врагом границ LineOfSight.

С т р а н и ц а 22 | 37
ТЕМА: TOWER DEFENSE

Выделите компонент LineOfSightBox и найдите на панели Details события


для этого компонента (Events). Нас интересует событие On Component Begin
Overlap:

Нажмите на зеленую кнопку с плюсиком, чтобы добавить это событие в


граф. Среда UE4 автоматически создаст входной нод для этого события:

В первую очередь мы попробуем «вытянуть» из того объекта, который


пересек LineOfSightBox его компонент TurretTargetComponent и проверить его на
валидность:

С т р а н и ц а 23 | 37
ТЕМА: TOWER DEFENSE

Теперь мы проверим, существует ли у турели на данный момент какая-то


цель. Если цель не задана, то новой целью турели мы установим того врага, который
пересек LineOfSightBox и спровоцировал срабатывание события Is Begin Overlap:

Вот и все! Откомпилируйте, сохраните Turret_Base_BP и закройте окно


редактора.

Запустите игру, чтобы убедиться в том, что теперь турели автоматически


поворачиваются в сторону врагов!

С т р а н и ц а 24 | 37
ТЕМА: TOWER DEFENSE

Создание снаряда для турели


Мы реализовали автонаведение турели на наших врагов. Теперь мы сделаем
так, чтобы наши турели не просто поворачивались в сторону врагов, а вели по ним
огонь!

Начнем с реализации снарядов, которыми будут стрелять турели. Сначала


добавим материал для снаряда.

Перейдите в папку Materials и создайте там новый материал. Дайте ему


название «Bullet_M»:

Откройте созданный материал для редактирования.

Мы создадим материал, который позволит нам стрелять «горящими»


снарядами, fireball-ами.

Перетащите в граф нод Constant3Vector, который позволит нам задать


материалу базовый цвет:

Дважды кликните в этом ноде по черному квадратику, чтобы открылось окно


выбора цвета Color Picker.
С т р а н и ц а 25 | 37
ТЕМА: TOWER DEFENSE

Подберите в этом окне примерно такой цвет:

Теперь добавьте в граф нод Constant, который будет содержать просто


некоторое число:

После того, как вы добавили данный нод, выделите его и на панели Details
установите значение параметра Value в 50:

С т р а н и ц а 26 | 37
ТЕМА: TOWER DEFENSE

Теперь мы умножим выбранный цвет на эту константу. Это позволит на


порядок усилить цвет.

Кликните ПКМ по пустому месту на графе и добавьте нод Multiply:

Теперь соедините ноды так, как это указано на картинке ниже:

С т р а н и ц а 27 | 37
ТЕМА: TOWER DEFENSE

Обратите внимание, что результат умножения подается не на вход Base


Color, как это мы делали ранее, а на вход Emissive Color. Вход Emissive Color
специально создан для того, чтобы получить на выходе светящийся материал.

После того, как вы все соединили нажмите на кнопку Save, чтобы сохранить
материал, и закройте окно редактора.

Обратите внимание на панель Content Browser. Мы получили наш


светящийся материал для снаряда!

Теперь приступим к реализации самого снаряда. Перейдите в папку


Blueprints и добавьте там новый Blueprint типа Actor. Назовите его «Bullet_BP»:

Откройте его для редактирования. Для начала создадим структуру нашего


снаряда, добавив в BP необходимые компоненты.

С т р а н и ц а 28 | 37
ТЕМА: TOWER DEFENSE

Добавьте компонент Sphere:

Теперь выделите добавленную сферу и на панели Details измените ее


материал на Bullet_M, который мы создали специально для снаряда:

В результате вы должны увидеть во Viewport светящуюся сферу:

С т р а н и ц а 29 | 37
ТЕМА: TOWER DEFENSE

Теперь выделите компонент Sphere и установите для него такие настройки


коллизии:

Здесь мы указали, что наш снаряд является физическим объектом и


разрешили ему генерировать событие On Hit.

Теперь мы добавим ключевой компонент нашего BP, который сделает его не


просто игровым объектом, а именно снарядом.

Нажмите на кнопку Add Component и добавьте компонент Projectile


Movement:

Наличие этого компонента позволит создать имитацию полета пули.

Теперь мы его немножко настроим. Выделите компонент Projectile


Movement и на панели Details найдите группу параметров Projectile. Настройте
параметры этой группы так, как это показано ниже на скриншоте:

С т р а н и ц а 30 | 37
ТЕМА: TOWER DEFENSE

В этой группе мы задали начальную и максимальную скорость полета пули и,


кроме этого, установили действующую на пулю силу гравитации.

Ну вот и все! Теперь откомпилируйте Bullet_BP, сохраните его и закройте


окно редактора.

Реализация стрельбы
Теперь, когда у нас есть сам снаряд, мы приступим к реализации стрельбы.

Откройте базовый Blueprint турели Turret_Base_BP и перейдите во вкладку


Event Graph.

Кликните ПКМ по пустому месту в графе и в выпадающем меню найдите и


выберите пункт Add Custom Event:

С т р а н и ц а 31 | 37
ТЕМА: TOWER DEFENSE

Среда UE автоматически добавит стартовый нод создаваемого события.


Дайте событию название «Fire»:

Сам алгоритм, который мы реализуем в этом событии заключается в том,


что, если у турели существует цель, то будет спауниться снаряд через определенные
промежутки времени.

Наш скрипт начинается с проверки на валидность текущей цели


CurrentTarget.

Перетащите на граф переменную CurrentTarget (Get) и подключите ее к ноду


IsValid, а выход нода IsValid подайте на нод Branch:

Если все текущая цель существует, если мы перешли по ветке True, то нужно
вызвать нод Spawn Actor From Class:

С т р а н и ц а 32 | 37
ТЕМА: TOWER DEFENSE

Установите значение параметра Class в Bullet_BP:

Теперь перед нами стоит задача рассчитать координаты, в которых будет


спауниться снаряд и его угол поворота.

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


MuzzleLocation. Этот компонент представляет собою ту точку, в которой должны
спауниться снаряды. Перетащите его с панели Components в граф:

Теперь вызовите для MuzzleLocation нод GetWorldTransform, чтобы


получить трансформацию этого компонента и результат, возвращаемый этим нодом
подайте на вход Spawn Transfrom нода SpawnActor Bullet BP:

С т р а н и ц а 33 | 37
ТЕМА: TOWER DEFENSE

Также есть еще один важный момент! Подайте на вход нода Spawn Actor
From Class ссылку на самого себя (на саму турель). Для этого потяните за вход
Owner и в появившемся меню найдите пункт Get a reference to self:

Это необходимо для того, чтобы знать позже, от какой конкретно турели
прилетел снаряд.

Теперь нам осталось добавить периодическое повторение выстрела через


заданный промежуток времени.

Добавьте рядом с нодом Spawn Actor From Class нод Delay:

С т р а н и ц а 34 | 37
ТЕМА: TOWER DEFENSE

Этот нод позволяет реализовать задержку на время, которое задается


параметром Duration. Подайте на вход Duration значение переменной FireRate и
соедините ноды в цепочку:

По окончанию времени ожидания мы заставим нашу турель выстрелить еще


раз. Для этого присоедините к цепочку Completed нода Delay вызов события Fire:

Остался лишь один маленький штришок. В цепочку обработки события On


Component Begin Overlap, которая начинается таким нодом:

С т р а н и ц а 35 | 37
ТЕМА: TOWER DEFENSE

Добавьте после нода SET вызов события Fire:

Теперь откомпилируйте Turret_Base_BP, сохраните его и запустите игру,


чтобы убедиться в том, что турели теперь ведут огонь по движущимся врагам!

Если сейчас вы запустите игру, то вы увидите, что турели постоянно


стреляют немножко мимо. Они, как бы, опаздывают и не успевают производить
выстрел по движущемуся врагу.

Для того, чтобы это исправить достаточно внести одну маленькую


корректировку.

Откройте Blueprint нашего врага Monster1_BP.

Выделите в нем компонент TurretTargetComponent и посмотрите, где он на


данный момент находится.

С т р а н и ц а 36 | 37
ТЕМА: TOWER DEFENSE

В эту точку и целятся наши турели. Поскольку враг находится в постоянном


движении, мы сдвинем эту точку немного вперед и, тем самым, постараемся
добиться нужного нам результата!

Задайте для TurretTargetComponent новые координаты местоположения:

Теперь вы можете увидеть, что точка сдвинулась и находится немного


впереди врага.

Откомпилируйте Monster1_BP, сохраните его и закройте окно редактора.

Если вы запустите игру сейчас, то увидите, что турели теперь стреляют, как
бы, на опережение и, тем самым, попадают по врагам!

С т р а н и ц а 37 | 37

Вам также может понравиться