[Перевод] Создание системы терминалов в UE4

image


Введение


Наша команда состоит из двух участников:

Сертач Оган (Sertaç Ogan)

Меня зовут Сертач, я занимаюсь программированием геймплея. Я разрабатываю проекты на Unreal Engine уже около 3,5 лет. Хотя моя должность называется «программист геймплея», мне нравится и программировать UI. Я работал и продолжаю работать над различными проектами, опубликованными в Steam.

Также я организую митапов по Unreal Engine в Турции. За прошлый год я организовал множество мероприятий и продолжаю заниматься их организацией. Наряду с этими мероприятиями я подумываю об организации семинаров о разработке игр совместно с университетами и старшими школами. В Турции много людей, которые хотят заниматься разработкой игр и мне нравится делиться накопленным опытом, потому что когда информацией делишься, её становится больше!
Кроме того, я состою в сообществе разработчиков Unreal Engine версии 4.18. Разумеется, даже небольшое участие в разработке этого потрясающего движка — это огромное удовольствие.

Кемаль Гюнель (Kemal Günel)

Меня зовут Кемаль, я 3D-художник. Почти 10 лет я проработал в кино- и игровой индустрии. Обычно я занимаюсь моделированием и освещением. Последние четыре года я больше сосредоточен на освещении. Пользуюсь Unreal Engine уже 3,5 года. За это время мне удалось поработать над различными игровыми проектами и множеством короткометражных и полнометражных фильмов. Я пытаюсь комбинировать вещи, которым научился в кино, и обнаруженные в процессе разработки игр. Всем этим я делюсь в формате курса на моих страницах на Artstation и Youtube, по возможности помогая другим разработчикам.

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

О проекте



В этой статье мы попробуем объяснить, как работают системы терминалов и головоломок, являющиеся базовой механикой игры TARTARUS. Мы считаем, что статья будет полезна и для других разработчиков как демонстрация того, на что способен Unreal Engine 4 и система блюпринтов. Мы постараемся как можно подробнее рассказать о проблемах, которые у нас возникали, и об их решениях. Но прежде чем перейти к системам терминалов и головоломок, мы хотели бы вкратце рассказать о проекте.

В основе своей TARTARUS — это основанная на тексте игра от первого лица. Ваша задача — спасти себя и корабль с вышедшими из строя системами от падения на планету Нептун. Игрок использует «терминал», а также механические инструменты, о которых мы расскажем ниже. Игра выпущена 22 ноября.

Мы конструировали и реализовывали систему терминалов, исходя из собственных потребностей. Но по сути она применима к любым проектам. Ниже мы опишем процесс её создания.

Программирование интерфейса


Прежде всего я хочу рассказать о логическом выполнении процесса. Если объяснять простыми словами, то он сформирован на основе получения и обработки ввода игрока. Специально для этого мы создали Widget Blueprint. Самая важная часть здесь заключается в том, что нам пришлось внести некоторые изменения, чтобы получать ввод игрока. В противном случае функция OnKeyDown не смогла бы перехватывать нажатия клавиатуры.

6401e71aeae1c0f94d062272f2b9ebe7.png


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

9ed9f4cdcc8302b4ecc57c4028797411.png


Самая важная часть здесь — получение названий нажимаемых пользователем клавиш с помощью функции Key Get Display Name. Дальнейшие действия выполняются в последующих этапах процесса.

8889de590a2ab45ecf975e0921b17988.gif


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

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

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

d0768f96ce5e53b875386ba40d6c69f8.png


Мы интегрировали весь ввод, получаемый от пользователей. Но на этом этапе у нас возникает небольшая проблема. Функция Key Get Display Name возвращает значения вида Space, Comma, Period, Hyphen, так как она возвращает названия клавиш. Поэтому нам нужно подвергать подобные клавиши процессу обработки и преобразовывать их в нужные нам значения.

b57425c038eb36910db7b3aa9a593668.png


Для этого мы создали функцию, которая вместо названий нажатых клавиш возвращает соответствующие им символы. Позже мы интегрировали её в ранее созданную схему.

1422846f82c1425ebeadf33fa93ec0ac.png


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

2d6d5f195337308af359ff5008a00fba.png


В разработанных нами для Tartarus терминалах есть несколько заданных в системе команд и их параметров. Мы распознаём их и выполняем необходимые процессе на основании возвращаемых команд и параметров.

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

2c3c6f95614fe0e44c43321eaaed4b6f.png


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

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

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

5f01be7b93a3cc39558dc84e27ee82d6.png


Она создана в виде двух Widget Blueprint, включающих в себя интерфейс системы и командные строки. Первый виджет — это MainWidget, в котором находятся фоновые процессы и дизайн. Второй виджет — это ItemWidget, в котором мы записывали описанные выше команды. Командные строки, то есть ItemWidget, находятся в ScrollBox расположенном в MainWidget, а при нажатии на Enter добавляется новая версия.

84bd742500674302129845c651d02ac4.png


bb25fed0da1af6b3570a9095b60cc37d.png


После завершения этапа дизайна нам необходимо обрабатывать команды после нажатия Enter, а затем добавлять в ScrollBox новый ItemWidget.

c275ddee3389edfc97e543b06c52de12.png


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

Материал


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

Важнейшим аспектом для нас стало научно-фантастический стиль. Мы хотели создать ЭЛТ-экраны, чтобы дисплеи терминалов выглядели яркими, живыми и ностальгическими. Мы объясним, как нам удалось создать их за несколько этапов.

232cde3f1afae5f66562252fdc82c1ae.jpg


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

772322a44912cad0047198ec445a1158.png


Для начала нам нужно определить предел «видимости» наших терминалов. «Камера захвата», расположенная в сцене, должна постоянно смотреть на поверхность и отображать ввод пользователя, получаемый из различных точек иерархии системы, при этом обеспечивая минимальные задержки и максимальную скорость. Чтобы добиться этого, мы привязали её к разделу «Texture Target», который является одним из разделов «камеры», создав «Canvas Render Target». Затем мы уже создавали материалы «Terminal» с помощью этой текстуры.

36537b65d699b91668375648407013cb.jpg


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

e495130c253fccaf67f2eb23a6494e13.jpg


Царапины на стеклянных экранах реализованы с помощью Roughness Map, как в примере ниже (изображение взято из Google).

6afd25aa8f4b443b79f30c23708d66de.jpg


Ещё один важный параметр — это максимизация, необходимая для создания эффекта Scanline, обеспечивающего иллюзию строк развёртки. Благодаря этой текстуре мы получили более приятное изображение, позволившее нам избавиться от монотонности экрана. Кажется, нам вполне удалось передать ретро-настроение.

b268eb216faa461201e0609edcb55d1c.jpg


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

d9c45de9668c0f89ce356019f45db817.png


Как вы видите, мы используем три основных текстуры. Давайте посмотрим на то, как и зачем мы управляем этими материалами. Особо важным нам казалось управлять размерами экрана, особенно для текстур Roughness и Scanline. Они не должны быть ни слишком большими, ни слишком маленькими. Нам нужно решить проблему имитации эффекта «Ghosting», который возникает при концентрации множества строк, например, тексты при нём исчезают с задержкой. Кроме того, мы не хотим трогать при этом текстуру, соединённую со значением Roughness. Поэтому мы пошли по пути разделения задачи на две части.

be416eb1d57a695702d179fea80bbcb8.jpg


095ffaa44faff8c56afbd13bb3bcde9f.jpg


Часть, которая показана в левом верхнем углу — это часть, в которой мы управляем размером царапин на экранах терминалов. Мы повторяем текстуру с помощью нода Multiply, нода Texture Coordinate и Scalar Parameter, добиваясь таким образом нужного эффекта. В правом верхнем углы мы повторяем текстуру Scanline и увеличиваем количество строк, пользуясь теми же нодами. С помощью нода Panner мы регулируем их частоту и положение. Ещё один важный момент заключается в том, что экраны терминалов состоят из стекла. Но вместо свойств просвечивания или масок материалов, создающих классический эффект проницаемости стекла, мы хотели использовать Opaque и обеспечить яркость с помощью значения Roughness. Схема показана ниже.

fc4dc75435e6ff3d7b85b6ff794c9ae1.jpg


Наконец, мы пытались добиться нужной яркости экрана с помощью значения Emissive, снова используя ScalarParameter. Полученный результат чем-то напоминает пример, который показан в Material Editor. Если приглядеться, можно заметить царапины.

b62436d7b0290a6a6070a203284ff0e2.png


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

32329a1e1ab9b77854ca45e621efa15e.jpg


Выше показаны параметры Material Instance и меш, к которому он применён. Ниже он показан в более понятном виде.

0c44002d9046a58fa572ac448e4cb444.png


2c7f4f5a16f650f72f9e355f4907434b.png


746e049c567cdce8bac0c084f82058ed.jpg


Один из использованных в игре терминалов и применение материалов.

9912d174affcd9c108813b60e1d6d4ac.png


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

UI головоломок


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

1) Определение действия/задачи, нуждающихся в выполнении/решении.

2) Сведение действия/задачи к простейшему виду и добавление шагов, нуждающихся в выполнении/решении.

3) Разработка интерфейса как «концепта» для действия/задачи, нуждающихся в выполнении/решении.

4) Усовершенствование (дизайна и кода)

1) Действие/задача в игре, требующие выполнения/решения, реализуются физическими действиями или использованием терминала. В случае физических действий (например, открытия клапанов в нужном порядке), их можно передать достаточно быстро. Благодаря модульному дизайну системы взаимодействий похожие ситуации возникают очень часто (например, подсказка «Нажмите [E]»). Но когда ситуация требует решения на экране терминала, наша работа становится гораздо сложнее и здесь та же система неприменима.

2) Например, нам нужно подключиться к другой секции корабля и открыть дверь с помощью терминала. Игрок перемещается по иерархии папок, которую довольно просто изменить для каждого терминала после понимания команд, а также того, как и где их использовать. Но всё меняется, когда дело доходит до экрана головоломки. Чтобы сделать простые действия например, открытие двери, сложными, но понимаемыми, и превратить их из одного действия в последовательность, нам пришлось потрудиться больше всего. Если продолжить пример с дверью, то список возможных действий игрока перечислен ниже.

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

B) Найти «особую секцию», в которой можно использовать эту информацию или присоединить её с помощью системы команд.

C) После получения доступа к особой секции использовать соответствующую информацию, полученную ранее, только для этой секции.

3) Если мы хотим, чтобы головоломка решалась с помощью экрана терминала, то лучше начать с разработки концепции интерфейса. Благодаря этому мы экономим время и можем предугадывать возможные проблемы. В то же время мы можем понять, подходит ли головоломка к теме игры. Самой большой трудностью было размещение экрана терминала в пределах экрана и обеспечение видимости всех его частей. Он должен быть одновременно функциональным, наглядным и обеспечивать звуковую обратную связь. Кроме того, мы хотели, чтобы некоторые части были подвижными. Поэтому все элементы головоломок были отдельными частями и иногда создавались отдельными блоками. Ниже показаны некоторые из концептов дизайна, использованные в игре версии и некоторые части головоломок.

4) Последним идёт этап усовершенствования. Здесь мы думали над тем, как мы можем усовершенствовать готовый интерфейс, при этом сохранив его понятность. Даже хотя нам иногда этого не хотелось, от многих элементов приходилось отказываться или изменять их.

e7a81e18fa539c694260cfb9a7c7152a.jpg


f8eeaa0431e68e009737694a9b60080a.jpg


345cd4db12e06c5649235fce1c1c22dc.jpg


3c8ea7f0480757ab021266394888b21d.jpg

Цвета и шрифты


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

Ещё одна проблема, связанная с цветами терминала, заключалась в том, что пользователь может изменять цвет текста терминала с помощью специальной команды. Игроки могут делать это с помощью команды Color. Сначала мы думали, что никто не будет её использовать, но наблюдая за «геймплейными» видео мы заметили, что у многих игроков игра работает с нестандартными цветами. Мы хотели оставить эту функцию по двум причинам. Первая заключалась в том, что пользователи DOS и люди, знакомые с командной строкой, были знакомы с этим и мы хотели возродить их воспоминания. Вторая причина заключалась в том, что благодаря смене цвета игровой процесс становился более личным процессом. Если вкратце подвести итог, то мы посчитали, что никто не пострадает от монотонности экранов и небольшого оживления цветов. Мы считаем, что сделали верное решение.

Следующей важной проблемой стали шрифты, отображаемые на экранах терминалов. Они не должны быть слишком тонкими и слишком жирными, поскольку экраны терминалов имеют изогнутые поверхности, как ЭЛТ-мониторы. Мы не хотели, чтобы символы у краёв экрана выходили за него или становились нечитаемыми. Они должны были соответствовать общей теме игры и терминалам. Мы выбрали шрифт, который показался нам подходящим, читаемым и жирным, похожим на стиль DOS и Commodore.

c01a22d13d7ee6ceffc4245ab8d14a0b.jpg


Оптимизация


Ещё одной проблемой стали проседания FPS в игре. Мы завершили этап прототипирования терминалов и после интегрирования их в игру мы заметили снижение FPS в среднем на 30%. Благодаря инструментам отслеживания скорости мы выяснили, что причиной проблемы является камера захвата (Capture Camera). Tartarus — это игра, в основном связанная с использованием терминалов. Но в то же время мы должны решать все другие проблемы внутри корабля, управляя главными героями. Поэтому при управлении персонажем камера захвата не обязана захватывать каждый кадр. Мы деактивировали некоторые параметры, позволявшие ей захватывать каждый возможный кадр. Для этого мы выбрали Capture Camera и деактивировали параметры Capture Everyframe и Capture On Movement в панели Details.

18c38bdd7f302a99ccc222c2a0d9b82e.png


Мы восстанавливаем эти параметры в момент переключения на управление терминалами.

66e7604ea1642e85249f44e16a9cfeef.png


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

84a66b3554d1e9054a959a22165b0985.gif


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

Фокус


Ещё одна проблема заключалась в том, что в некоторых ситуациях, например, при нажатии Alt+Tab, терминалы теряют фокус. Это не было проблемой, если игрок находился в меню или взаимодействовал с мышью. Но мы не хотели, чтобы игроку приходилось использовать мышь в ситуации, когда он будет пользоваться только клавиатурой. Кроме того, некоторые пользователи могли решить, что это баг игры. Поэтому мы решили использовать следующую схему. В этом случае пользователь постоянно фокусируется на терминале, пока он не отходит от него.

51065fb3955580c8d760065680100c23.png


Событие Event On Focus Lost запускается в момент потери фокуса, возвращая фокус терминалу. Но важный момент заключается в том, что когда пользователь хочет покинуть терминал намеренно, то мы присваиваем переменной Lost Focus значение true. Даже если в таком случае срабатывает это событие, фокус не передаётся автоматически терминалам.

Мысли в заключение


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

f018bd306ab07af7145896e59fa0d24c.png


995b51e7f06d9c211a2651621604706e.png

© Habrahabr.ru