Создание прототипа раннера в Unreal Engine 4

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

Меня зовут Игорь Титаренко, я работаю Unreal Engine разработчиком в компании SolidCore Games. Свежий пример, который я хочу с вами рассмотреть — прототип раннера. По этой ссылке вы можете скачать файлы проекта и потестить его.

В готовом прототипе будет:

Персонаж бегущий вперед, на кнопки “A”, “D” меняем локейшн по оси “X”.

Уровень, по которому мы передвигаемся — Tile Map. При перемещении персонажа, пройденный тайл перемещается вперед.

Монеты, которые мы будем собирать и подсчет очков.

Простой UI (Game Over, Score, Restart) и конец игры при столкновении с препятствием.

Ассеты я подготовил заранее и буду подгружать из готового проекта. Логику напишу с нуля.

Создание проекта и настройка персонажа

Создадим проект на основе Third Person, в котором у нас по умолчанию будет персонаж и уровень.

Я убираю из уровня лишнюю геометрию и настраиваю персонажу управление. Изначально, при нажатии на “W” вызывается событие MoveForward и персонаж передвигается вперед. Мы будем вызывать логику движения вперед не нажатием кнопки, а на Event Tick, то есть на каждый кадр в игре, чтобы персонаж все время бежал вперед.

Затем настраиваем перемещение персонажа на кнопки “A”, “D” — меняем локейшн по оси X.

Создаем массив float переменных, в котором будем хранить значения локации по оси X каждой из трех “дорожек”, по которым бежит персонаж.

Далее проверяем по какой полосе мы бежим — 0, 1 или 2, затем на нажатие кнопок “A”, “D” добавляем или отнимаем индекс полосы, если он не выходит за минимальное и максимальное значение.

После этого, в зависимости от индекса полосы дороги, задаем координаты по оси Х для нашего персонажа.

Tile Map

Начнем с подготовки ассетов для генерации уровня. Нам понадобится:

  1. Машинки, которые мы будем расставлять по уровню.
  2. Монетки, которые мы будем собирать.
  3. Tile — blueprint actor с наполнением уровня. Из тайлов мы будем генерировать “коридор” нашего уровня.

BP_Car

Теперь сделаем машинки, которые будут спауниться на уровне. Чтобы разнообразить игру, будем генерировать случайную машину из массива возможных вариантов. Создаем новый blueprint actor, добавляем ему компонент Static Mesh. Создаем массив с вариантами геометрии для наших машин. Далее пишем логику, которая при создании машинки будет генерировать рандомное (в пределах массива) число — индекс, и по нему задавать нашему эктору геометрию из массива.

BP_Coin

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

Чтобы геометрия монет и машинок не пересекалась, мы будем спаунить монеты выше, чтобы они падали на карту. При столкновении с машинкой или дорогой отключим им физику. Таким образом монеты будут на одинаковой высоте и над асфальтом и над машинками, в тех местах где они есть. Здесь они у меня на неравном расстоянии друг от друга, но на то он и прототип, чтобы быстро передать принцип без длительной проработки.

Создаем blueprint actor, добавляем ему компонент геометрии и коллизию, которая будет отвечать за отключение физики при столкновении с поверхностью.

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

Далее добавим логику отключения физики, когда она нам больше не нужна.

Сделаем так, чтобы монетка все время вращалась.

BP_Tile

Создаем тайл, из которого будет генерироваться уровень, по которому будет бежать персонаж. Я создаю новый Blueprint actor, добавляю ему нужные нам компоненты:

  1. StaticMesh — геометрию зданий и окружение.
  2. Box Collision “SpawnPoint Box” коллизию которая будет отрабатывать событие, когда персонаж пробежит данный тайл. Это необходимо, чтобы мы могли вызвать логику уничтожения данного тайла и спауна нового впереди.
  3. Spawn point — точку координат, в которой мы будем спаунить новый тайл.

Далее пишем функции генерации машин. Логика здесь такая — получаем локейшн тайла и в случайном месте из заданного диапазона по оси Y спауним BP_Car.

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

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

Теперь в GameMode напишем логику генерации уровня.

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

Функция спауна тайлов будет выглядеть так:

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

В Game Mode на Event Begin Play создадим цикл, который будет вызывать спаун тайлов нужное нам количество раз.

Начало игры запускает цикл. Он проходит от нуля до пяти. То есть функция Spawn Tile вызывается шесть раз. Соответственно, в начале игры спаунится шесть тайлов.

Если сейчас запустить игру, сгенерируется наш tile map, но мы можем пробежать его от начала до конца. Напишем логику для real time генерации тайлов при перемещении по уровню. В BP_Tile добавляем к SpawnPoint Box событие On Component Begin Overlap. Оно будет срабатывать, когда бокс компонент пересечется с другим объектом игрового мира, у которого есть коллизия. Событие проверяет, что столкнулось с SpawnPointBox. Если это character, которым мы управляем, то вызываем из Game Mode функцию спауна нового тайла и после задержки удаляем пройденный тайл.

UI, Подсчет очков

Создадим Widget Blueprint и назовем его WBP_Point.

Добавим в наш виджет следующие текстовые виджеты:

  • Score
  • GameOver
  • Your Score
  • Total Score

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

Visibility всех элементов виджета кроме Score устанавливаем Hiden и д

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

В целом наш виджет должен выглядеть примерно так:

Перейдем в Graph и создадим переменную “Score” типа integer — целое число.

Для текстов Score и Total Score привяжем созданную интовую переменную в качестве значения. Теперь из переменной зададим значение в текст, а переменную, в свою очередь, будем менять в персонаже.

Теперь добавим наш виджет во вьюпорт. Для этого в ThirdPersonCharacter на Event Begin Play создадим виджет WBP_Points, добавим его во вьюпорт и сохраним ссылку на него в переменную “Widget Score”.

Теперь у нас есть практически все для того, чтобы собирать монетки. Создадим в ThirdPersonCharacter событие PickupPoints. При вызове события получаем значение переменной Score из ссылки на виджет Widget Score, добавляем к нему 5 очков и обратно записываем в переменную Score.

PickupPoints будем вызывать из BP_Coin. Добавим событие On Component Begin Overlap для static mesh, в котором проверим с чем пересекается монетка. Если это ThirdPersonCharacter, вызываем у него PickupPoints и уничтожаем монетку.

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

Далее создадим логику конца игры, которая будет вызываться на столкновение персонажа с машиной. Для этого создаем в WBP_Points функцию Game Over, в которой будем менять visibility текстовых виджетов Game Over, Total Score, Restart button.

Добавим отдельный Box Collision component в ThirdPersonCharacter.

Привяжем событие на столкновение с этим боксом, в котором мы проверим: если ThirdPersonCharacter сталкивается с BP_Car, то вызываем функцию Game Over из WBP_Points, которая отрисует нужный нам интерфейс. Далее отключаем движения персонажу и включаем отображение курсора мыши, чтобы иметь возможность нажать Restart.

Теперь, если врезаться в машинку, мы получим такой результат:

Посетите неделю открытых лекций курса «Game Developer»

Подписывайтесь на нас в Facebook, Telegram, Vkontakte, Pinterest.

Статью подготовил Олег Мощенко.