Устройство спецэффектов для игр под NES. Часть 1

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

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

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

Дисклеймер


В каких-либо описаниях железа приставки NES или употреблении терминов я могу ошибаться и, скорее всего, ошибаюсь. В описании программной части информация, предположительно, более точная, проверенная дизассемблированием и отладочными скриптами. Ссылки на все инструменты, использованные в статье, приведены в конце. Если в тексте упоминается «эмулятор» без уточнения названия, подразумевается Fceux. Особо важные или интересные, на мой взгляд, моменты выделены восклицательными знаками.

Краткие сведения о возможностях вывода графики NES


Предположим, что вы прочитали статьи nesdoug«а в переводе от BubaVV, и уже знаете базовые вещи о программировании графики для NES. Я не буду писать об этом детально, а лишь опишу кратко и упрощённо (можно пропустить эту часть и возвращаться к ней, когда будут возникать вопросы, почему какой-либо эффект сделан так, а не иначе):

  1. Видеопроцессор NES имеет четыре экранные страницы (и несколько режимов работы с ними, чаще всего две страницы просто копируют содержимое двух других), которые содержат данные об выводимых тайлах и атрибуты этих тайлов. Блок размером 2×2 тайла может быть окрашен всего 3 разными цветами + использовать 1 фоновый цвет.
  2. Также видеопроцессор умеет делать аппаратный скроллинг картинки между несколькими экранными страницами. Это значит, что на экране может рисоваться часть из первой экранной страницы и часть из второй. Экранные страницы при рендеринге «зациклены» в порядке 1–2–1–2–1–2…, т.е. если видеопроцессор дошёл до конца одной из страниц, он берёт данные из начала следующей. Это проще, чем звучит, можно открыть в эмуляторе какую-нибудь игру, и включить отладочное окно отображения экранных страниц, чтобы наглядно посмотреть, как это работает.
    (разбор устройства скроллинга с картинками здесь)
  3. Картинка в экранной странице рисуется тайлами из CHR-банка памяти, в котором в один момент находятся 256 тайлов памяти. Видеопроцессор «видит» два таких банка, один для отрисовка спрайтов, другой для отрисовки фона.
    При этом есть режим отрисовки спрайтов размером 8×16, при котором можно рисовать спрайты данными сразу из обоих банков.
    Эта память находится на картридже, поэтому в зависимости от «начинки» картриджа банки могут быть устроены по разному — переключаемые программно банки с ROM-памятью (чаще всего частями по 1, 2 или 4 килобайта), либо RAM-память, в которую можно записывать данные (чаще всего происходит копирование данных из банков с кодом).
  4. Тайлы из банков — это картинки размером 8×8 пикселей 2-битной цветности. К этим двум битам прибавляется два бита цвета атрибута тайла (общие для всех пикселей тайла). В итоге получается 4х битный индекс цвета в палитре из 16 цветов. В один момент времени в видеопроцессоре активны две палитры — для тайлов фона и спрайтов.
  5. Кроме фона, видеопроцессор отрисовывает до 64 спрайтов. Существует также ограничение на количество спрайтов, выводимое на одной строке, при превышении которого видеопроцессор пропускает спрайты, которые не успеет отрисовать.

Я не буду разбирать детально эти особенности NES, они «разжёваны» в обзорных статьях, например, тут. Я привёл их только для того, чтобы по ним перечислить 5 основных способов создания анимации и эффектов на NES — анимацию записью в экранную страницу, анимацию сменой позиции скроллинга, анимацию сменой содержимого CHR-банка, анимацию сменой палитры и анимацию отрисовкой спрайтов.

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

А теперь, внимание, все графические эффекты на NES делаются одним из этих способов или их сочетанием!

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

Синхронизация центрального процессора консоли и видеопроцессора


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

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

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

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

При этом используется несколько возможных вариантов синхронизации центрального и видеопроцессора консоли.

  • Использование маппера, который генерирует прерывания в конце отрисовки каждой строки, по таймеру либо по каким-либо другим условиям. Прерывание говорит центральному процессору «брось все дела, только сейчас можно быстро записать несколько байт в видеопамять, это важнее, к остальному вернёшься потом». Программа может установить обработчик этого прерывания — и выполнить какой-то код за то время, пока луч на экране гасится для перехода из одной строки на другую. Это самый простой для разработчика подход. Самый распространённый из таких мапперов — MMC3. «Железная» часть организации этого очень интересна, вдумайтесь — с помощью микросхемы на КАРТРИДЖЕ добавлялась новая возможность использования видеопроцессора на КОНСОЛИ.
  • В случае если маппер не позволяет генерировать прерывания, начинаются извращения. Программа должна сделать цикл простоя, ожидая появления какого-либо специально подстроенного условия, сигнализирующего о том, что видеопроцессор дошёл до отрисовки определённого места на экране (для этого могут применяться спрайты или вообще предназначенные для вывода звука функции консоли).

    Другой путь — отмерять количество тактов на инструкции, которые были выполнены (в этом случае программа должна обеспечивать равенство по времени выполнения двух веток условного оператора, и следить, чтобы инструкции, работающие с динамически составленными адресами, не выполняли переход за границы 256-байтной страницы памяти). Но даже при этом случае такты CPU и PPU не совпадают в точности, поэтому разработчики стараются делать такие трюки в тех местах экрана, где пользователь не заметит мерцания пикселей. Посмотрите на скриншот:
    5kcoyhy0j_ynaqhfcu0wvxnrdic.png

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

Про оба способа синхронизации между CPU и PPU посреди кадра (прерыванием, и по условию проверки на CPU), если будет интерес, я расскажу в другой статье с примерами кода, сейчас достаточно понимать, что изменение памяти может быть сделано как между кадрами (и даже это разработчики должны сделать достаточно быстро!), так и даже в ходе отрисовки кадра.
Теперь можно переходить к разбору того, чего можно достичь тем или иным способом.

Анимация сменой палитры


Межкадровая


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

Самый простой из них — обычное мерцание, когда один цвет сменяются другим. Другой эффект — «бегущие волны цвета», например, волны водопада, зыбучих песков или огня. Наконец, третий тип эффектов — высвечивание одним из цветов в палитре одного кадра изображения с одновременным гашением других цветов, таким образом можно показать движущуюся частицу дождя, снега или другого эффекта с частицами. Если вы внимательно читали теорию, то можете подсчитать, что у такой анимации будет максимально три кадра, если не совмещать её с анимацией записью в экранную страницу (тайлы ограничены двумя битами цвета, из которых один занят под фон). Все три вида такой анимации можно увидеть в разных уровнях игры Duck Tales 2:

На видео показано проигрывание анимации в обе стороны.
К сожалению, эффект дождя плохо видно в видео, поэтому вот он на гифке:
hpvfw-tyqgvvpedkx7wlnjvo9dm.gif

Опознать такой тип анимации очень легко, и для этого не нужны специальные инструменты. Открываем окно PPU Viewer в эмуляторе и следим за тем, меняется ли палитра или нет. Для простоты изучения «высвечивания» кадров с каплями дождя замедляем скорость.

Посреди кадра (мидфреймовая)


Изменить палитру полностью посреди кадра сложно, но можно.

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

Менее очевидный — менять цвет с одного на другой постоянно для создания эффекта градиента.

Как распознать
Эмулятор позволяет посмотреть, какая палитра была загружена в момент отрисовки какой-либо строки кадра. Смотрим заставку Indiana Jones and the Last Crusade:
69k1ormkaoh-8ttmq2j7u_x7nom.png
Индиана Джонс и градиентная заливка

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

Анимация записью в экранную страницу


Межфреймовая


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

__gdlfsk8fdihw1jpnznypfnxxk.png
В этой игре при взрыве пушки, дверей или ворот, обновляется логический «блок», размером 4×4 тайла.

Создание иллюзии тайлов размером меньше, чем 8×8
Хорошо описано в статье с разбором Battle City. Вкратце, для каждого тайла размером 8×8 создаётся несколько вариантов такого же типа с отсутствующими частями 4×4, в итоге программной заменой тайлов можно создавать иллюзию того, что игра использует тайлы размером 4×4 пиксела:
image

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

Мидфреймовая


Нет смысла отображать изменение в экранной странице до завершения отрисовки кадра, нормальные люди могут подождать до следующего кадра 1/24 секунды.

Анимация спрайтами


Межфреймовая


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

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

Существует и нецелевое применение спрайтов — для того, чтобы с помощью них синхронизировать процессор и видеопроцессор (например, Sprite Zero Hit и Sprite Overflow).

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

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

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

Рендеринг частиц

Рендеринг дождя в Mitsume ga tooru.
wvymr1avkxh9tsbtvkxbydir8_o.png

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

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

Похожий эффект — рендеринг звёзд в Galaxian или в Addams Family.
er0ak71gguuusuiyzvi0fxbupzk.png

Отображение теней
В 2,5-D играх, таких как Jurassic Park, спрайты используются, чтобы отобразить тени под объектами — так игрок может определить высоту, на которой находится объект.
o0pgzuj6sf4gnmx7hom1p7mxkme.png

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

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

Маскировка спрайтов под фон

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

Примеры:

cgdf0wxsvav9pwmh9bpuuyor3p8.gif
В Mitsume ga Tooru, помимо раздельной скорости движения грузовика и фона, для усиления эффекта спрайтами дорисовывается столб, как будто находящийся на отдельном слое, движущемся с медленной скоростью.

emr2_feorzrrtokkvclm3z1sflw.png
В известной заставке Megaman 2 спрайты создают эффект, что весь дом находится на отдельном слое, хотя это не так.

1d5gfbaqxpdhveg-x0uluejyymm.png
В Bucky O«Hare присутствует как эффект параллакса (движущийся отдельно слой выделен зелёными линиями), так и дополнительно для иллюзии движения воды ниже добавлены спрайты на неподвижный слой.

bmrykiktjvcqae3bxs9elfexkxg.gif
Большое колесо вначале башни после первого уровня в Castlevania 3. Кажется, будто колесо вращается, но движущиеся шестерёнки — это спрайты, ездящие так, как будто составляют единое целое с основой колеса. В дополнение мелкие шестерёнки также анимированы переключением банков памяти, про этот эффект будет рассказано далее, в примере с Power Blade 2.

Существуют и обратные эффекты, когда фон маскируется под спрайт (технически, это НЕ анимация спрайтами, хотя кажется ей). Например, целый босс может рисоваться в фоне. Так делают потому, что не хватило бы 64 спрайтов для отрисовки огромного босса. Этот эффект будет рассмотрен в группе эффектов изменением позиции скролла.

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

Например, Galaxian. Причина такой хитрости инопланетян — отрендерить их всех спрайтами не получилось бы, так как их слишком много.
cxs5o1-ipypqbtlxk7j0vqcsnzy.png

Эта техника часто используется в играх от Capcom, чтобы показать взаимодействие с интерактивными блоками — разбиваемые камни в Duck Tales 1–2, ящики в Chip and Dale Rescue Rangers 1–2, ракушки в Little Mermaid.

rjdlmqdqm0_qhpmkhs8xsppqiga.png
На скрине два ящика из Chip & Dale, один из которых нарисован в фоне, а второй брошен игроком и является спрайтом. Также можно заметить, что цвета в палитре объектов немного отличаются.

В Prince of Persia техника используется более продвинуто. В те моменты (и только в них), когда игрок пробегает в «ворота», нарисованные на фоне, и должен пройти между двумя столбами, поверх персонажа спрайтами дорисовывается такой же самый столб, как и в фоне, который закрывает игрока. Так что оба столба в фоне остаются за персонажем, но перед персонажем временно появляется третий столб, чтобы сохранить иллюзию правильного порядка объектов.

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

Эффект полупрозрачного/непрозрачного фона
Зато показать полупрозрачность фона возможно. Для каждого спрайта на экране в памяти видеопроцессора отведён флаг »рисовать ли спрайт за фоном или перед ним». Если все пиксели тайла фона непрозрачные — такой спрайт будет полностью невидим за фоном. Если же часть пикселей фона будет прозрачной, а другая — нет, то персонаж за фоном будет «просвечивать».

cgyxwmrdztnlups0hbzlx-ejehm.png
Мой любимый пример полупрозрачного фона — секретный проходы в Duck Tales 2.

Если выходить из прохода в замедлении покадрово, можно заметить проблему организации полупрозрачности таким образом — бит «за фоном/перед фоном» устанавливается для целого спрайта 8×8 — поэтому Скрудж ненадолго проваливается за факелы в том месте, где он частично стоит в проходе, а частично уже вышел из него.

Решение этой проблемы — закрыть спрайт частично другим спрайтом — так появится возможность увеличить точность отображения вплоть до 1 пиксела. Этот эффект можно наблюдать детально в Mystery World Dizzy или Nightshade.

qp5n1ss0ueyjo5oxvz8g1qo7stg.png
Персонаж отображается частично за колонной, а частично перед стеной, как и должен. Детально этот эффект разобран здесь

Эффект сумки Кота Феликса
Напоследок, приём из разряда «грязных хаков» из игры Felix the Cat. Посмотрите, как Феликс прячется в сумку — нижняя часть спрайта плавно пропадает в сумке, а верхняя остаётся видимой перед фоном.
wmqg_4lif-q329_gwy3alyby1oq.gif

Если включить скрипт отображения спрайтов, получится такая картинка:
k8ghehad97mfq3ceflubub7zbt4.png

В левой части есть 3 странных спрайта (стоит отметить, что скрипт отображает спрайты немного неверно — игра использует спрайты 8×16, а скрипт рисует только 8×8 пикселей, так что на самом деле они идут друг за другом по вертикали непрерывно).

Если разобраться немного детальнее, а именно включить логгирование координат спрайтов в скрипте, то станет заметно, что это не 3 спрайта, а 24, по 8 в каждой строке. Это максимум, который может успеть отрисовать видеопроцессор в одной строке. Он делает это слева направо, поэтому после того, как 8 спрайтов отрисованы в невидимой зоне экрана, спрайты Феликса, залезающего в сумку, в этих строках отрисовываться уже не успевают. Таким хитрым образом сделана маскировка той части спрайтов, которые находятся ниже границы погружения.

Игр с таким эффектом маскировки не так уж и мало (см. здесь раздел Use of excess sprites for masking effects)

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

Анимации изменением содержимого CHR-банка


Для начала стоит чуть более детально рассмотреть отличия между картриджами с CHR-ROM и CHR-RAM (бывают и такие, на которых присутствуют оба типа памяти). Для программиста разница в том, что игра с CHR-ROM позволяет быстро переключать целые банки памяти. Разновидности мапперов имеют разные размеры «банков» — по 1, 2, 4 килобайта. Это даёт возможность иметь общую часть банка и переключаемую. Переключение осуществляется несколькими командами мапперу, и может быть сделано со скоростью несколько раз за кадр.

Картриджи с CHR-RAM требуют для «переключения» непосредственной записи нужного числа байт в память адресного пространства PPU (для программы — через запись по определённому адресу CPU). То есть для изменения одного целого тайла видеопамяти потребуется 16 команд записи.

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

Межфреймовая анимация


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

Пример такой анимации — механические объекты на заводах в уровнях Power Blade 2

wdmjtdtu8hcoiggjncj64ozzzwa.gif
Анимированные механизмы

lzcuxpdn2rnju6mtd-vppun5yiy.png
Отдельные кадры анимации банками (переключается нижняя половина банка).

Изучить содержимое CHR-ROM банков можно с помощью любого тайлового редактора, например, TLP. Это позволяет также определить и размер переключаемого игрой банка.

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

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

Одна из самых «экстремальных» игр, использующих анимации CHR-RAM — Battletoads.
Во-первых — она использует изменение содержимого CHR-RAM для анимации персонажей! Т.е. в памяти хранится только один кадр для каждой жабы и он постоянно обновляется. Такой эффект позволяет хранить в банке памяти больше данных.
4jotrem_ckkzq9o4lj7jnn_cjdi.png

Теперь запустим скрипт подсчёта кол-ва переданных за кадр байтов. Скрипт написан для эмулятора Mesen, потому что он единственный позволяет отслеживать нужные события видеопроцессора из lua.

r2-whotojh_k1zghnj49jbgv1w0.png
Как видно, на 2-м уровне, игра передаёт до 256 байт за кадр. Причём на чётных кадрах анимируется жаба, а на нечётных — фон (Игре ещё нужно выполнять обновления экранных страниц при скроллинге, они тоже делаются записью в PPU, а также анимацию второго игрока).

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

Пример процедурного изменения памяти CHR-RAM — зеркальное отражение блоков размером 2×2 тайла в Gun Smoke. Посмотреть на варианты содержимого памяти «до» и «после» можно в редакторе уровней CadEditor:
a4hkil8hsi_wo7up0xk3yd26qyc.png

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

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

Более детально стоит разобрать самый красивый эффект анимации переключением банков (возможный как с CHR-ROM, так и CHR-RAM), достигаемый в сочетании с эффектом скроллинга — симуляцию параллакса (отдельный слой, движущийся с другой скоростью, чем основной).

За наличие этого эффекта игра автоматически попадает в рейтинги «лучшие графические игры на nes».

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

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

Примеры игр:


Bucky O’Hare. Параллакс в окошках — иллюзия отдельного слоя.

0zmh2mz45hwstjjn82cgmfteyxg.gif
Battletoads. Вертикальный параллакс — стенки колодца движется быстрее, чем задняя стена, так как находятся ближе.

-zxg9q3x_jgupdpjbsihpuiqp1k.gif
Mitsume ga Tooru. Стена отдельно от платформ.

p-3f7ttzehkfrhbtrgyymqz2jlq.gif
Micro Mashines. Пол «шашечками» — с одновременным горизонтальным и вертикальным параллаксом.

Разберём эти красивые эффекты более детально.

Вот скрипт для редактора CadEditor, который берёт все сдампленные кадры видеопамяти и отрисует их в виде png-картинок. Результат работы:
qidlfa69i9rwxrhwnwwyypq3sci.png

Затем используем команду для ImageMagic, чтобы склеить кадры вместе:

convert -delay 1x24 -loop 0 *.png animation.gif

Получается такая гифка (для Bucky O«Hare):
ia0r84tuknjftltvwjjovdmj8ne.gif

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

Аналогично, можно посчитать, что для боевых жаб в колодце используется для эффекта вертикального параллакса анимация блока стены в 32 кадра, это 6 килобайт данных на 1 только блок (4×4 тайла) анимации!
o3cj5hacq2hacqbjnbjjpr7gwlm.gif

Тут видно, что при использовании CHR-RAM (в Боевых Жабах) в отличие от CHR-ROM (в Баки) не нужно выделять целый банк или тщательно планировать место в других банках заранее. Однако плата за это — более сложный код и отсутствие возможности анимировать весь банк.

Ещё один способ изучения эффектов параллакса — разрушить иллюзию, поставив анимированный блок в такое место, где он не будет гладко сшиваться с другими блоками. Например, откроем уровень с колодцем в редакторе CadEditor и добавим «блок-индикатор»:
jhwg2s7gornyv2n65y4chsgozc4.png

Загрузим изменённый уровень в эмулятор и посмотрим результат:
gdtdaz9cloangsffsyy4njowzwy.gif

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

Ещё один «эффект», демонстрирует Jurassic Park сразу после запуска игры. Это просто запись красивой анимации в нескольких банках подряд покадрово и прокрутка её без всякой интерактивности. Не буду его демонстрировать из обиды на разработчиков, из-за того, что они так увлеклись демо-эффектами на старте игры, что у них не осталось места под красивую концовку, думаю, многие игроки были разочарованы этим.

Мидфреймовая анимация


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

Очевидное применение этому – отрисовать интерфейс одним банком, а игровой экран — другим. Можно также выделить какую-то часть экрана (лучше отделённую горизонтально от основной части). В Teenage Mutant Ninja Turtles 3 есть оба этих эффекта — фон пляжа нарисован тайлами из одного банка, пляж — другими, а красивый интерфейс с рожицами черепах — третьими.
ezfa-wbl-9g1mfa1oqgdtrzk-rm.png

Более технологичный эффект — переключить банк так, чтобы нарисовать одну

© Habrahabr.ru