[Перевод] Туториал по Unreal Engine. Часть 10: Как создать простой FPS

image


Шутер от первого лица (first-person shooter, FPS) — это жанр, в котором игрок использует оружие и смотрит на мир глазами персонажа. FPS-игры чрезвычайно популярны, что видно по успеху таких франшиз, как Call of Duty и Battlefield.

Unreal Engine изначально был создан для разработки FPS, поэтому вполне логично использовать его для создания такой игры. В этом туториале вы научитесь следующему:

  • Создавать Pawn с видом от первого лица, который сможет двигаться и осматриваться вокруг
  • Создавать оружие и привязывать его к Pawn игрока
  • Стрелять пулями с помощью трассировки прямых (также известной как трассировка лучей)
  • Наносить урон акторам


Примечание: эта статья является десятой частью серии туториалов, посвящённых движку Unreal Engine:


  • Часть 1: Знакомство с движком
  • Часть 2: Blueprints
  • Часть 3: Материалы
  • Часть 4: UI
  • Часть 5: Как создать простую игру
  • Часть 6: Анимация
  • Часть 7: Звук
  • Часть 8: Системы частиц
  • Часть 9: Искусственный интеллект
  • Часть 10: Как создать простой FPS


Приступаем к работе


Скачайте заготовку проекта и распакуйте её. Перейдите в папку проекта и откройте BlockBreaker.uproject. Вы увидите следующую сцену:

535c08f9d8b1e4bce036d479d3b746ce.jpg


Зелёная стена состоит из множества целей. Когда им наносят урон, они становятся красными. Когда их здоровье достигает нуля, они исчезают. Красная кнопка заново устанавливает все цели.

Для начала нужно создать Pawn игрока.

Создание Pawn игрока


Перейдите в папку Blueprints и создайте новый Blueprint Class. Выберите в качестве родительского класса Character и назовите его BP_Player.

57d1a63f0f1ee41ed797ccaa72a0a067.jpg


Character — это разновидность Pawn, но с дополнительным функционалом, например, с компонентом CharacterMovement.

8ba2ecdbc419d4280b367b0ea8e3c908.jpg


Этот компонент автоматически обрабатывает движение, например, ходьбу и прыжки. Мы просто вызываем соответствующую функцию, благодаря чему Pawn перемещается. В этом компоненте также можно задать переменные, такие как скорость ходьбы и прыжков.

Чтобы заставить Pawn двигаться, нам нужно знать, когда игрок нажимает клавишу движения. Для этого мы привяжем движение к клавишам W, A, S и D.

Примечание: если вы незнакомы с привязками, то можете прочитать о них в части туториала, посвящённой Blueprints. Привязкой клавиш мы определяем, какие клавиши будут выполнять действие.


Создание привязок движения


Выберите Edit\Project Settings и откройте настройки Input.

Создайте два Axis Mappings под названием MoveForward и MoveRight. MoveForward будет управлять движением вперёд и назад. MoveRight — движением влево и вправо.

4cacabf49c368e10e9ec02e03b1cabe8.jpg


Для MoveForward замените клавишу на W. После этого создайте ещё одну клавишу и выберите S. Измените Scale для S на -1.0.

01fcd9aecb816edda31c0885c44da856.jpg


Примечание: если вы хотите подробнее узнать о поле Scale, прочитайте часть туториала, посвящённую Blueprints. В разделе »Значение оси и масштаб ввода» говорится, что это и как этим пользоваться.


Позже мы будем умножать значение масштаба на вектор forward Pawn. Это даст нам вектор, направленный вперёд при положительном масштабе. Если масштаб отрицателен, то вектор будет направлен назад. С помощью получившегося вектора можно будет двигать Pawn вперёд и назад.

d4904379dfaaf4798c532ad5b82a3e93.jpg


Теперь нам нужно сделать то же для движения влево и вправо. Измените клавишу для MoveRight на D. Затем создайте новую клавишу и выберите для неё A. Измените Scale для A на -1.0.

5c609b8911acc6b9e864b1488d947c70.jpg


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

Реализация движения


Откройте BP_Player, а затем откройте Event Graph. Добавьте событие MoveForward (то, которое указано в списке Axis Events). Это событие будет выполняться в каждом кадре, даже если вы ничего не нажимаете.

0c5ef5bc3aba52b4b786600cf981c44b.jpg


Также оно будет подавать на выход значение Axis Value, которое равно заданным ранее значениям Scale. Оно будет подавать на выход 1 при нажатии на W и -1 при нажатии на S. Если не нажимать клавиши, то на выходе будет 0.

Далее нужно приказать Pawn двигаться. Добавьте Add Movement Input и соедините его следующим образом:

6bc157422b5a43c39862279e6c9539aa.jpg


Add Movement Input будет получать вектор и умножать его на Scale Value. Это преобразует его в соответствующем направлении. Поскольку мы используем Character, то компонент CharacterMovement будет перемещать Pawn в этом направлении.

Теперь нам нужно указать направление движения. Так как мы хотим двигаться вперёд, то можем использовать Get Actor Forward Vector. При этом будет возвращаться вектор, направленный вперёд. Создайте его и соедините следующим образом:

7f37f0bed678ddb24c390bfa7eb9470c.jpg


Подведём итог:

  1. MoveForward выполняется каждый кадр и передаёт на выход Axis Value. Это значение будет равно 1 при нажатии на W и -1 при нажатии на S. Если не нажимать ни одну из этих клавиш, то на выходе будет 0.
  2. Add Movement Input умножает вектор forward Pawn на Scale Value. Благодаря этому в зависимости от нажатой клавиши вектор будет направлен вперёд или назад. Если не нажимать клавиши, то вектор не будет иметь направления, то есть Pawn не будет двигаться.
  3. Компонент CharacterMovement получает результат из Add Movement Input, после чего двигает Pawn в этом направлении.


Повторим процесс для MoveRight, но заменим Get Actor Forward Vector на Get Actor Right Vector.

4fe8146207bb3dad436ee00784717079.jpg


Для проверки движения нужно задать Pawn по умолчанию в игровом режиме.

Задание Pawn по умолчанию


Нажмите на Compile и вернитесь в основной редактор. Откройте панель World Settings и найдите раздел Game Mode. Измените значение Default Pawn Class на BP_Player.

f897c42563c641396a71fcb693f5f185.jpg


Примечание: если у вас нет панели World Settings, то перейдите в Toolbar и выберите Settings\World Settings.


Теперь при запуске игры вы автоматически будете использовать BP_Player. Нажмите на Play и воспользуйтесь клавишами W, A, S и D для движения по уровню.

GIF
054ca27e573cc1752cad0f28c5b03f99.gif


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

Создание привязок обзора


Откройте Project Settings. Создайте ещё два Axis Mappings под названием LookHorizontal и LookVertical.

21b08eaf57b175a8efe1a8a1c82b2c22.jpg


Замените клавишу для LookHorizontal на Mouse X.

1706d994da3b16bafb1835ddacd6b7aa.jpg


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

Теперь изменим клавишу для LookVertical на Mouse Y.

e50dc8345b9cccf3e589d5ea0d4bd969.jpg


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

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

Реализация обзора


Если у Pawn нет компонента Camera, то Unreal автоматически создаёт камеру за вас. По умолчанию, камера будет использовать поворот контроллера.

Примечание: если вы хотите узнать больше о контроллерах, то изучите наш туториал «Искусственный интеллект».


Несмотря на то, что контроллеры не являются физическими, у них всё равно есть свой поворот. Это значит, что можно направить взгляд Pawn и камеры в разных направлениях. Например, в игре от третьего лица персонаж и камера ни всегда смотрят в одном направлении.

GIF
ee3cdfbad8f4ac05eac25ec282dd62d5.gif


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

Откройте BP_Player и создайте событие LookHorizontal.

2d59836f1f40ffde2d1bbb52cad4e282.jpg


Чтобы заставить камеру поворачиваться влево или вправо, нам нужно регулировать рыскание (yaw). Создайте Add Controller Yaw Input и соедините его следующим образом:

1fb64fee56c28d57106a7f95911b74d2.jpg


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

Повторите процесс для LookVertical, но замените Add Controller Yaw Input на Add Controller Pitch Input.

6bca7bbf0c4104a8b62f33acc99e618a.jpg


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

Если вы предпочитаете неинвертированное управление, то умножьте Axis Value на -1. Это инвертирует Axis Value, что инвертирует наклон контроллера.

c24a658c3fe341acb7977b1a6665a2a2.jpg


Нажмите на Compile и нажмите Play. Оглянитесь вокруг с помощью мыши.

GIF
dd37dee96383bde68d5d90c2636cd220.gif


Теперь, когда всё движение и обзор готовы, пришла пора создавать оружие!

Создание оружия


Помните, что при создании Blueprint Class можно выбрать родительский класс? Можно так же выбирать в качестве родительских собственные Blueprints. Это полезно, когда имеются разные типы объектов, обладающие общим функционалом или атрибутами.

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

69f9e6635cb9cefb060f8aa8c65753f2.jpg


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

Создание базового класса оружия


Вернитесь в основной редактор и создайте Blueprint Class типа Actor. Назовите его BP_BaseGun и откройте.

Теперь мы создадим несколько переменных, задающих свойства оружия. Создайте следующие переменные типа float:

  • MaxBulletDistance: максимальная дальность полёта каждой пули
  • Damage: количество урона, наносимого пулей при попадании в актора
  • FireRate: промежуток (в секундах) между выстрелами пуль оружием


448a22cc03928a2bc14fb05ab701ce89.jpg


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


Теперь нам нужно физическое представление этого оружия. Добавьте компонент Static Mesh и назовите его GunMesh.

b0516f393e97fe55cbc3885c28a0ecb8.jpg


Пока не волнуйтесь о выборе статического меша. Мы займёмся этим в следующем разделе, где будем создавать дочерний класс оружия.

Создание дочернего класса оружия


Нажмите на Compile и вернитесь в основной редактор. Для создания дочернего класса нажмите правой клавишей мыши на BP_BaseGun и выберите Create Child Blueprint Class.

GIF
cbea7c070f14cac296ac42daf1ca8dac.gif


Назовите его BP_Rifle и откройте. Откройте Class Defaults и задайте переменным следующие значения:

  • MaxBulletDistance: 5000
  • Damage: 2
  • FireRate: 0.1


4dfe54999a0c289268aca05ea1e7c949.jpg


Это значит, что максимальный путь каждой пули будет равен 5000. Если она попадёт в актора, то нанесёт 2 единицы урона. При стрельбе очередями интервал перед каждым выстрелом будет равен не менее чем 0.1 секунды.

Теперь нам нужно указать меш, который будет использоваться оружием. Выберите компонент GunMesh и выберите для Static Mesh значение SM_Rifle.

618c34ed5b56963e64a7c5b330d7f419.jpg


Теперь оружие готово. Нажмите на Compile и закройте BP_Rifle.

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

Создание камеры


Откройте BP_Player и создайте компонент Camera. Назовите его FpsCamera.

1d51b14873a35992ccb2b38b45fb21da.jpg


Позиция по умолчанию находится довольно низко, из-за чего игрок может почувствовать себя маленьким. Измените location камеры FpsCamera на (0, 0, 90).

10ebb1db8f1ebc666ea203e80cae21eb.jpg


По умолчанию, компоненты Camera не используют поворот контроллера. Чтобы исправить это, перейдите в панель Details и включите Camera Settings\Use Pawn Control Rotation.

4b97ae2258946ea1fe3407486708a1e8.jpg


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

Задание местоположения оружия


Для создания местоположения оружия мы можем использовать компонент Scene. Эти компоненты идеально подходят для задания местоположений, потому что содержат только Transform. Выберите FpsCamera и создайте компонент Scene. Таким образом он прикрепится к камере. Назовите его GunLocation.

66f057451e1be4d77cfad2581cbbabf6.jpg


Благодаря тому, что мы прикрепили GunLocation к FpsCamera, оружие постоянно будет сохранять своё положение относительно камеры. Таким образом, мы всегда будем видеть оружие перед камерой.

Теперь присвоим location компонента GunLocation значения (30, 14, -12). Так мы расположим оружие впереди и слегка сбоку от камеры.

7eda3626f28fd76de6b20234b2256b8d.jpg


Затем зададим rotation значения (0, 0, -95). Когда мы прикрепим оружие, то будет казаться, что оно всегда направлено в центр экрана.

56d14858278425f7cf0e7e90a75175b8.jpg


Теперь нам нужно создать оружие и прикрепить его к GunLocation.

Создание и прикрепление оружия


Найдите Event BeginPlay и создайте Spawn Actor From Class. Выберите для Class значение BP_Rifle.

061d51dca101a6f683ab8a0ec01496a2.jpg


Поскольку нам нужно будет использовать оружие позже, мы сохраним его в переменной. Создайте переменную типа BP_BaseGun и назовите её EquippedGun.

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

Выбрав для переменной тип BP_BaseGun, мы создали большое «отверстие», подходящее под многие «фигуры».


Теперь присвоим EquippedGun значение Return ValueSpawn Actor From Class.

bc5ea4b2be85b9b86f40f414fbc51564.jpg


Чтобы прикрепить оружие, мы можем использовать AttachToComponent. Создайте его и задайте для Location Rule и Rotation Rule значение Snap to Target. Благодаря этому оружие будет иметь то же местоположение и поворот, что и родитель.

20411120aaad3107b00da7b31bd0bb67.jpg


Теперь создадим ссылку на GunLocation и соединим всё следующим образом:

53eb06713169f07cdb472d2c87e29343.jpg


Подведём итог:

  1. При создании BP_Player он будет создавать экземпляр BP_Rifle
  2. EquippedGun будет хранить ссылку на созданный BP_Rifle для дальнейшего использования
  3. AttachToComponent присоединяет оружие к GunLocation


Нажмите на Compile и нажмите Play. Теперь при создании игрока будет создаваться и оружие! При поворотах оружие будет всегда находиться перед камерой.

bba56e81aff12133547ec2e5678c6c9f.gif


Теперь начинается интересное: мы будем стрелять! Чтобы проверить, попала ли куда-нибудь пуля, мы можем использовать трассировку прямых.

Стрельба пулями


Трассировка прямых — это функция, получающая начальную и конечную точки (образующие прямую). Она проверяет каждую точку на прямой (от начала до конца), пока на что-нибудь не наткнётся. В играх для проверки попадания пули чаще всего используется такой способ.

Поскольку стрельба — это функция оружия, она должна выполняться в классе оружия, а не игрока. Откройте BP_BaseGun и создайте функцию Shoot.

Затем создайте два входа Vector и назовите их StartLocation and EndLocation. Они будут начальной и конечной точками трассировки прямых (которые мы будем передавать из BP_Player).

76bc3346558cff77ef595311d70a9deb.jpg


Трассировку прямых можно выполнять с помощью LineTraceByChannel. Этот нод проверяет попадания с помощью канала коллизий Visibility или Camera. Создайте его и соедините следующим образом:

5463dc82033abcd8128c8cbbb96a4f4a.jpg


Теперь нам нужно проверить, попадает ли во что-нибудь трассировка прямых. Создайте Branch и соедините его так:

02ca4c5a1721b6c1fffb66ca2ac791f3.jpg


При попадании Return Value будет выдавать на выход true, и наоборот.

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

Создание частиц попадания пули


Сначала нам нужно получить местоположение попадания трассировки. Перетащите левой клавишей мыши Out Hit на граф. В меню выберите Break Hit Result.

2a2c09b8195dcfc61104b4809100c7a0.jpg


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

Создайте Spawn Emitter at Location и задайте для Emitter Template значение PS_BulletImpact. Затем соедините его Location с Location нода Break Hit Result.

48af75e6f48b6650b53150c22939b16e.jpg


Вот как это будет выглядеть:

4bebf88743ad612fc6c7e0bc36f2d315.jpg


Подведём итог:

  1. При выполнении Shoot она выполняет трассировку прямой с переданными начальной и конечной точкой
  2. Если попадание зафиксировано, то Spawn Emitter at Location создаст PS_BulletImpact в точке попадания


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

Вызов функции Shoot


Для начала нам нужно создать привязку клавиш для стрельбы. Нажмите на Compile и откройте Project Settings. Создайте новую Axis Mapping под названием Shoot. Выберите для неё клавишу Left Mouse Button и закройте Project Settings.

ee9c8bde7d4f26690475ef2db31b8221.jpg


Затем откройте BP_Player и создайте событие Shoot.

382bf5e26f48c569ca915ae32ecd30c8.jpg


Чтобы проверить, нажимает ли игрок клавишу Shoot, нам достаточно проверить, равно ли значение Axis Value единице (1). Создайте выделенные ноды:

3b3972addb482164027385de42e72215.jpg

Затем создайте ссылку на EquippedGun и вызовите его функцию Shoot.

c08d87159ae10883c881879647b0b41c.jpg


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

Вычисление точек трассировки прямой


Во многих FPS пуля летит из камеры, а не из оружия. Так делают потому, что камеру уже и так идеально выровнена с прицелом. Поэтому если мы будем стрелять из камеры, то пуля гарантированно полетит туда, где находится курсор.

Примечание: некоторые игры всё-таки реализуют стрельбу из оружия. Однако для стрельбы ровно в прицел требуются дополнительные вычисления.


Создайте ссылку на FpsCamera и соедините её с GetWorldLocation.

5e7b8742558e21bd2ef0c37a7f2c44c2.jpg


Теперь нам нужна конечная точка. Не забывайте, что у оружия есть переменная MaxBulletDistance. Это значит, что конечная точка должна находится на расстоянии MaxBulletDistance единиц от камеры. Чтобы реализовать это, создайте выделенные ноды:

c03c3f3d4fbb95738888a96f5b5a61c5.jpg


Затем соедините всё следующим образом:

483ad1544f1c790e33bba95e1615beff.jpg


Подведём итог:

  1. Когда игрок нажимает или удерживает левую клавишу мыши, оружие будет выпускать пулю с начальной точкой в камере
  2. Пуля пролетит расстояние, указанное в MaxBulletDistance


Нажмите на Compile, а затем на Play. Удерживайте левую клавишу мыши, чтобы начать стрельбу.

GIF
8248d1f948334196470e4fdc0bca21bd.gif


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

Уменьшение частоты стрельбы


Во-первых, нам нужна переменная, чтобы определить, может ли игрок стрелять. Откройте BP_Player и создайте переменную типа boolean с названием CanShoot. Задайте ей значение true по умолчанию. Если CanShoot равна true, то игрок может стрелять, и наоборот.

Замените раздел Branch на следующее:

de3ab2974872be4808cac417dc0ed34b.jpg


Теперь игрок может стрелять только если нажата клавиша Shoot и переменная CanShoot равна true.

Теперь добавим выделенные ноды:

e80d7140cfa2489e799072e4c4dc1ba7.jpg


Изменения:

  1. Игрок может стрелять, только когда удерживает левую клавишу мыши, и когда CanShoot равна true
  2. Когда игрок стреляет пулей, переменной CanShoot присваивается значение false. Это не позволит игроку выстрелить снова.
  3. CanShoot будет снова присвоено значение true через промежуток времени, указанный в FireRate


Нажмите на Compile и закройте BP_Player. Нажмите Play и проверьте новую частоты стрельбы.

GIF
a561d58cdef8f9e4ce41764b1495226b.gif


Теперь мы научим цели и кнопку реагировать на пули. Это можно сделать, добавив им урон.

Применение урона


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

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

Для того, чтобы можно было управлять получением актором урона, нам сначала нужно применить урон. Откройте BP_BaseGun и добавьте Apply Damage в конце функции Shoot.

42b994920cc251cc60fc4eb5976a043d.jpg


Теперь нам нужно указать, какому актору мы хотим наносить урон. В нашем случае это актор, в которого попадает трассировка прямой. Соедините Damaged Actor с Hit Actor нода Break Hit Result.

d475e0317bc36d50a7e4f522325eed87.jpg


Наконец, нам нужно указать величину нанесённого урона. Получите ссылку на Damage и соедините её с Base Damage.

3fafda8022c71feb230c289a9898390e.jpg


Теперь при вызове Shoot она будет наносить урон акторам, попавшим в трассировку прямой. Нажмите на Compile и закройте BP_BaseGun.

Теперь нам нужно обработать то, как актор получает урон.

Обработка урона


Сначала мы обработаем то, как урон получают цели. Откройте BP_Target и создайте Event AnyDamage. Это событие выполняется, когда актор получает урон, не равный нулю.

42b73d2576fc7e0852839ae5e95ce61e.jpg


Теперь вызовите функцию TakeDamage и соедините контакты Damage. Это вычтет здоровье из переменной Health цели и обновит цвет цели.

0f6bddd90b1910c6e9c85dfe388a942d.jpg


Теперь, когда цель получает урон, она теряет здоровье. Нажмите на Compile и закройте BP_Target.

Теперь нам нужно обработать то, как получает урон кнопка. Откройте BP_ResetButton и создайте Event AnyDamage. Затем вызовите функцию ResetTargets.

38ba00e9250fa4f67d0b703e1340cad3.jpg


Это будет восстанавливать все цели при получении урона кнопкой. Нажмите на Compile и закройте BP_ResetButton.

Нажмите на Play и начните стрелять по целям. Если вы хотите восстановить цели, то выстрелите по кнопке.

GIF
88b313e87f8c2ef8fee5ce5a57407092.gif


Куда двигаться дальше?


Готовый проект можно скачать отсюда.

Хотя созданный в этом туториале FPS очень прост, его можно запросто расширить. Попробуйте создать больше оружия с разными типами частоты стрельбы и урона. Можете даже попробовать добавить функцию перезарядки!

© Habrahabr.ru