Хабрахабр

Разработка демо для NES — HEOHdemo

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

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

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

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

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

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

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

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

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

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

Код

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

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

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

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

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

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

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

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

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

Разработка

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

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

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

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

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

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

Сборка

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

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

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

Отладка

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

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

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

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

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

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

Музыка

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

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

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

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

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

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

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

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

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

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

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

Помехи

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

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

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

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

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

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

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

Название

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Реализация эффекта очень проста на словах, но потребовала длительной отладки. Обработчик прерывания запускает растровый эффект на нужной строке (чтобы оставить время основному потоку). Далее выполняется код с очень точно подобранными таймингами для установки попиксельного вертикального смещения отображаемой карты тайлов для каждой строки экрана. Это требует весьма хитрой схемы записей в регистры 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 часов подряд, не считая подготовительных работ во время планирования. Так как сам по себе абстрактный коридор не очень интересен, был придуман внутренний сюжет сцены с Марио и туалетным юмором, логически завершающий её, а также и демо в целом.

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

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

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

Титры

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

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

Вид

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

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

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

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

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

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть