Привет, Хабр! Меня зовут Александр, и сегодня мы сравним Unity и Unreal Engine 4.
Думаю, многие разработчики пробовали движок Unity и видели сделанные на нём игры, проекты, какие-то демки. Его главный конкурент — движок Unreal Engine. Он берёт своё начало в проектах компании Epic Games, таких как шутер Unreal Tournament. Давайте рассмотрим, как начать работу с движком Unreal после Unity и какие препятствия могут подстерегать нас на пути.
Бывает, что 3D-движки сравнивают весьма поверхностно, либо акцентируют внимание только на одной из фич, например, на графике. Мы же холиварить не будем и рассмотрим оба движка в качестве равноправных инструментов. Наша цель — сопоставить две технологии и помочь вам разобраться в движке Unreal Engine 4. Сравним базовые системы движков на конкретных примерах кода демо-проекта UShooter (Unreal + Unity Shooter), специально сделанного для этих целей. Проект использует версию Unity 5.5.0 и Unreal Engine 4.14.3.
Система компонентов (Unity)
Когда мы запускаем проект на Unreal, то видим, что персонаж в сцене — лишь один объект. В окне World Outliner нет привычных нодов модели (вложенных объектов, мешей), костей скелета и т. д. Это следствие различий систем компонентов Unity и Unreal.
В Unity сцена состоит из объектов типа Game Object. Это пустой универсальный объект, к которому добавляются компоненты, реализованные скриптами поведения (MonoBehaviour) и встроенными компонентами движка. Иногда их оставляют пустыми, в качестве объекта-маркера, на месте которого будет создан, например, игровой персонаж или эффект.
Все эти объекты мы видим в окне Hierarchy в редакторе движка. Они имеют встроенный компонент Transform
, с помощью которого мы можем управлять положением объекта в пространстве 3D-сцены. Например, скрипт движения объекта меняет координаты в функции Update
, и объект двигается. Для добавления подобного скрипта на Game Object достаточно двух кликов. Создав объект — персонажа или предмет, — мы его настраиваем, добавляем скрипты и сохраняем в prefab (файл, хранящий Game Object и его дочерние объекты). Впоследствии мы можем менять сам prefab, и эти изменения отразятся на всех подобных объектах.
Вот как выглядит класс RocketProjectile
, представляющий собой ракету в проекте UShooter.
public class RocketProjectile: MonoBehaviour
{
public float Damage = 10.0f;
public float FlySpeed = 10.0f;
void Update()
{
gameObject.transform.position += gameObject.transform.forward * FlySpeed * Time.deltaTime;
}
void OnCollisionEnter(Collision collision)
{
// Обработка столкновения
}
}
Мы задаём параметры снаряда в редакторе, при желании меняем скорость перемещения (свойство FlySpeed
) и урон (Damage
). Обработка столкновений происходит в функции OnCollisionEnter
. Unity сам её вызывает, так как на объекте есть компонент Rigid Body.
Система компонентов (UE4)
В Unreal Engine 4 игровые объекты представляются Actor’ами и их компонентами. AActor
(«актер») — это основной класс объекта, который помещается в сцене. Мы можем его создать в игровой сцене (как из редактора, так и кодом), менять его свойства и т. д. Также есть класс, от которого унаследованы все сущности движка: UObject
.
Компоненты добавляются к Actor’у, игровому объекту. Это может быть оружие, персонаж, что угодно. Но эти компоненты условно скрыты от нас в аналоге Prefab’а — Blueprint Class
.
В объекте Actor, в отличие от Unity, существует понятие Root Component
. Это корневой компонент объекта, к которому крепятся остальные компоненты. В Unity достаточно мышкой перетащить объект, чтобы поменять у него иерархию вложенности. В Unreal это делается через привязку компонентов друг к другу ("attachment").
В Unity существуют функции Start
, Update
и LateUpdate
для обновления или начала работы скриптов MonoBehaviour. Их аналоги в Unreal — функции BeginPlay
и Tick
у Actor'а. У компонентов Actor’а (UActorComponent
) для этого существуют функции InitializeComponent
и ComponentTick
, поэтому нельзя «в один клик» сделать из компонента Actor, и наоборот. Также, в отличие от Unity, Transform есть не у всех компонентов, а только у USceneComponent
и унаследованных от него.
В Unity мы можем практически в любом месте кода написать GameObject.Instantiate
и получим созданный из Prefab’а объект. В Unreal же мы «просим» объект мира (UWorld
) создать экземпляр объекта. Создание объекта называется в анриале спауном, от слова spawn. Для этого используется функция World->SpawnActor
.
Персонажи и их Controller’ы
В Unreal для персонажей существуют специальные классы APawn
и ACharacter
, они унаследованы от класса AActor
.
APawn
— класс персонажа, которым может управлять игрок или AI. В Unreal для управления персонажами есть система контроллеров. Мы создаём Player Controller
или AI Controller
. Они получают команду управления от игрока или внутреннюю логику, если это AI, и передают команды движения самому классу персонажа, APawn
или ACharacter
.
ACharacter
создан на основе APawn
и имеет расширенные механизмы перемещения, встроенный компонент скелетного меша, базовую логику перемещения персонажа и его представление для сетевой игры. Для оптимизации можно создать персонажа на основе APawn
и реализовать только необходимый проекту функционал.
Описание игрового класса (Actor’а)
Теперь, немного узнав о компонентах Unreal, мы можем взглянуть на класс ракеты в Unreal-версии UShooter.
UCLASS()
class USHOOTER_API ARocketProjectile : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ARocketProjectile();
// Called when the game starts or when spawned
virtual void BeginPlay() override;
// Called every frame
virtual void Tick( float DeltaSeconds ) override;
// Rocket fly speed
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Rocket")
float FlySpeed;
// Rocket damage
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Rocket")
float Damage;
// Impact (collsion) handling
UFUNCTION()
void OnImpact(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit);
private:
/** Collision sphere */
UPROPERTY(VisibleDefaultsOnly, Category = "Projectile")
USphereComponent* CollisionComp;
};
Взаимодействие редактора и скриптов, которое в Unity не требует специального кода, работает в Unreal через генерацию кода. Этот специальный код Unreal генерирует при сборке. Чтобы редактор мог показать свойства нашего объекта, мы делаем специальные обёртки: UCLASS
, GENERATED_BODY
и UPROPERTY
. Также мы декорируем свойства и описываем, как редактор должен с ними работать. Например, EditDefaultsOnly означает, что мы можем изменить свойства только дефолтного объекта, blueprint class’а (prefab’а, если провести аналогию с Unity). Свойства могут быть сгруппированы в разные категории. Это позволяет быстрее найти интересующие нас свойства объекта.
Функция OnImpact
— аналог OnCollisionEnter
в Unity. Но для работы с ней требуется подписаться на события компонента USphereComponent
в конструкторе или даже во время игры. Это не работает автоматически, как в Unity, зато здесь есть возможность оптимизации. Если нам больше не нужно реагировать на столкновения, мы можем отписаться от события.
Блупринты (Blueprint)
Типичное действие после создания C++ класса в Unreal — создание на его основе Blueprint Class
’а. Это расширение объекта, которое нам предоставляет Unreal. Система Blueprint’ов в Unreal используется для визуального программирования. Мы можем создавать визуальные схемы, соединять события с какими-то реакциями на них. Через блупринты движок упрощает взаимодействие программистов и дизайнеров. Мы можем написать на С++ часть игровой логики и предоставить доступ к ней дизайнерам.
При этом Unreal позволяет отделить, если требуется, C++ исходники проекта от его бинарников и контента. Дизайнеры или аутсорсеры могут работать с собранными dll-библиотеками и никогда не узнают, что происходит внутри C++ части проекта. Это дополнительная степень свободы, предоставляемая движком.
Unreal хорош тем, что в нём практически всё связано с Blueprint’ами. Мы можем расширять ими С++ классы, создавать из них Blueprint-наследников и т. д. Эта система тесно связана со всеми компонентами движка, от его внутренней логики до визуальных компонентов, collision, анимации и т. д.
В Unity есть схожие системы визуального программирования, например Antares Universe. Они не входят в состав движка и созданы поверх него, поэтому в любой момент что-то может сломаться (например, при обновлении версии движка). Система визуального скриптования в Unity не предусмотрена. На мой взгляд, это серьёзный недостаток по сравнению с Unreal. Ведь благодаря таким системам даже далекие от программирования люди могут составить схему взаимодействия объектов или связать какую-то последовательность действий. К слову, в Unreal все шаблоны проектов имеют две версии: как на основе C++ кода, так и целиком на Blueprint’ах. Таким образом, создать простой проект без использования кода, целиком на блупринтах — вполне реально.
Демка шутера (UShooter)
В Unity мы пишем демку с нуля, а в Unreal опираемся на шаблоны. В шаблоне выберем управление и вид камеры, и Unreal сгенерирует проект с указанными настройками. Это хорошая основа, от которой вы можете отталкиваться для ускорения разработки и создания прототипа проекта.
Поверх шаблона Side Scroller мы добавляем собственный интерфейс (HUD), бочки, несколько видов оружия и звуки. Дадим игроку ракетницу и railgun, пусть героически стреляет по взрывающимся бочкам.
Система ввода (Unity)
Управлять персонажем будем с помощью системы ввода. В Unity мы обычно настраиваем ввод через Input Manager, создаём виртуальные именованные оси. Например, «идти вперёд» или «стрелять». Даём им имена и потом получаем значение какой-либо оси или состояние виртуальной кнопки. Обычно скрипты, которые занимаются управлением объектами, получают состояние осей в функции Update
. В каждом кадре опрашивается состояние оси и целого ряда кнопок управления.
Система ввода (UE4)
В Unreal тоже есть виртуальные оси, но там есть разделение на собственно оси (значения, полученные от джойстика, и т.п.) и кнопки действия. В отличие от Unity, мы привязываем оси и кнопки к функциям класса, который реализует управление персонажем. Связь создаётся через компонент UInputComponent
. Такой компонент ввода есть у класса персонажа ACharacter
.
Вызовом BindAxis("MoveRight", this, &AUShooterCharacter::MoveRight)
в Input Component мы привязываем нажатие кнопки MoveRight к вызову одноимённой функции движения. Не требуется каждый кадр заниматься опросом кнопки.
Также в Unreal не ограничено количество альтернативных кнопок. В Unity в Input Manager есть только основная кнопка и альтернативная. Чем больше устройств ввода в вашей игре, тем острее может быть эта проблема.
Работа с 3D-моделями
Как уже говорилось, в Unreal мы не видим в сцене структуру скелета персонажа. Дело в том, что компоненты скелета не являются Actor’ами или чем-то подобным. Это внутренние свойства скелета и анимации. Как тогда привязать к персонажу оружие или скрыть одну из его частей? Может, мы хотим надеть на него модную кепку или привязать оружие к руке.
В Unity мы выделим модель оружия в редакторе, перетащим в нужную кость, можем даже повесить на него отдельный скрипт управления. В Unreal мы будем пользоваться сокетами (Socket) — точками крепления на игровых объектах. Сокеты — это часть скелета в моделях со скелетной анимацией (в Unity такие модели называются Skinned Mesh, в Unreal’е — Skeletal Mesh). Также сокеты можно добавлять в статические меши (Static Mesh).
Выбираем кость, к которой крепится сокет, и задаём имя сокета, например S_Weapon
, если к точке крепится оружие. После создания сокета можно создать («заспаунить») объект в позиции этого сокета или привязать его к сокету через механизм привязки (функции AttachTo
). Система немного запутанная, в отличие от Unity, зато более универсальная. Мы можем один раз настроить названия точек, тем самым отделив игровую логику от настроек моделей. Причём если у нас имеется несколько моделей с одним скелетом, то сокеты надо будет добавить только в скелет. В демке шутера сокеты используются при создании снарядов и эффектов выстрела.
Система анимации (Unity)
У нас есть персонаж, мы знаем, как работать с вводом, теперь нужно проигрывать анимацию. В Unity для этого есть Animation Controller, в нём мы описываем определённые состояния персонажа. Например, бежать, прыгать или умереть. Каждому блоку соответствует свой анимационный клип, и мы настраиваем такой граф переходов:
Хотя эта схема называется Animation Controller, внутренней логики у неё нет. Это просто схема переключения анимации в зависимости от состояния. Чтобы она работала, мы заранее объявляем в этом контроллере названия переменных, соответствующих состоянию персонажа. Скрипт, управляющий анимацией, зачастую сам передаёт эти состояния контроллеру каждый кадр.
В переходах между состояниями (на схеме показаны стрелочками) мы настраиваем условия переходов. Можно настроить смешивание (crossfade) анимации, т. e. время, в течение которого одна анимация затухнет, а другая продолжится, для их плавного совмещения.
Система анимации (UE4)
В Unreal всё сделано Blueprint’ами, анимация не исключение. Создаём Animation Blueprint
, который будет управлять анимацией. Он тоже представляет собой граф состояний. Так выглядит машина состояний, она управляет финальной анимацией персонажа в зависимости от движения или состояния смерти.
Тут мы видим уже знакомые нам состояния Idle/Run, Jump, Dead. Но один узел совмещает в себе Idle и Run. Внутри него находится так называемый Blend Space 1D, он используется для плавного перехода анимации в зависимости от значения одной или нескольких переменных. С помощью Blend Space можно привязать скорость персонажа к переходу между анимацией Idle и Run. Кроме того, получится настроить несколько точек перехода. Например, от нуля до метра в секунду персонаж идёт медленно — это будет движение, интерполированное между анимацией Idle и Walk. А после некоторого порогового значения включается бег (Run). И всё это будет в одном узле Animation Blueprint’а, который обращается к Blend State.
Стрелочками показаны переходы между состояниями, но, в отличие от Unity, мы можем создать Blueprint, реализующий внутреннюю логику работы этих переходов. В Animation Blueprint есть доступ к персонажу, на котором он используется, поэтому Blueprint сам обращается к его параметрам (скорость движения и т. п.). Это можно рассматривать как дополнительную оптимизацию, так как позволяет не рассчитывать параметры, которые не используются для текущего состояния персонажа.
В Unreal существует множество инструментов для анимации. Montage представляет собой подсистему и редактор, который позволяет совмещать анимационные клипы и их фрагменты.
Тут представлено совмещение машины состояний движения с анимацией атаки, которую мы проигрываем через инструмент Montage.
В нижней части рисунка — фрагмент схемы Animation Blueprint, который отвечает за реакцию на выстрел из оружия. Команда Montage Play включает анимацию выстрела, затем Delay ждёт, пока она закончится, и анимация выключается командой Montage Stop. Так сделано, потому что в машине состояний анимации мы не можем задать однократное проигрывание анимационного клипа. Если анимация зациклена и соответствует какому-то состоянию персонажа, мы можем управлять анимацией через машину состояний. А если требуется проиграть один клип анимации по событию, то можем сделать через Montage.
Проблема вложенных Prefab’ов
Большая проблема в Unity — вложенные prefab’ы. На случай, если проблема вам не знакома, рассмотрим пример.
Предположим, объект «стол с ноутбуком» сохранили в prefab table1, а затем понадобился второй подобный объект, но уже с зелёным цветом экрана ноутбука. Создаём новый prefab — table2, перетаскиваем в него старый ноутбук, меняем цвет экрана на зелёный, сохраняем. В результате table2, второй prefab, становится совершенно новым объектом, у него нет никаких ссылок на оригинал. Если мы поменяем исходный префаб, это никак не отразится на втором префабе. Простейший случай, но даже он не поддерживается движком.
В Unreal, благодаря наследованию Blueprint’ов, такой проблемы нет: изменение исходного объекта отразится на всех дочерних объектах. Это пригодится не только для игровых объектов, персонажей, какой-то логики или даже статических объектов на сцене, но и для системы интерфейсов.
С другой стороны, можно попытаться победить эту проблему в Unity, используя ассеты в Asset Store. В Unity есть плагины, расширения движка, которые так и называются — Nested Prefabs. Существует несколько подобных систем, но они немного костыльные, сделаны поверх движка, поддержки нет. Они пытаются сохранить в себе внутреннее состояние объекта. Когда запускается игровая сцена, они пробуют восстановить внутренние структуры, их поля, свойства и т. д., удаляют устаревшие объекты в сцене и заменяют их экземплярами из префабов. В результате мы получаем не только удобство вложенных префабов, но и ненужные тормоза, лишнее копирование данных и создание объектов. А если что-то в движке поменяется, то эти системы могут и вовсе отвалиться по неизвестным причинам.
Системы UI
В Unity нельзя сохранить в prefab элементы окон или какие-то виджеты. Можем попытаться, но возникнет та же самая проблема префабов: движок забудет о старых объектах. Поэтому зачастую в Unity мы создаём элементы управления, добавляем скрипты и потом их копируем, не создавая prefab. Если приходится добавлять в такие «виджеты» что-то новое, требуемые изменения нужно повторять вручную.
В Unreal мы можем сохранить элементы интерфейса в виджеты (Widget Blueprint), быстро сделать на основе одних элементов управления новые. Cделали кнопку и надпись, пусть это будет наш status bar widget. На основе стандартных и новых виджетов получается быстро и удобно строить окна интерфейса. К слову, виджеты также расширяются за счет Blueprint’ов, можно описать логику их работы на визуальных схемах.
В Unreal система редактирования интерфейсов и всех виджетов открывается в отдельной вкладке редактора. В Unity интерфейс редактируется через специальный объект Canvas, расположенный прямо в 3D-сцене и зачастую даже мешающий её редактировать.
Преимущества и недостатки
Для новичка значительно проще движок Unity, у него устоявшееся сообщество, множество готовых решений. Можно расширять редактор скриптами, добавлять новые меню, расширять инспектор свойств и т. п.
В Unreal тоже можно написать для редактора свои окна и инструменты, однако это чуть сложнее, так как надо делать плагин, и это тема для отдельной статьи. Это посложнее, чем в Unity, здесь нельзя написать маленький скрипт, чтобы появилась полезная кнопка, расширяющая функционал редактора.
Из плюсов Unreal стоит отметить визуальное программирование, наследование blueprint’ов, виджеты UI, систему анимации с множеством возможностей и многое другое. Кроме того, в Unreal Engine 4 существует целый набор классов и компонентов, рассчитанных на создание игр: Gameplay Framework. Gameplay Framework является частью движка, на нём созданы все шаблоны проектов. Классы Gameplay Framework открывают множество возможностей — от описания игровых режимов (Game Mode) и состояния игрока (Player State) до сохранения игры (Save Game) и управления персонажами (Player Controller). Особенная фича движка — продвинутая сетевая подсистема, выделенный (dedicated) сервер и возможность запуска сетевой игры в редакторе.
Заключение
Мы сравнили движки Unity 5 и Unreal Engine 4 на конкретных примерах и проблемах, с которыми вы можете столкнуться, начав работу с движком Unreal. Часть сложностей, присущих Unity, решена в Unreal Engine 4. Конечно, невозможно в одном докладе сделать всесторонний обзор этих технологий в полной мере. Однако мы надеемся, что данный материал поможет вам в изучении движка.
Комментарии (0)