Ограничения 8-битных игр и их точное воссоздание в Unity
Ретро-игры с простыми механиками и пиксельной графикой способны пробуждать тёплые воспоминания у опытных игроков, и в то же время вполне доступны для более юной аудитории. Сегодня многие игры называются «ретро», но для создания ностальгического стиля требуются усилия и планирование. Именно поэтому мы пригласили ребят из Mega Cat Studios помочь нам в обсуждении этой темы. В этом посте мы рассмотрим всё необходимое для создания аутентичной графики в стиле игр для NES, в том числе важные параметры Unity, графические структуры и цветовые палитры.
Создание аутентичного арта в стиле NES
Для начала мы рассмотрим основы создания графики для игр, соответствующих ограничениям классической Nintendo Entertainment System. Это поколение консолей накладывает серьёзные ограничения на художников, стремящихся воспроизвести его аутентичную графику. Это ограничения на используемые палитры и на размер и количество объектов на экране. Кроме того, важно учитывать, что разрешение этой консоли составляет 256×240 пикселей.
Палитры
При создании совместимой с NES графики художник должен учитывать множество ограничений. Во-первых, самым важным из таких ограничений является то, как в изображении используются цветовые палитры. NES уникальна тем, что все возможные цвета палитры «зашиты» в консоль. NES выбирает, какие цвета использовать в изображении, отправляя наборы значений графическому процессору NES, после чего графический процессор возвращает цвета, соответствующие этим значениям. Ниже показано изображение цветовой палитры NES:
Эти цвета невозможно менять, потому что они являются частью самой консоли. Во всех играх NES используются сочетания этих цветов, из которых составляются изображения.
Субпалитры
Для создания используемых в играх сочетаний создаются субпалитры, которые привязываются к внутриигровым спрайтам или фоновым изображениям. NES разбивает палитру на субпалитры, которые можно назначать спрайтам и фонам. Каждая субпалитра содержит один общий цвет, который используется во всех субпалитрах, и три уникальных цвета. Она может загружать по четыре субпалитры для фонов и четыре субпалитры для спрайтов. В случае спрайтов общий цвет в начале каждой субпалитры считается прозрачным.
Вот пример используемых в игре набора субпалитр. В верхней части показаны субпалитры фона, внизу — субпалитры спрайтов. В этом примере чёрный — общий цвет, используемый во всех субпалитрах. Так как в спрайтах общий цвет считается прозрачным, в субпалитрах для спрайтов нужен второй чёрный цвет, который используется как видимый цвет.
Назначение субпалитр
Ограничения на использование палитр становятся ещё более строгими, когда художник переходит к тому, как палитры используются в игре. Чтобы объяснить это, нужно подробнее рассказать о том, как ретро-консоли хранят и отображают графику. Графика любой ретро-консоли хранится внутри игры как тайлы размером 8×8 пикселей. Благодаря этому художники могут экономить место, повторно используя тайлы для разных объектов. (Например, части дороги можно использовать заново, создав из них обрыв или здание). Также важно то, что информация о цвете не хранится вместе с графикой. Все тайлы сохраняются в монохромной палитре. Благодаря этому когда тайл отображается в игре, ему можно назначить субпалитру и одновременно отображать на экране с разными субпалитрами. Это важно при воссоздании графики ретро-консолей на современных платформах, потому что это влияет на то, как мы назначаем палитры графике.
NES по разному назначает палитры спрайтам и фонам. Спрайтам она назначает палитры потайлово. Это значит, что с каждым тайлом 8×8 в спрайте может быть связана одна из четырёх субпалитр спрайта.
В этом персонаже-ниндзя используются две субпалитры, увеличивающие цветовую глубину. Справа видно, что он разделён на отдельные тайлы 8×8. В таком разделённом виде заметно, что светло-бирюзовый и тёмно-красный, использованные в мече и головной повязке, уникальны для этих тайлов, а тёмно-фиолетовый и чёрный контур используются в оставшихся трёх тайлах.
На фоны накладываются гораздо более строгие ограничения. Палитры фонов назначаются фрагментам размером 16×16. Привязки субпалитр фона всего экрана называются Attribute Tables (таблицами атрибутов). Именно из-за этих таблиц в большинстве ретро-изображений активно используются повторяющиеся тайловые сегменты. Такие сегменты обычно состоят из тайлов 16×16, благодаря чему помещаются в таблицы атрибутов. Несмотря на то, что это было вызвано аппаратными ограничениями, такие тайлы фонов 16×16 стали определяющей характеристикой ретро-графики и теперь абсолютно необходимы для её воссоздания в современных играх.
Вот пример фона красивого города в стилистике RPG, сделанного с учётом этих ограничений. На изображении справа видно, что он хорошо разделяется на блоки размером 16×16 пикселей, а палитры задаются для каждого блока. Для экономии места такие элементы, как черепица крыши, трава и кирпичи на мосту, составлены из повторяющихся сегментов этих блоков. Черепица крыши маленьких зданий использует одинаковые тайлы, но им назначаются разные субпалитры, придающие ей уникальный внешний вид.
Наложение спрайтов
Даже несмотря на то, что художники могут использовать разные субпалитры для каждого тайла спрайта 8×8, у них может возникнуть ситуация, когда необходимо придать спрайту бОльшую цветовую глубину. В таком случае можно использовать наложение спрайтов. Наложение спрайтов — это разделение спрайта на два отдельных спрайта и размещение их друг поверх друга. Это позволяет художникам обойти условие использования одной субпалитры на тайл 8×8. Благодаря этому художники по сути могут удвоить количество цветов, которые можно использовать в одной области размером 8×8. Единственным серьёзным недостатком такого подхода являются ограничения рендеринга спрайтов. NES одновременно способна отображать только 64 спрайтовых тайла размером 8×8, и только по восемь спрайтовых тайлов в одной горизонтальной линии. Если это ограничение достигнуто, все осталььные тайлы рендериться на экране не будут. Именно поэтому во многих играх для NES при большом количестве спрайтов на экране они начинают мерцать. В таком случае единственный способ отображения всех спрайтов — показывать их в перемежающихся кадрах. Такие ограничения нужно учитывать при наложении слоёв спрайтов, потому что это удваивает не только количество цветов, но и количество спрайтовых тайлов в одной горизонтальной линии.
Вот пример наложения спрайтов (Sprite Layering) в действии. Слева показана исходная трёхцветная версия спрайта пирата-призрака. Художник разделил её на две части — тело/шляпу и лицо/руки, а затем назначил им разные палитры. Справа показан результат наложения двух элементов друг на друга.
Чтобы обойти ограничения таблицы атрибутов, можно реализовать слои спрайтов при помощи фона. Этот трюк обычно используется для статических изображений, например экранов сюжета и портретов персонажей, что придаёт им гораздо большую глубину цвета. Чтобы реализовать это, художник должен отрисовать часть изображения как фон, а затем наложить спрайты поверх него для заполнения оставшихся частей.
В портрете пирата-призрака тоже используются слои спрайтов, придающие ему бОльшую глубину. Его зелёный череп рендерится на экране как спрайт, а воротник и шляпа — как часть фона. Это позволяет художнику использовать больше цветов в сегменте 16×16, чтобы полностью обойти ограничения таблицы атрибутов.
Графические банки
Чтобы объяснить ещё одно важное ограничение NES, нам сначала нужно вернуться к тому факту, что графика хранится в тайлах. Тайлы графики хранятся на страницах по 256 тайлов, и тайлы из этих страниц не могут загружаться во VRAM в разных местах, поэтому становится сложно на лету соединять и смешивать тайлы с разных страниц. VRAM консоли NES способна отображать одновременно 512 таких тайлов. Кроме того, она делит тайлы наполовину, для спрайтов и фонов. Это значит, что одновременно консоль может отображать только 256 тайлов спрайтов и 256 тайлов фона. Если художник хочет отобразить большое разнообразие спрайтов и фоновых элементов, такое ограничение сильно ему мешает.
Вот графическое представление фоновых и спрайтовых тайлов игры, загружаемых во VRAM. Консоль хранит фоны и спрайты на отдельных страницах.
Чтобы обойти это ограничение, в NES применяется функция, позволяющая художнику разбить каждую страницу на частичные страницы, называемые банками. Поэтому хотя NES не может загружать отдельные тайлы из разных точек графических данных, она способна загружать в разное время отдельные части страницы. В большинстве игр такие банки имеют размер 1 КБ и 2 КБ. Банк 1 КБ соответствует одной четвёртой страницы, или 64 тайлам, а банк 2 КБ — половине страницы, или 128 тайлам. Художник должен принять решение, хочет ли он зарезервировать каждый тип банка под элементы спрайтов или фонов, потому что необходимо использовать оба типа. Это значит, что невозможно иметь банки 1 КБ и для спрайтов, и дя фонов. Одна страница должна использовать банки 1 КБ, а другая — 2 КБ. Как правило, большинство игр использует банки 1 КБ под спрайты, а 2 КБ под фоны, потому что тайлсеты фонов обычно более статичны и требуют меньшей вариативности и замены на лету.
Разбитое на банки изображение, которое мы видели выше. В левой части показаны фоны, использующие банки по 2 КБ, то есть разделённые пополам, а в правой части показаны спрайты, разделённые на банки по 1 КБ. Каждый банк можно свободно заменять на лету.
Полезность банков 1 КБ для спрайтов довольно важна. Если спрайт персонажа имеет большое количество анимаций, не помещающихся в одну страницу, и в то же время необходимо загружать другие спрайты, то отдельные действия можно загрузить в банки 1 КБ, а затем заменять их в зависимости от происходящих на экране действий. Это также увеличивает вариативность спрайтов, которые могут быть использованы в одной области игры. Например, если игрок должен встретить на уровне игры шесть типов врагов, но в страницу спрайтов помещается только игрок и три других типа спрайтов, то когда один тип врага пропадает с экрана, игра может заменить один из банков врагов новым типом врага.
Один из немногих серьёзных недостатков применения банков по 1 КБ для спрайтов и по 2 КБ для фонов заключается в способе обработки фоновой анимации консолью NES. Чтобы анимировать фоновый элемент игры для NES, художник должен создать дублирующие банки анимированных фоновых элементов. Каждый новый дубликат банка будет содержать следующий кадр анимации для каждого из анимированных элементов. Эти банки сменяют друг друга, создавая анимацию. Если художник использует для фонов банки размером в полстраницы, то для хранения всех этих дублирующих банков может потребоваться много места. Один из способов обхода этого ограничения заключается в размещении всех анимированных фоновых элементов для всей игры в одном банке. Но тогда художник сталкивается с другим ограничением: у него остаётся всего 128 тайлов для статических элементов каждого фона. Каждый художник сам принимает решение о том, какой способ хранения ему подойдёт больше.
Трюки со слоями
Во многих играх той эпохи использовались трюки для создания таких эффектов, как параллаксный скроллинг фона, но они тоже ставят перед художниками и дизайнерами сложную задачу. Более поздние 16-битные консоли имели поддержку множественных фоновых слоёв, но NES не обладала такой возможностью. Все фоны были единым плоским изображением. Для создания ощущения глубины и слоистости применялись различные программные трюки. Например, для создания параллаксного скроллинга разработчики могли задавать регистр, который сообщал, когда на экране отрисовывалась определённая горизонтальная линия (называющаяся линией развёртки).
Затем они могли использовать этот регистр для управления скоростью и направлением скроллинга экрана. Благодаря этом можно создавать горизонтальную строку фона, которая прокручивается с отличной от остального фона скоростью. Для художников и дизайнеров трюк заключался в том, чтобы учитывать то, что фон по-прежнему является одним плоским изображением. Если платформа или любой другой элемент, который должен находиться «перед» медленно движущимся фоном, помещается в эту область, то он тоже будет скроллиться медленнее, чем остальная часть изображения. Это значит, что дизайнерам приходилось располагать фоновые элементы в сцене таким образом, чтобы эффект не искажался.
В этом примере выделенную красным область для имитации глубины можно заставить скроллиться медленнее, чем остальная часть фона. Интерфейс в верхней части экрана не скроллится, хоть и тоже является частью плоского фонового изображения.
Существует и ещё один трюк, благодаря которому художники могли «переместить» один из фоновых элементов вперёд. На NES разработчики могли сделать так, чтобы приоритет спрайта был меньше нуля. Если это сделать, то спрайт будет отображаться под всеми непрозрачными пикселями фона. Приоритеты спрайтов тоже можно изменять и переключать на лету, благодаря чему отдельные элементы могут при необходимости менять приоритет спрайта.
Рабочие процессы Unity для создания максимального ощущения ретро
Скачайте пример проекта и начните работать вместе с нами!
Mega Cat Studios из Питтсбурга, штат Пенсильвания, превратила создание ретро-игр в форму искусства. На самом деле некоторые из их игр даже можно приобрести на картриджах и сыграть в них на таких ретро-консолях, как Sega Genesis.
Little Medusa и Coffee Crisis
Недавние изменения в рабочих процессах Unity превратили движок в среду, очень хорошо подходящую для создания ретро-игр. Система 2D Tilemap была усовершенствована и теперь поддерживает тайловые карты из прямоугольных, шестиугольных и изометрических тайлов! Кроме того, можно воспользоваться новым компонентом Pixel Perfect Camera, чтобы добиться целостного попиксельного движения и графических эффектов. Можно даже использовать стек постобработки для добавления всевозможных красивых экранных ретро-эффектов. Однако прежде чем всё это делать, необходимо правильно импортировать и настроить ассеты.
Подготовка спрайтовых ассетов
Чтобы ассеты были чёткими и пиксельными, их нужно сначала правильно сконфигурировать. Выберите каждый из используемых ассетов в окне Project, а затем измените в инспекторе следующие параметры:
- Filter mode смените на «Point»
- Compression смените на «None»
Другие режимы фильтрации приводят к небольшому размытию изображения, что нарушает чёткий пиксельный стиль, к которому мы стремимся. При использовании Compression данные изображений сжимаются, что приводит к небольшому снижению точности. Это важно учитывать, потому что из-за сжатия некоторые пиксели могут сменить цвет, потенциально изменив всю цветовую палитру.
Чем меньше размер и количество цветов у спрайта, тем сильнее будет влиять на него сжатие. Вот пример сравнения обычного сжатия (по умолчанию) и отсутствия сжатия.
Обычное сжатие / Изображение без сжатия — выглядит точно так же, как оригинал
Ещё один аспект, который нужно учитывать — это параметр Max Size изображения в Inspector. Если спрайтовое изображение имеет по любой из осей размер больше, чем свойство Max Size (по умолчанию оно равно 2048), то оно будет автоматически подогнано под максимальный размер. Обычно это приводит к потере качества и изображение начинает выглядеть расплывчатым. Так как некоторые платформы не могут поддерживать текстуры размером более 2048 по любой из осей, лучше всего будет оставаться в этих пределах.
Max size имеет значение 2048 / А теперь max size равен 4096
На изображении выше показан спрайт из листа спрайтов (спрайтшита), имеющего по одной оси размер 2208 при max size равном 2048. Как видите, увеличив свойство Max Size до 4096, мы смогли обеспечить правильный размер изображения без потери качества.
Наконец, при подготовке спрайта или листа спрайтов необходимо установить параметру pivot unit mode значение Pixels вместо Normalized.
Благодаря этому опорная точка (pivot point) изображения будет определяться на основе пикселей, а не в плавном интервале от 0 до 1 по каждой оси изображения. Если опорная точка спрайта не будет точно привязана к пикселю, то мы потеряем расположение спрайта с точностью до пикселя. Опорные точки для спрайтов можно задавать в редакторе Sprite Editor, который открывается в Inspector, когда выбран ассет спрайта.
Установка пакета 2D Pixel Perfect
Подготовив ассеты, мы можем сделать камеру пиксельно-точной (pixel perfect). Пиксельно-точный результат будет выглядеть чётким и ярко выраженным. Признаками неточного пиксель-арта становятся размытость (искажение) и прямоугольность некоторых пикселей.
Пакет 2D Pixel Perfect можно импортировать при помощи Package Manager движка Unity. Нажмите в панели инструментов на меню Window, а затем выберите Package Manager. В новом окне нажмите на Advanced и поставьте флажок Show preview packages. Выберите в списке слева 2D Pixel Perfect, а затем нажмите Install в правом верхнем углу окна.
Вот и всё. Теперь вы готовы начать работу с компонентом пиксельно-точной камеры.
Высокий уровень пиксельной точности
Компонент Pixel Perfect Camera добавляется к компоненту Camera движка Unity и дополняет его. Чтобы добавить его, перейдите к основной камере и добавьте к ней компонент Pixel Perfect Camera. Если компонента Pixel Perfect Camera в меню нет, то выполните описанные выше действия, чтобы импортировать его в проект.
Теперь давайте изучим имеющиеся параметры.
Сначала я рекомендую включить «Run In Edit Mode» и установить в окне Game режим соотношения сторон «Free Aspect», чтобы можно было свободно изменять окно игры. Компонент будет отображать в окне игры полезные сообщения, указывая, является ли отображение пиксельно-точным в текущем разрешении.
Теперь можно пройтись по каждому параметру и посмотреть, как они влияют на внешний вид игры.
- Assets Pixels Per Unit — это поле является ссылкой на параметр, который можно выбрать для каждого ассета в инспекторе. В общем случае каждый ассет, который будет использоваться в мировом пространстве игры, должен иметь одинаковое значение пикселей на единицу (pixels per unit, PPU), и здесь тоже нужно указать такое же значение. Если мир игры представлен в виде сетки тайлов и спрайтов, и размер каждого составляет 16 на 16 пикселей, то логично будет указать PPU 16 — каждый тайл сетки будет иметь в координатах мирового пространства размер 1. Обязательно введите сюда выбранное значение PPU.
- Reference Resolution — здесь нужно указать разрешение. в котором должны отображаться все ассеты. Если вам нужен ретро-стиль, то обычно это очень низкое разрешение. Например, Sega Genesis имела разрешение 320×224. При портировании игры с Sega Genesis мы используем справочное разрешение 320×224. Например, при соотношении сторон 16:9 хорошо подойдёт 320×180 или 398×224 (если вы хотите сохранить разрешение по вертикали).
- Upscale Render Texture — этот параметр заставляет сцену рендериться как можно ближе к справочному разрешению с последующим растягиванием до размеров экрана. Так как при использовании этого параметра экран заполняется полностью, мы рекомендуем его, если вам нужна полноэкранная пиксельно-точная графика без границ по краям. Также «Upscale Render Texture» сильно влияет на внешний вид спрайтов при их повороте.
1. Оригинал (без поворота) 2. Без Upscale Render Texture (с поворотом на 45 градусов, пиксельная точность потеряна, потому что размер пикселе по диагональным краям варьируется) 3. С Upscale Render Texture (поворот на 45 градусов, пиксельная точность сохраняется, все пиксели имеют одинаковый размер, но спрайт по сравнению с оригиналом выглядит менее точным.)
-
Pixel Snapping (можно включить только при отключенном Upscale Render Texture) — при включении этого параметра рендереры спрайтов (sprite renderer) будут автоматически привязываться к сетке в мировом пространстве, а размер сетки будет зависеть от выбранного PPU. Учтите, что на самом деле это не влияет на позиции transform объектов. В результате этого по-прежнему можно плавно интерполировать объекты между позициями, но визуально движение останется пиксельно-точным и привязанным к сетке.
- Пример:
Слева Pixel Snapping отключен. Фон находится в позиции (0, 0), а спрайт персонажа — в (1.075, 0). Некоторые пиксели выровнены неверно. Заметьте, что некоторые пиксели накрыты тенью только наполовину. Справа Pixel Snapping включен. Те же позиции — фон в (0, 0), спрайт персонажа в (1.075, 0). Пиксели идеально привязаны друг к другу.
- Crop Frame (X и Y) — этот параметр усекает видимую область мирового пространства, чтобы она точно соответствовала справочному разрешению, и добавляет чёрные границы, заполняющие все пробелы по краям экрана.
- Stretch Fill — всегда доступен, если включить x и y для Crop Frame. Этот параметр заставляет камеру масштабировать окно игры так, чтобы оно соответствовало экрану с сохранением соотношения сторон. Так как масштабирование выполняется не только для целочисленных кратных справочного разрешения, то во всех остальных случаях пиксельная точность будет утеряна. Однако преимущество этого параметра заключается в том, что несмотря на потерю пиксельной точности во многих разрешениях, по краям не будет чёрных полос и экран окажется заполненным полностью. Несмотря на то, что stretch fill часто вызывает размытие, обычное предупреждение не отображается.
Персонаж и фон, размытые stretch fill
Рекомендации по использованию Pixel Perfect Camera
Если вам нужно пиксельно-точное отображение с привязкой к сетке, то я рекомендую следующее:
- Выберите справочное разрешение, которое никогда не будет больше разрешения окна игрока (например 320×180).
- Включите или отключите Upscale Render Texture
- Включите этот параметр, если в игре будут повороты, отличающиеся от 90, 180 и 270 градусов и если вам нравится визуальный эффект, создаваемый этим параметром для повёрнутых спрайтов.
- Upscale Render Texture в некоторых разрешениях может создавать не пиксельно-точные изображения; это зависит от справочного разрешения. Поэкспериментируйте с этим параметром в разных разрешениях экрана, включив в компоненте Pixel Perfect Camera опцию Run in Edit Mode и проверьте, возникают ли проблемы с вашим справочным разрешением. Если вам удалось добиться пиксельно-точного изображения во всех целевых разрешениях, то такая конфигурация будет наилучшей для пиксельно-точной игры.
- Включите или отключите Pixel Snapping
- Это в основном зависит от ваших личных предпочтений. Без привязки движение будет гораздо более плавным, но пиксели окажутся не выровненными.
- Включите Crop Frame X и/или Y, если отключен Upscale Render Texture
- Если вам не удаётся добиться пиксельно-точных результатов при upscale render texture, усечение по X и/или Y обеспечит пиксельно-точное изображение во всех разрешениях выше справочного, но в некоторых разрешениях создаст большие границы по краям экрана.
- Отключите Stretch Fill
Мы рекомендуем настроить камеру так, чтобы она была оптимизирована под соотношение экрана 16:9, в том числе и справочное разрешение, если это возможно. На момент написания статьи большинство игроков играет на мониторах с соотношением сторон 16:9 и в разрешении 1920×1080. Например, справочное разрешение 320×180 имеет соотношение 16:9, а потому при разрешении 1920×1080 и других разрешениях, кратных 320×180, например, 1280×720, чёрных границ по краям экрана не будет.
В панели инструментов Unity можно зайти в пункт Edit > Project Settings > Player и ограничить соотношения сторон, поддерживаемые игрой. Если вы обнаружите, что определённая конфигурация выглядит хорошо в нужном вам соотношении, но плохо подходит к отдельным соотношениям, то здесь можно отключить эти соотношения. Однако учитывайте, что не у всех пользователей экран будет хорошо совместим с вашими ограничениями, поэтому так делать не рекомендуется. Вместо этого используйте cropping, чтобы у таких пользователей отображались чёрные границы, и игра не запускалась в разрешении, не соответствующем их экрану.
Заключение
Когда вы пытаетесь создать проект, аутентичный для ретро-консоли, необходимо учитывать множество технических аспектов, о которых в современной разработке никто не думает. Из-за особенностей рендеринга изображений и работы с малым объёмом памяти старых машин дизайнерам приходилось мыслить творчески и обходить аппаратные ограничения. В современную эпоху нам нужно знать об этих ограничениях и методиках для точного воссоздания внешнего вида и дизайна игр той эры. В следующем посте мы рассмотрим ограничения дизайна эпохи 16-битных игр, а также рабочий процесс Unity, необходимый для воссоздания настоящего стиля «старого телевизора».