...

пятница, 28 апреля 2017 г.

UE4 для Unity-разработчиков

image


Привет, Хабр! Меня зовут Александр, и сегодня мы сравним 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.


Фрагмент RocketProjectile.cs
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.


image


Компоненты добавляются к 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.


Фрагмент RocketProjectile.h
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++ части проекта. Это дополнительная степень свободы, предоставляемая движком.


image


Unreal хорош тем, что в нём практически всё связано с Blueprint’ами. Мы можем расширять ими С++ классы, создавать из них Blueprint-наследников и т. д. Эта система тесно связана со всеми компонентами движка, от его внутренней логики до визуальных компонентов, collision, анимации и т. д.


В Unity есть схожие системы визуального программирования, например Antares Universe. Они не входят в состав движка и созданы поверх него, поэтому в любой момент что-то может сломаться (например, при обновлении версии движка). Система визуального скриптования в Unity не предусмотрена. На мой взгляд, это серьёзный недостаток по сравнению с Unreal. Ведь благодаря таким системам даже далекие от программирования люди могут составить схему взаимодействия объектов или связать какую-то последовательность действий. К слову, в Unreal все шаблоны проектов имеют две версии: как на основе C++ кода, так и целиком на Blueprint’ах. Таким образом, создать простой проект без использования кода, целиком на блупринтах — вполне реально.


Демка шутера (UShooter)


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


image


Поверх шаблона Side Scroller мы добавляем собственный интерфейс (HUD), бочки, несколько видов оружия и звуки. Дадим игроку ракетницу и railgun, пусть героически стреляет по взрывающимся бочкам.


Система ввода (Unity)


Управлять персонажем будем с помощью системы ввода. В Unity мы обычно настраиваем ввод через Input Manager, создаём виртуальные именованные оси. Например, «идти вперёд» или «стрелять». Даём им имена и потом получаем значение какой-либо оси или состояние виртуальной кнопки. Обычно скрипты, которые занимаются управлением объектами, получают состояние осей в функции Update. В каждом кадре опрашивается состояние оси и целого ряда кнопок управления.


Система ввода (UE4)


image


В 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).


image


Выбираем кость, к которой крепится сокет, и задаём имя сокета, например S_Weapon, если к точке крепится оружие. После создания сокета можно создать («заспаунить») объект в позиции этого сокета или привязать его к сокету через механизм привязки (функции AttachTo). Система немного запутанная, в отличие от Unity, зато более универсальная. Мы можем один раз настроить названия точек, тем самым отделив игровую логику от настроек моделей. Причём если у нас имеется несколько моделей с одним скелетом, то сокеты надо будет добавить только в скелет. В демке шутера сокеты используются при создании снарядов и эффектов выстрела.


Система анимации (Unity)


У нас есть персонаж, мы знаем, как работать с вводом, теперь нужно проигрывать анимацию. В Unity для этого есть Animation Controller, в нём мы описываем определённые состояния персонажа. Например, бежать, прыгать или умереть. Каждому блоку соответствует свой анимационный клип, и мы настраиваем такой граф переходов:


image


Хотя эта схема называется Animation Controller, внутренней логики у неё нет. Это просто схема переключения анимации в зависимости от состояния. Чтобы она работала, мы заранее объявляем в этом контроллере названия переменных, соответствующих состоянию персонажа. Скрипт, управляющий анимацией, зачастую сам передаёт эти состояния контроллеру каждый кадр.


В переходах между состояниями (на схеме показаны стрелочками) мы настраиваем условия переходов. Можно настроить смешивание (crossfade) анимации, т. e. время, в течение которого одна анимация затухнет, а другая продолжится, для их плавного совмещения.


Система анимации (UE4)


В Unreal всё сделано Blueprint’ами, анимация не исключение. Создаём Animation Blueprint, который будет управлять анимацией. Он тоже представляет собой граф состояний. Так выглядит машина состояний, она управляет финальной анимацией персонажа в зависимости от движения или состояния смерти.


image


Тут мы видим уже знакомые нам состояния 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.


image


В нижней части рисунка — фрагмент схемы 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)

    Let's block ads! (Why?)

    Комментариев нет:

    Отправить комментарий