История проведения фестивалей компьютерного искусства, также известных как демопати, насчитывает в нашей стране уже четверть века. Люди со всех концов страны собираются, чтобы показать свои упражнения в извлечении невозможного из старых или современных компьютеров и сверхмалых объёмов кода. В первую пятилетку одним из главных демопати страны стал CAFe (внезапно, Computer Art Festival), проводившийся в Казани с 1999 по 2003 годы. Позже он надолго исчез с радаров, отдав пальму первенства более известным ныне Chaos Constructions и DiHalt, и только в этом году произошло довольно триумфальное его возвращение — если не по масштабу мероприятия, то по количеству разнообразных работ, показ которых затянулся до шести утра. Среди них была и моя, о создании которой пойдёт речь в этой статье.


На демосцене я скорее сочувствующий, чем активный участник. Мои основные интересы относятся к разработке ретро-игр и звукового софта. Ранее я сделал только одно условно-полноценное демо для красного телефона, ставшее с тех пор сценовым мемом, и около десятка небольших интро для разных платформ. Организаторы CAFe, заблаговременно начавшие персональную агитацию авторов, сначала удалённо, через общих знакомых, а потом и в личных встречах, в итоге замотивировали меня на создание моей первой действительно полноценной работы в категории полноформатного демо. Как повелось, на очередной не самой популярной платформе — игровой приставке NES/Famicom, известной большинству под названием Денди.

Планирование


Изначально я собирался делать давно задуманное демо для другой, более популярной 8-битной платформы. Но для его реализации требовалось провести значительный объём работ исследовательского характера, то есть не было понимания ни о сроках, ни о принципиальной осуществимости идеи, ни о качестве возможного результата. Поэтому конкретные действия предпринимались очень неспешно. Сроки начали ощутимо поджимать, и стало ясно, что если я хочу сдержать обещание об участии, необходимо срочно выбрать другой, более прогнозируемый в реализации проект. После обдумывания второй идеи, для категории Wild, также требовавшей экспериментов, я решил вернуться к давним заготовкам демо для NES, делавшимся ещё три года назад, тогда для Мультиматографа. Сами они в итоге использованы толком не были, но направление работы было наконец определено.

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

Первоочередным вопросом, который требовалось решить — сколько нужно эффектов (сцен), чтобы сделать продолжительность демо достаточной для претензии на крупную форму, а не на простое интро. Также требовалось понять, какова эта достаточная длительность.
Для этого, помимо прочего, был просмотрен текущий топ работ для выбранной платформы на Pouet, и проанализированы две лучшие – High Hopes и NESPECCY. Последнее, представляющее собой приглашение на это же самое демопати, вышло в декабре 2018, и до этого момента как-то не попадалось мне на глаза. Поначалу это даже несколько ударило по мотивации, так как я изначально предполагал для своей будущей работы уровень попроще.

По результатам анализа оказалось, что в High Hopes примерно шесть эффектов и длительность 2:45, а в NESPECCY около 11 эффектов и длительность 3:45. То есть в среднем около 25 секунд на эффект. Я предположил, что в среднем будет достаточно 10-15 эффектов, из расчёта, что только 3-5 из них будут относительно сложными технически, а остальные могут быть простыми, но симпатичными филлерами. Чтобы не затягивать показ простых сцен, в среднем получится примерно 15 секунд на эффект, с итоговой продолжительностью демо в примерно те же две-три с небольшим минуты.

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

Таким образом была определена первоочередная задача — успеть сделать в срок хоть что-то, пригодное для участия в конкурсе, а также режим разработки в стиле «нет времени объяснять». Это также исключило ведение дневника разработки, и теперь приходится восстанавливать события по памяти, стараясь сильно не соврать.

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

Код


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

Было решено выбрать достаточно мощную и даже избыточную конфигурацию — маппер MMC3 с 256 килобайтами ПЗУ кода и 256 килобайтами ПЗУ графики, и постараться не переживать, что её полный потенциал останется нераскрытым. Такая конфигурация не самая продвинутая из существовавших и тем более возможных, она широко применялась в коммерческих играх, начиная с Super Mario Bros. 3 (1988), но ранее авторы демо для NES ограничивались более простыми мапперами и меньшими объёмами памяти. В итоге было использовано 80% объёма ПЗУ кода и чуть меньше двух третей объёма ПЗУ графики. Избыточный объём памяти позволил не тратить лишнее время на решение задачи сжатия данных, хотя совсем без этого не обошлось.

Также был очевиден метод разработки — в основном на языке C, с ассемблерными вставками по необходимости, в основном это копирование в видеопамять, обработчики прерываний и плеер музыки. Я давно и успешно применяю такой подход на ретро-платформах. Он сильно экономит время разработки, так как прототипирование кода можно делать по месту: сначала реализовать его на C и сразу получить работающий на NES результат, а потом, если он работает недостаточно быстро, переписывать его частями на ассемблер по уже готовому шаблону, пока не будет достигнута необходимая скорость.



Одной из главных ожидаемых проблем, которая, впрочем, на практике не доставила особых хлопот, был объём основного ОЗУ — у NES он составляет всего 2 килобайта, и в своей работе я решил обойтись без его расширения, хотя маппер в принципе позволял такой ход. Память в системах на основе процессора 6502 в виду его тотальной восьмибитности часто считают в 256-байтных страницах, и в них распределение ОЗУ получилось следующим:

  • Одна страница для «быстрых» переменных;
  • Одна страница под аппаратный стек и палитру (значительная часть стека не используется);
  • Одна страница под буфер списка спрайтов (его требуют особенности архитектуры видеоконтроллера);
  • Одна страница под список обновлений видеопамяти;
  • Одна страница под список параметров растровых эффектов.

Две последние объявлены в программе в виде обычных unsigned char массивов по 256 байт и местами также переиспользуются для других целей. Оставшиеся три страницы отданы под программный стек cc65, то есть под глобальные и локальные переменные программы на C.

Нужно отметить, что динамическое выделение памяти (malloc/free) на подобных платформах практически не используется, требовалось статическое решение. Сначала я придерживался метода использования как можно большего количества локальных переменных, что позволило автоматически переиспользовать для них память программного стека. Но локальные переменные работают существенно медленнее, а доступного под глобальные переменные объёма в итоге стало не хватать, да и просто было некомфортно писать код подобным образом, многократно используя одноимённые переменные. Тогда я применил ранее не использовавшееся решение: объявление в конфигурации линкера нескольких сегментов ОЗУ, физически расположенных в одних и тех же адресах. Группы переменных, используемых в пределах одного эффекта, размещались с помощью директив компилятора в этих сегментах, позволяя повторно использовать одну и ту же память.

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

При работе над последним эффектом, коридором с Марио, я столкнулся с занимательным сообщением об ошибке при компиляции: local label overflow. Как я понял, у компилятора есть внутренний лимит на количество сгенерированных локальных меток (видимо 65536) внутри одного объектного модуля, я же для упрощения управления разделяемой памятью использовал один общий модуль. Одним из возможных решений было разбиение кода на несколько модулей, и я уже планировал его применить, однако после перемещения одного массива констант из исходника на C в ассемблерную часть (incbin с внешней меткой) проблема решилась сама собой, лимит перестал превышаться, и даже после добавления ещё пары сотен строк кода сообщение об ошибке более не возникало.

Разработка


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

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

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

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

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

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

Сборка


Изначально было ясно, что наибольшее время в хронометраже займут части с вступлением, названием, приветами, и финальные титры. Работа над ними велась в первую очередь, поэтому в какой-то момент оказалось, что у демо уже есть полноценные начало и конец (такие же, как в итоговом релизе), но практически полностью отсутствует середина, для которой более-менее был готов только один эффект. Сроки же подходили к концу. Работа над проектом превратилась в многочасовой марафон, занимая всё возможное свободное, да и несвободное тоже, время. Остававшиеся в планах эффекты были отсортированы по сложности, все задумки были максимально упрощены. Были сделаны космические захватчики, побеждены проблемы с вращающимся картриджем, со скрипом завершена простая в реализации башня (экспериментировал с внешним видом). По мере готовности эффектов определялась последовательность их показа.

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

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

Отладка


За неимением возможности отладки на реальном железе, как и самого железа, демо отлаживалось исключительно в эмуляторах. Основная работа шла в эмуляторе FCEUX. Он не отличается высокой точностью, но имеет хороший отладчик и очень быстро запускается, что важно при методе разработки «дописал пару строк, запустил, проверил». Критичные ко времени эффекты также отлаживались в гораздо более точных, но менее удобных Mesen, punes и Nestopia.

Так как сам формат демо подразумевает стремление добиться от платформы того, чего ранее на ней никто не делал, это испытывает на прочность точность эмуляторов, в том числе проявляя их несогласие по поводу различных предельных случаев. Отладив эффект в одном эмуляторе, можно получить артефакты в нём в других эмуляторах. Из-за этого, а также из-за особенностей архитектуры NES, многие завязанные на точные тайминги эффекты приходилось отлаживать повторно по многу раз. В итоге я настроил демо на чистую работу в популярном эмуляторе FCEUX, в комплекте с которым оно отправлялось на конкурс, и на нормальную работу во всех остальных эмуляторах.

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

Демо непосредственно поддерживает только NTSC-версию приставки, так как поддержка PAL очень существенно увеличила бы объём работы и затраты времени на отладку — некоторые эффекты требуют сильно отличающихся настроек, а для сохранения продолжительности демо и скорости музыки потребовалось бы делать два набора данных музыки и дважды выполнять ручную подгонку эффектов. Поэтому при запуске производится определение системы, и если обнаружена PAL-версия, выдаётся предупреждение о возможной некорректной работе. Само демо при этом запускается, но работает с сильными визуальными артефактами в некоторых эффектах и на 17% медленнее.

После релиза некоторые зрители запускали демо на разных реальных приставках – американской NES, Famicom AV, и даже Pegasus (аналог нашей Dendy Classic 2 в Польше, Чехии и некоторых других странах). Оказалось, что на всех приставках демо работает в основном нормально, но с визуальными проблемами в некоторых сценах, и что самое интересное, эти проблемы у всех разные. Часть из них, вероятно, связана с недостаточно точной реализацией маппера MMC3 в популярных Flash-картриджах, так как наименьшее количество проблем возникло на обычном картридже с реальным MMC3. Следующей наименее проблемной конфигурацией оказался Pegasus, хотя демо там не должно было нормально работать в принципе — на нём проявляется только небольшой артефакт на сцене с Марио. К моему удивлению, наиболее сложный в отладке и критичный к таймингам эффект — вращающийся картридж — стабильно работает на любой конфигурации тестового железа.

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

Музыка


Работа над музыкой началась с подбора темы из передачи «Новая реальность» и попыток скрещивания её со «Славься». Этот процесс занял неприлично много времени. Потом АОН'ом же навеяло идею сделать ударные в начальной части демо в виде простых отрывистых сигналов двух разных высот, и был грубо набросан фрагмент музыки с такими ударными для эффекта с названием демо. Также возникла идея, что этот же фрагмент, но в другой аранжировке, будет можно использовать в части с приветами, как подобие общей музыкальной темы. На этом работа по музыке по большей части остановилась, и остальная её часть писалась за два дня до крайнего срока. Тогда же выполнялась совместная подгонка эффектов и музыкального материала.

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

Подбор и эксперименты велись в Reaper с помощью MIDI-клавиатуры, непосредственно же музыка писалась традиционным для современной разработки способом, в популярном редакторе FamiTracker. Во время сочинения мелодий поиск идей вёлся импровизацией на MIDI-клавиатуре, озвучиваемой в Reaper, пока FamiTracker воспроизводил в цикле ранее запрограммированный ритм.



Так как штатный проигрыватель FamiTracker довольно значительно нагружает процессор NES, занимая до одной пятой времени кадра, а альтернативные проигрыватели сильно ограничивают возможности, было решено использовать классический для демо на маломощных компьютерах метод проигрывания дампа регистров — максимально быстрый, но требующий значительных объёмов памяти. Места на картридже оказалось достаточно, чтобы обойтись простейшим форматом без сжатия. Для формирования дампа была сделана утилита, «проигрывающая» выгруженный из FamiTrackerNSF-файл и записывающая все изменения регистров в бинарный файл. Иначе говоря, вся логика проигрывателя музыки вынесена на этап компиляции, а на NES выполняется только отправка полностью готовых данных в звуковой генератор.

Для лучшей синхронизации с эффектами в поток данных музыки также были введены маркеры. В демо они применяются для вспышек в сцене с названием и приветствиями. Маркеры устанавливаются прямо в FamiTracker командой Zxx, которая штатно предназначена для непосредственной установки уровня ЦАП канала сэмплов.

Разбор эффектов


Надпись в начале


Код позаимствован из старого проекта. Появляющаяся часть строки выводится спрайтами с разными палитрами, дальше впечатывается в фон. Надпись «основано на реальных эффектах» намекает, что дальнейшие эффекты не таковые, это шутка на тему постоянных споров о допустимости и роли анимации в демо.

Вступление со слонёнком и бегущей строкой


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

Технически сцена очень проста, главная сложность в ней — конверсия нарисованной в Graphics Gale графики в многослойной спрайт, чтобы обеспечить необычно большое количество цветов в нём (10). Всего четыре слоя, у каждого своя палитра. Слои сконвертированы в NES Screen Tool по отдельности и вручную сложены в общий спрайт в редакторе метаспрайтов.


Помехи


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

Тайловый набор содержит 8 наборов шума разной плотности по 32 тайла, сгенерированных простой программкой на PC. Они выведены в первые восемь строк карты тайлов. С помощью прерывания MMC3, срабатывающего каждые семь строк растра (одна теряется на синхронизацию), для каждой тайловой строки устанавливается случайное смещение слоя фона, грубой установкой адреса отображения. Это позволяет получить более реалистичную анимацию помех, чем простой шум одной плотности.



Цветные полосы


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

Хотя таймер визуально выглядит очень просто, и беглым взглядом этого можно не заметить, он также делает нечто необычное в рамках возможностей платформы. Цифры имеют размер 12x16 пикселей, но слой фона состоит из тайлов 8x8, а спрайты не могут покрыть более чем четверть ширины экрана — тогда как таймер занимает две трети. Для получения возможности вывода не попадающего в сетку шрифта был применён трюк — подготовлена графика всех пар цифр от 00 до 99, после чего сконвертирована в набор тайлов с удалением дублей — таким образом она уложилась в полторы сотни тайлов. Эффект падения таймера выполнен простым вертикальным скроллом. Так как графика цветных полос представляет собой сплошную заливку цветом, их движение визуально не проявляется.



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

Анимация уменьшения и увеличения сделана для одной строки тайловой карты, то есть требуется обновлять всего 32 байта видеопамяти. Эффект содержит 238 кадров, таким образом данные анимации занимают 7 килобайт. Набор тайлов во время отображения эффекта не изменяется, он содержит все возможные варианты цветопереходов полос, не попадающих на границы тайлов, всего их 53. Сами цветопереходы нарисованы вручную, а анимация полос сгенерирована более сложной программой для PC, сначала рисующей кадр нужного масштаба полос, а потом подбирающей подходящие тайлы и атрибуты из уже готового набора.


Название


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

Шрифт сделан прямоугольным для простоты анимации. Каждая буква по сути имеет высоту в пять условных пикселей и ширину в четыре. Всего есть пять разных комбинаций пикселей в каждой строке буквы. В Blender подготовлена анимация плавного вращения с ребра на плоскость всех пяти комбинаций. Кадры объединены в один файл, цветность снижена до трёх цветов, после чего изображение импортировано в NES Screen Tool – это дало набор в 82 тайла и карту тайлов для собственно анимации. На самом деле таких наборов используется два, для вращения букв в разные стороны.



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



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

Космические захватчики


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



Тайлы выводятся записью в видеопамять в слой фона. Звёзды выводятся спрайтами.

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

Вращающийся логотип


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

Главная техническая сложность сцены — уменьшение объёма памяти, требуемой для такой плавной и крупной анимации. Если хранить её кадры без сжатия, один кадр занимал бы один 4-килобайтный набор (так как в 256 тайлов два кадра не поместятся), и потребовалось бы 256 килобайт ПЗУ графики, то есть весь используемый в демо объём. Для решения этой проблемы применено сразу три трюка: анимация вращения только на 90 градусов (64 кадра), после чего меняются местами два цвета граней; конверсия графики с потерями, чтобы разместить группы по четыре кадра в наборах по 256 тайлов; а также обновление видеопамяти в видимой части растра. Таким образом анимация заняла в четыре раза меньше места.

Модель буквы и анимация вращения сделаны в Blender. В процессе пришла идея её отзеркалить, что соответствует концепции демо, и это показалось забавным — сцена как бы говорит «и-и-и-и-и-и». Кадры анимации объединены в группы, глубина цвета уменьшена до 4, и эти изображения сконвертированы функцией импорта с потерями в NES Screen Tool. Эта функция, пожалуй, моё главное секретное оружие — при превышении лимита тайлов во время компрессии она ищет наиболее похожие визуально тайлы и объединяет их, пока не останется требуемое количество тайлов. Разумеется, это даёт визуальные артефакты конверсии и требует последующей ручной доработки, но позволяет втискивать в лимит более сложные изображения с меньшими трудозатратами.



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

Монетки выводятся спрайтами и летают по более-менее честно рассчитываемой (с помощью fixed point) траектории. Старый код доработан, чтобы вращение шло, по возможности не пересекаясь с более крупным логотипом, а также чтобы монетки появлялись и исчезали постепенно в нужные моменты. Заходят за графику логотипа они с помощью флага приоритета, который есть у каждого спрайта.

Вращающийся картридж


Реализация эффекта очень проста на словах, но потребовала длительной отладки. Обработчик прерывания запускает растровый эффект на нужной строке (чтобы оставить время основному потоку). Далее выполняется код с очень точно подобранными таймингами для установки попиксельного вертикального смещения отображаемой карты тайлов для каждой строки экрана. Это требует весьма хитрой схемы записей в регистры PPU, причём часть из них должна точно попадать в определённое место строки. В NTSC-версии приставки тактовая частота процессора и частота следования пикселей не кратны, а также время выполнения кода может немного изменяться в зависимости от нескольких факторов, поэтому тайминги каждой строки постепенно уплывают и требуют коррекции.

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



Анимация вращения полностью рассчитана заранее с помощью программы на PC и занимает значительный объём в ПЗУ кода — три банка по 16 килобайт, в которых для каждого из 256 кадров анимации хранится таблица в 176 номеров отображаемых строк растра. Можно было сделать эффект и в реальном времени, я уже делал подобный для задумки демо для другой платформы, но в данном случае было достаточно свободной памяти и не было лишнего времени.

Пакман


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

Главной проблемой было нахождение способа быстрого создания анимации. Сначала возникла идея снова применить Blender либо другие графические программы. Но после первых попыток и некоторых подсчётов стало ясно, что будет достаточно всего восьми кадров анимации открывания рта из полностью закрытого до полностью открытого положения, и их гораздо проще и быстрее нарисовать вручную в Gale.

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



Кирби


Этот эффект отлаживался на моей старой картинке Catrix. Был план сделать смену изображения кошачьей морды на альтернативную картинку с чеширским котом через какой-то визуальный эффект. Когда возникла идея добавить в демо игровых персонажей, после недолгих выборов – нужно было срочно нарисовать две новые полноэкранные картинки — было решено взять Кирби, и сделать его «чеширскую» версию. Сам эффект смены картинок в процессе упростился до короткого мерцания.

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

Башня


Мне всегда нравился эффект в финальном уровне игры Battletoads, и я давно хотел повторить его. В демо он пришёлся к месту в сцене с приветствиями.

Техническая часть очень проста, это обычный вертикальный скроллинг слоя фона. Эффект вращения достигается анимацией графики тайлов, карта же тайлов остаётся неизменной. Для анимации задействован режим MMC3 с четырьмя переключаемыми окнами CHR. В первом окне находятся тайлы статичной части, а три других переключаются с разной скоростью. Одно из окон также плавно меняет направление анимации. Так как в каждом окне помещается 64 тайла, а анимированный элемент требует только 28, графика тайлов дублируется в каждом окне для получения анимации в противоположном направлении.

    

Главной проблемой в реализации эффекта была подготовка анимации. Она выполнена программой на PC, берущей на входе текстуру 16x16 и таблицу скоростей для каждого пиксельного столбца, и генерирующей блок из 16 кадров анимации, в виде набора из 256 тайлов (часть из них пустые). Наборы конвертируются в нужный формат в NES Screen Tool и распределяются по банкам ПЗУ графики с нужными смещениями через опции директивы incbin ассемблера.

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

Коридор


Было желание сделать какую-то сцену с перспективой, типа классического тоннеля. Классические тоннели или настоящий рейкастер типа Wolfenstein 3D отпали из-за сложности. К тому же, на NES уже существует несколько настоящих рейкастеров, в том числе с текстурами, но все они смотрятся не очень эффектно из-за крайне низкой скорости.

Меня очень увлекает псевдотрёхмерная графика в ранних играх, и среди старых наработок уже был спрайтовый коридор с плавной анимацией поворотов, вдохновлённый игрой Zig Zag для ZX Spectrum. Было решено задействовать его. Так как сам по себе абстрактный коридор не очень интересен, был придуман внутренний сюжет сцены с Марио и туалетным юмором, логически завершающий её, а также и демо в целом. Сцена делалась самой последней, и несмотря на использование заготовок, работа над ней заняла более 11 часов подряд, не считая подготовительных работ во время планирования.

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

Графика кадров заняла два набора тайлов. В нужные моменты анимации обновляется слой фона, подключается нужный набор графики и устанавливаются два цвета в двух палитрах (для верхней и нижней половины экрана), так как при поворотах на 90 градусов цвета стен меняются местами.

Карты тайлов анимации сначала копируются в ОЗУ кодом в фиксированном банке из одного из двух подключаемых нижних банков ПЗУ. Во время отображения растра сразу же под отображаемым окном эффекта рендер выключается и происходит пересылка данных из ОЗУ в видеопамять. Выключение рендера также служит нижней границей визуального отсечения для спрайта Марио.

Титры


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

Сначала я хотел сделать просто плавное затемнение краёв, но в процессе предварительных экспериментов попробовал варианты с горизонтальным смещением строк, мне понравился результат, и я решил не тратить дополнительное время. Небольшое затенение добавлено с помощью спорной возможности видеоконтроллера NES, усиления-приглушения цветовых компонент. Спорность заключается в том, что в разных региональных версиях и клонах приставки эта функция работает по разному, но в таком применении особых проблем возникнуть не должно (не считая так называемого RGB PPU, где края будут залиты белым цветом).

Вид


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

Для имитации масштабирования применяется тот факт, что все крупные спрайты на NES составляются из маленьких, в данном случае размером 8x16. Если смещать их друг относительно друга, можно получить эффект масштабирования или искажения, вполне прилично смотрящийся при небольших значениях изменений. Для данного эффекта требуется умножать смещение каждого спрайта от центра на масштаб, но так как процессор NES не справляется с большим количеством даже целочисленных умножений, применяется трюк — в начале кадра считается координатная сетка по осям X и Y, и вывод спрайтов использует эти заранее посчитанные значения. Таким образом количество требуемых умножений значительно уменьшается — при составном спрайте 8x8 элементов (64 спрайта) требуется всего 16 (8+8) умножений вместо 128 (8*8*2).

NES может отображать только 4 градации серого, включая белый и чёрный. В эффекте имитируется 7 градаций: если отображать через кадр белый и белый пиксель, он будет белым, если серый и серый — будет серым, а если чередовать белый и серый, то визуально он будет выглядеть как промежуточная градация между белым и серым. Основной сложностью снова является подготовка графики, а именно двух полукадров изображения, что было выполнено рядом ручных манипуляций в Gale и GIMP. Для снижения мерцания строки полукадров чередуются.

  

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

Карта памяти


В разработке постоянно использовалась утилита NES Space Checker, визуализирующая заполнение банков памяти. Довольно интересно увидеть наглядный пример, что и где находится внутри программы.



PRG00...PRG02 – данные музыки
PRG03 – сэмпл для сцены с Видом
PRG04...PRG06 – данные анимации вращения картриджа
PRG07 – тайловая анимация движения вглубь коридора
PRG08 – тайловая анимация поворота в коридоре
PRG09 – код сцены с башней
PRG0A — код сцены с вращающимся логотипом
PRG0B — код сцен с названием и коридором
PRG0C — код сцен с вращающимся картриджем, Кирби, Видом, захватчиками, Пакманом
PRG0D — код сцен со вступлением, шумом, цветными полосами, титрами
PRG0E — основной банк кода, содержит только вызовы всех сцен
PRG0F — фиксированный банк кода, плеер музыки, вывод спрайтов, обработчики прерываний

CHR00 – слонёнок и тайлы для цветных полос
CHR01 — шрифт и шум
CHR02 — спрайт Вида и тайлы вращающихся букв в названии
CHR03 — тайлы нормального Кирби
CHR04 — тайлы чеширского Кирби
CHR05 — графика для сцены с вращающимся картриджем
CHR06...CHR0D – анимация вращающегося логотипа
CHR0E – шрифт для приветствий и анимация тайлов захватчиков
CHR0F — тайлы Пакмана
CHR10...CHR13 – анимация текстур башни
CHR14 — тайлы коридора
CHR15 — спрайт Марио
CHR16...CHR1F — не используются

Вместо заключения


Закончу комбо-мемом: в любой непонятной ситуации пишите демосцены, господа!

Комментарии (5)


  1. amphasis
    08.11.2019 12:47

    Очень нравится читать о платформах старых компьютеров и игровых консолей. Всегда мечтал написать своё демо/интро, но прокрастинатор во мне побеждал. Поражает, как люди умудряются выжать максимум из возможностей железа. Спасибо за детальное описание.
    PS Вначале подумал, что НЕОНdemo явялется отсылкой к фильму «Neon demon» 2016 года :)


  1. namikiri
    08.11.2019 14:37
    +5

    За ВИDа отдельный плюс.


  1. unbeliever
    08.11.2019 14:47
    +3

    Вы можете поддержать эту работу и другие релизы с демопати CAFe'2019 своим голосом на портале Pouet. Так победим!

    image


  1. VEG
    08.11.2019 17:09
    +1

    Если эта демка сейчас выглядит так круто, боюсь представить насколько лучше оно бы выглядело, если бы делалось не в такой спешке, как это описано в статье =)

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


  1. Refridgerator
    08.11.2019 17:48

    Посмотрев демку я окончательно убедился, что никакой я не программист, а лишь убедительно им притворяюсь)

    А самое крутое это
    спектрумовская пасхалка в конце