Pull to refresh

Making of NES gameinvitro «MULTIDEFENDER»

Reading time 13 min
Views 1.4K
Итак, конец марта, на носу Revision 2020, впереди ещё долгий месяц самоизоляции, и всё началось, как обычно, с того, что мы решили написать очередную демку для NES. Конечно, “демка” — это немного громко сказано. Исходя из собственных возможностей и количества оставшегося времени до дедлайна, курс был взят на создание простенькой инвитры к Мультиматографу (российское demoparty в Вологде), состоящей из скроллера с информацией и нехитрого эффекта эквалайзера, основанного на логотипе Multimatograf.



Как это работает — разберём подробно, каждую часть, от начала и до конца.

Скроллер и лого


Сначала была написана именно эта часть, как основная. Все “обвесы” были придуманы и введены уже по ходу, как это всегда и случается. Градиент, и логотип выполнены на фоновом слое. Мы перебрали несколько вариантов внешнего вида:

градиент вариант 1  градиент вариант 2

Пока наконец не утвердили текущий:

градиент финальный

Муки выбора объясняются довольно просто: не совсем простая организация экрана приставки и его атрибутов. Экран NES состоит из 32х30 тайлов размером 8х8 пикселей. И если например на ZX Spectrum или Commodore 64 один двухцветный атрибут действует на одно знакоместо, то здесь атрибут четырёхцветной палитры покрывает область аж из 2х2 знакомест, или квадрат 16х16 пикселей. Палитр-атрибутов доступно всего 4. Поэтому, чтобы отрисовать и градиент и красный логотип пришлось задействовать 3 палитры с общими цветами в каждой из них.

фон в программе Nesst

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

По ходу дела выяснились некоторые нюансы, как например то, что шахматный дизеринг на ЭЛТ телевизорах будет выглядеть совсем не так, как это выглядит в эмуляторе, что и было проверено на реальной приставке с реальным телевизором:

искажения шахматной текстуры на ЭЛТ телевизоре

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

Итоги:

  1. Фон и движущийся логотип — это одно целое. Никаких отдельных спрайтов, только монолитный фон.
  2. Синусоидальная горизонтальная траектория управляет положением всего фона.
  3. Чтобы визуально “разделить” фон и логотип, был выбран “европейский” шахматный стиль рисовки + добавлены спрайты-уголки в середине экрана на правом и левом краю, чтобы ещё прочнее закрепить иллюзию неподвижного фона.

Скроллер


Строка со скроллером отделена от основного экрана “сплитом”. Этот приём применялся во многих классических играх, например — HUD в Super Mario. Но, в отличие от Super Mario, обе части нашего “разделённого” экрана имеют свой автономный скролл. Реализовать односторонний скроллинг на Денди — проще простого, собственно, так и сделаны все платформеры со скроллом экрана: каждая новая буква (или столбец пейзажа/лабиринта) отрисовывается в соседнем экране каждые +16 пикселей, затем камера смещается на 16 пикселей вправо, и всё повторяется снова.

Чтобы скроллинг не казался совсем скучным, к нему было применено аж два эффекта:

1. Подобие “гигаскрина” на NES. Почему бы и нет? Для этого шрифт был отрисован шахматной текстурой: чётные пиксели одним индексом палитры, нечётные — вторым, между которыми выполнялся переход по таблице: сначала четных точек текстуры, затем нечетных. В результате мы получили промежуточный цвет между существующими цветами из палитры NES, и визуально изменение цвета выглядит более плавным. Не сильно заметный эффект, если не знать об этом, но приглядевшись к скроллу, вполне можно понять о чём речь:

шрифт для скроллинга

2. “Прыжки” строки скроллинга в такт ударным. Для этого отслеживался буфер плеера и его шумовой канал, а к горизонтальному скроллу добавлялся вертикальный.

Итоги:

Денди позволяет разделить экран как минимум на две части, каждую из которых можно скроллить автономно. Этих двух частей нам хватило и для логотипа и для скроллера. Для наглядности предлагаем посмотреть на “камеру” логотипа:

анимация камера логотипа

И “камеру” скроллера:

анимация камера скроллинга

Теперь пора переходить к обвесам.

Логотип NESDEV


Вообще это уже вторая наша работа в данном составе, поэтому для такого объединения был выбран лейбл “Nesdev team” по названию Telegram канала, в котором мы обсуждаем всё, что связано с консольками, их программированием и новостями. Внешний вид надписи был выбран по аналогии с логотипом Titan в их демо-работе Overdrive, который, в свою очередь, пародировал стиль фирменного логотипа Sega, а под градиентный эффект мы застолбили некоторое количество картриджа таблицами для фейда. Получилось неплохо:

анимация фейды по таблицам

Но, в силу ограниченного количества свободного пространства на картридже, после некоторых ревизий было решено от некоторых из этих таблиц избавиться, и перевести фейд на стандартные решения библиотеки neslib (кроме текста под логотипом):

анимация фейды neslib

Вологодские кружева


анимация вологодские кружева

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

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

кружева - референс  кружева отрисовка

В этой картинке 387 уникальных тайлов. Беда в том, что страничка графики NES содержит 256 тайлов. В данном случае на помощь приходит всё тот же старый добрый split screen. Если до сплита у нас была активна первая страничка графики, то после сплита мы можем щёлкнуть банкой и включить второй чарсет. Это действительно только по отношению к мапперам с CHR-ROM, но опять же, все подробности в той самой статье, крайне рекомендуем, кто ещё не ознакомился.

Свободных банок с графикой у нас оставалось аж ещё две, поэтому экран был разделен по вертикали на 2 части и было подобрано оптимальное место сплита, в котором переключался банк графики. Таким образом мы поимели два отдельных чарсета по 256 тайлов для верхней и нижней части, в каждой из которых разместилось примерно по 192 тайла. Этого вполне хватало, и оставался необходимый запас на дополнительные тайлы для анимации пересекающихся частей узора.

Банк 1:

кружева банк 1

Банк 2:

кружева банк 2

Нетрудно заметить, что фоновые страницы банков имеют разные наборы тайлов, а спрайтовые страницы одинаковы: гранулярность переключаемых банков — 8 килобайт (4 на фон, 4 на спрайты). Количество разных тайлов для слоя спрайтов получилось меньше 256, поэтому для удобства вывода все спрайты помещены в одну страничку, которая продублирована в обоих 8 килобайтных банках.

Палитра имеет 3 цвета, которые мы можем менять (четвертый — фоновый, общий, в данный момент чёрный), значит фактически мы можем организовать 4 фазы одноцветной анимации на одно знакоместо используя комбинацию тайлов спрайта и заднего фона.

фазы анимации спрайтов и фона

Для этого рисуем первые три фазы анимации в тайле спрайта индексами палитры 1, 2, 3 (на картинке для наглядности каждая фаза выделена красным, синим и зеленым цветами соответственно) и тайл фона в качестве четвертого тайла анимации.
В теории это выглядит так:

Фаза 1: индекс палитры 1 = белый, остальные — цвет фона (чёрный);
Фаза 2: индекс палитр 1 и 2 = белый, остальные — цвет фона;
Фаза 3: индекс палитр 1, 2, 3 = белый.
Фаза 4: удаляем спрайт и “впечатываем” в фон заключительную фазу из фонового чарсета.

Переходим к следующему знакоместу, и т.д.

Традиционно, для наглядности, можно посмотреть что происходит на спрайтовом слое отдельно:

кружева - анимация спрайтового слоя

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

Фоновая картинка состоит из 3х цветов: два оттенка серого и один белый цвет (палитра 0). Всё это продублировано в палитру 2, за одним исключением: один из серых цветов изменен на черный, и изначально экран залит именно этой палитрой.

В процессе вывода вензелей, мы “построчно” заменяем палитру в экране, создавая эффект “раздвигания” дополнительного слоя с детализацией вензелей. На картинке ниже этот слот палитры отмечен темно-серым цветом, красным положение нулевого спрайта, а желтым время ожидания до переключения банки (зачем именно так — описано чуть ниже):

кружева - места сплита и переключения банок

Вся анимация разделена на 3 фазы (фаза 4 объединена с фазой 1), чтобы уложиться во время звучания вступительного музыкального фрагмента.

Каждая из фаз длится 4 фрейма, и в каждом из фреймов отрисовывается отдельно:

0 фрейм — фоновые тайлы верхней части
1 фрейм — спрайты верхней части
2 фрейм — фоновые тайлы нижней части и цветы (их появление задано таблицей по глобальному счетчику фреймов)
3 фрейм — спрайты нижней части

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

Была написана прототипирующая утилита (javascript+html), нарисованы несколько слоев фона с удалением части графики для анимации пересекающихся мест и несколько слоёв маски, затем покадрово прокликано, в результате чего на выходе появились бинарники для тайлов, экрана и код на Си с массивами данных анимации. Также для полноты картины были добавлены все запланированные эффекты и музыка для этой части чтобы на этапе прототипирования можно было увидеть полную картину. Утилита добавлена в репозиторий, результат работы хранится в localStorage, также при обновлении страницы идет прокликивание и пересчет всех данных и выводится массив анимации в виде json в текстовое поле.

утилита парсинга анимаций

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

Итоги: комбинация анимированных спрайтов и отрисовка фона познакоместно — это старая добрая традиция, использовавшаяся как в играх, так и в некоторых демо-продуктах (HEOHdemo), но выполненная с разными подходами можно получить разные эффекты, всё зависит от вашей фантазии.

Мозаика “MULTIMATOGRAF”


Здесь всё просто — разделённый на “кубики” логотип + массивы начальных координат и смещений. Наверное стоит упомянуть небольшой использованный хак. Лимит спрайтов NES — 8 штук на строку растра, но нам никак не удавалось избавиться от девятого — “нулевой спрайт”, которым экран разделяется на две части, и это создавало небольшую проблему: один из спрайтов логотипа пропадал, пересекая Y позицию “нулевого” спрайта. Менять положение логотипа не хотелось, тем более было потрачено немало времени на его отрисовку в основной части интро, и методом проб и ошибок было найдено подходящее место для нулевого спрайта (в начале строки предшествующей сплиту), а после самого сплита спрайт прятался за пределами экрана, плюс была организована программная задержка до конца строки после чего уже переключался банк с чарсетами. В результате спрайт логотипа не отображался только несколько фреймов и только в одной строке экрана, что не резало глаза и вполне вписалось в наши цели.

Интра или игра?


Тем временем mr287cc яростно добивал свободное пространство картриджа нашей следующей задумкой: мы решили обыграть актуальную в наши дни “коронавирусную” тему, и, глядя как дата организации вологодской демопати отодвигается всё дальше и дальше, в силу карантина, решили во что бы то ни стало “защитить” Мультиматограф от коронавируса. Было сделано несколько сплайновых траекторий по которым должны были летать вирусы, а в нижней части экрана расположили космолёт, который должен был стрелять по вирусам и превращать их в пиво. Но поскольку пиво из вирусов — так себе затея, идею немного перекроили, и у нас получился просто космолёт из игры Galaga, уничтожающий цепочки вирусов, летающих по разным траекториям.

зарождение игры

Дальше — больше, простенький ИИ пилота был доработан до менее простенького, затем были созданы звуковые эффекты для появления цепочек вирусов, их уничтожение и анимация взрыва, счетчик очков, а также реализованы приветы-приглашения в виде таблицы нigh score:

invites-greetings

А в какой то момент мы и вовсе решили что надо дать возможность зрителю самому управлять кораблем и уничтожать вирусы. Это было первым шагом на пути превращения интры в полноценную мини-игру.

Всем очень понравилась эта затея, а вдоволь наигравшись кто-то в шутку подметил что не хватает босса и тут нас было уже не остановить. Хотя на тот момент еще не полностью были готовы некоторые демосценические части работы, мы на волне охватившего нас восторга полностью бросили все силы на игровую часть. mr287cc переделал перемещение корабля с линейного на инерционное, а TmK запилил первую версию босса:

босс - первая версия

Вдохновившись боссом из Castlevania этот вариант был доработан, и в нашем геймвитро появился хороший, годный, полноценный босс.

босс финальный вариант

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

Лимит свободного ПЗУ тем временем закончился, несмотря на то, что я распихал некоторые данные из PRG в CHR-ROM, и мы смирились с тем, что полировать игру дальше уже не получится. Однако, практически перед дедлайном, когда были отрендерены видео для показа, был найден неоптимальный код с открытыми циклами, после правки которого освободилось ещё 3 килобайта, данные в CHR-ROM были упакованы и дополнены другими данными из PRG-ROM, благодаря чему удалось получить ещё примерно килобайт, но времени на доработку уже не оставалось.

К моменту дедлайна далеко не все из задуманного было реализовано, но сдаваться мы не собирались.

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

информация об игре

Краткое руководство:

  1. Дожидаемся завершения вступительного эффекта с вологодскими кружевами, после чего запустится интро и игровой демо-режим.
  2. Кнопка “Select” переключает отображение таблицы high score, информации об игре.
  3. Нажимаем кнопку “Start”.
  4. Расстреливаем заразу как можно скорее: времени на битву отведено немного — всего 6 минут.

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

Враги нападают волнами: 3 волны вирусов (в каждых из этих волн могут появляться “супервирусы”, упускать их не рекомендуется, поскольку за них начисляются дополнительные очки). По завершении 3 волн спаунится босс, затем снова по привычному шаблону: 3 волны, босс, пока не закончится отведённое время.

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

ввод никнейма при завершении игры

Итоги:

  1. Мы выставились на Revision;
  2. Мы пригласили вас на Мультиматограф;
  3. Мы сделали “геймвитро” для мало представленной на демосцене платформы;

Отдельное спасибо bfox и Quiet за советы и тестирование геймплея.

P.S. от TmK:

В целом было не просто, в основном мы писали на Си и только некоторые время-зависимые вещи на ассемблере (например вывод анализатора на лого). До этого у меня был опыт разработки под NES в предыдущей нашей работе (NESPECCY), но с того время много забылось (и код написанный тогда мной на ассемблере сейчас выглядел незнакомым и как правильно оптимизировать код на Cи — во многих моментах оптимизация была достигнута путем простого брутфорса различных вариантов написания кода).

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

Несколько слов о музыкальном оформлении от n1k-o/Stardust


Поскольку целью нашей работы являлось приглашение сочувствующих демосцене людей на Вологодскую пати, первая же мысль была взять за основу известный трек, в котором постоянно уточняют — «где-где — в Вологде, вот где». Идея с кавер-ремиксом на этот трек оказалась несостоятельной: он не очень вписывается в стиль инвитро, поэтому было решено оставить от оригинала вступление и далее в треке я выполнил краткий пассаж в мотив припева. На этом часть с кавер-ремиксом завершилась и далее пошел оригинальный трек. (“Оригинальный” с небольшими оговорками: был подобран референс, который хорошо сочетался с демо-ориентированным видеорядом: в нем была внятная линия баса, забористый фанковый бит, запоминающаяся мелодия, в общем, всё, чтобы слушать и не выключать хотя бы 2-3 цикла.)

В первые подходы был подобран основной мотив песни про Вологду, покрутив его туда-сюда, я набросал бит и линию баса, стараясь сделать так, как было предложено в “интрошном” треке.

vortex tracker

Набрасывать все эти подходы мне проще в Vortex Tracker`е, несмотря на то, что этот редактор предназначен прежде всего для написания музыки под ZX Spectrum: в нём мне намного быстрее и привычнее работается, а это скорость и качество — главное. Перенести же ноты из одного редактора в другой (FamiTracker) не самая большая проблема: благодаря усилиям Ивана Пирога и Ко Vortex tracker умеет копировать данные в буфер Famitracker’a.

Таким образом, я сделал в черновом виде почти всю основную зарисовку в NES’овский трек, при желании, этот трек можно дописать и для ZX Spectrum.

Второй этап работы — трансфер трека из Vortex tracker в FamiTracker, задача монотонная — копируй и вставляй. Благо, я уже почти год писал для некоторых ребят и интерфейс FamiTracker’a стал почти родным (но не для быстрых набросков, тут я все еще спотыкаюсь об интерфейс). Скопировав все партии, я принялся прописывать каждую из них, подбирать инструменты и всё то, что должно было насытить и придать треку завершенный вид.

FamiTracker

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

В общем, на этом как бы можно и завершать рассказ, т.к. ничего больше не было, трек я перенёс, насытил и ещё немного развил, а у ребят периодически появлялись идеи, как бы ещё меня нагрузить работой — я же сделал всё на зависть быстро, хоть и долго раскачивался. Также приходилось дополнять финальный трек отбивками в конце паттернов, чтобы порулить “прыжками” скроллинга, или разбавлять басовые и ударные партии, чтобы сделать разрядочку для слуха.

FamiTracker часть трека

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

sound fx

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

Скачать пак с пати и финальной версией можно по ссылке.

Репозиторий с процессом разработки

Credits:

  • Кодил, рисовал: TmK
  • Кодил, графоманил: mr287cc
  • Звукоизвлекал: n1k-o/Stardust
  • Рисовал, эффектил, держал свечку: Adam Bazaroff

P.S.: Приглашаем любителей демосцены принять участие в демопати Мультиматограф. Ориентировочная дата проведения — начало июля, а там всё зависит от того насколько успешно вы будете бороться с вирусом в нашей игре.
Tags:
Hubs:
+17
Comments 6
Comments Comments 6

Articles