![image](https://habrastorage.org/getpro/habr/post_images/2bb/2a9/0a4/2bb2a90a47aceecd01c315c29cb6f64f.png)
Оглавление
Оглавление
Часть I: подготовка
- Введение
- 1. Краткая история NES
- 2. Фундаментальные понятия
- 3. Приступаем к разработке
- 4. Оборудование NES
- 5. Знакомство с языком ассемблера 6502
- 6. Заголовки и векторы прерываний
- 7. Зачем вообще этим заниматься?
- 8. Рефакторинг
Часть II: графика
9. Picture Processing Unit (PPU)
Содержание:
- Палитры
- Таблицы паттернов
- Спрайты
- Фоны
«Игра» для NES состоит из трёх компонентов: отображаемой на экране графики, пользовательского ввода через какой-нибудь контроллер и звука для музыки и звуковых эффектов. Игра использует пользовательский ввод для изменения отображаемой графики и воспроизводимого звука, пока пользователь не отключит систему. В этой серии глав мы рассмотрим каждый из этих трёх компонентов, начав с того, как NES отображает графику.
Палитры
Как вы можете помнить из Главы 4, NES использует для всей своей графики фиксированный набор из 64 цветов.
![](https://habrastorage.org/webt/bw/z0/ue/bwz0ue06cxf9mhdccp-thpwtq5u.png)
Палитра цветов NES.
Эти цвета используются для заполнения ячеек в восьми четырёхцветных палитрах. Четыре палитры используются для отрисовки объектов фона (background), а оставшиеся четыре — для отрисовки объектов спрайтов переднего плана (foreground). Каждый отрисовываемый на экране элемент использует одну из этих палитр, то есть в одном графическом объекте может быть максимум 4 из 64 возможных цветов.
Таблицы паттернов
Что же такое «графические объекты»? NES не позволяет разработчикам выполнять попиксельную отрисовку. При разрешении 256x240 пикселей для каждого экрана графики потребовалось бы задание 61440 пикселей цветовой информации, а такой объём слишком велик для размера памяти консоли. Поэтому базовой единицей графики NES является «тайл» (tile) размером 8x8 пикселей. Один экран графики имеет ширину 32 тайлов и высоту 30 тайлов (960 тайлов).
CHR-ROM на картридже NES содержит две таблицы паттернов, каждая из которых состоит из 256 тайлов размером 8x8. Одна таблица паттернов используется для графики фона, вторая — для спрайтовой графики.
[Ещё один вариант — использовать тайлы 8x16 (шириной 8 пикселей и высотой 16 пикселей), как это сделано в играх наподобие The Legend of Zelda. Хотя это уменьшает количество тайлов, которые можно хранить в таблицах паттернов, зато позволяет применять тайлы из другой таблицы в качестве и фона, и спрайта. В некоторых играх преимущество возможности использования тайлов в обоих слоях может перевесить недостаток меньшего количества тайлов.
Эти проблемы в основном становятся несущественными благодаря использованию чипов мэпперов, позволяющих выполнять переключение банков с графикой таблиц паттернов. Подробнее о них мы поговорим позже в этой книге.]
Каждый тайл в таблице задаётся двумя «битовыми плоскостями» (bit plane), определяющими, какой цвет палитры (0-3) используется для каждого пикселя тайла. Одна битовая плоскость определяет «младший бит» (low bit) каждого пикселя в тайле, а вторая — «старший бит» (high bit). (Как вы можете помнить, два бита позволяют задать четыре значения, соответствующих четырём цветам палитры.) Каждый тайл занимает 16 байт памяти, то есть 8 КБ памяти чипа CHR-ROM хватает ровно на 512 тайлов двух таблиц паттернов. Благодаря тому, что указывается индекс палитры, а не сам цвет, тайлы занимают меньше памяти и могут при необходимости использоваться с разными палитрами.
![](https://habrastorage.org/webt/qm/ud/i0/qmudi0n49ij-zx_bjf_y2xamyqo.png)
Пример тайла таблицы паттернов. Байты
$xxx0
-$xxx7
задают «младший бит» каждого пикселя, а байты $xxx8
-$xxxf
задают «старший бит» каждого пикселя.Всё, что игра для NES отрисовывает на экране, содержится в её таблицах паттернов.
[Ситуация сложнее, если используются CHR-RAM или переключение банков, однако всё отрисовываемое, строго говоря, в тот или иной момент присутствует в таблицах паттернов.]
У консоли нет «системного шрифта»: если вам нужно отрисовать текст в игре для NES, то придётся создавать тайлы шрифта в таблице паттернов. (Именно поэтому в большинстве игр для NES, особенно в первых, шрифты «кричали» в верхнем регистре.) Из-за ограниченности места под тайлы также было важно их эффективное многократное использование. Возможность использования тайлов различными способами или хитрое применение смены палитр может обеспечить игре большее визуальное разнообразие даже в условиях ограничений памяти CHR-ROM.
![](https://habrastorage.org/webt/tj/7n/wr/tj7nwrsct-ivlhgoxtoijyhgdow.png)
![](https://habrastorage.org/webt/fp/_7/ih/fp_7ihehb-nd5xdl-hbvoar2yxk.png)
Таблицы спрайтов и фонов из Super Mario Bros.. Всё, что отрисовывается в этой игре, присутствует в двух таблицах, в том числе сам Марио (в первых пяти строках таблицы спрайтов), все враги в игре, графика текста и очков (первые три столбца таблицы фонов), а также множество разных труб, растений и замков.
Спрайты
Спрайты представляют собой слой «переднего плана» графики. Каждый спрайт — это отдельный тайл, который можно разместить в любом месте экрана с точностью до пикселя. Также спрайты можно зеркально отражать по вертикали или горизонтали (но не вращать), а каждый спрайт может указывать, какую из четырёх палитр спрайтов он будет использовать. Однако эта гибкость имеет свою цену: из-за ограничений памяти и времени обработки NES одновременно может отображать только 64 спрайта, а в каждой растровой строке (горизонтальной строке пикселей) может отрисовываться только восемь спрайтов.
Если вы играли в разные игры на NES, то, вероятно, замечали мерцание, при котором некоторые спрайты быстро появляются и исчезают, как будто это сделано ненамеренно.
Пример мерцания спрайтов из Mega Man II
Это мерцание возникает из-за того, что в одной растровой строке отрисовывается слишком много спрайтов. Так как PPU может отрисовывать в каждой растровой строке не больше восьми спрайтов, все спрайты после первых восьми не отрисовываются. Однако эффект мерцания — это осознанный выбор разработчика игры, помогающий игроку. Мерцание возникает, когда разработчик выбирает, какие именно восемь спрайтов идут первыми в каждой растровой строке. Благодаря этому игрок может видеть все спрайты в растровой строке, только не все одновременно. В противном случае спрайты врагов могли оказываться совершенно невидимыми, что было бы нечестно.
При отрисовке спрайтов на экран любой пиксель тайла, который использует первый цвет палитры (с индексом 0) отрисовывается прозрачным, что позволяет отображаться фоновому слою. Это означает, что в каждом спрайте можно использовать только три цвета. Благодаря использованию прозрачности можно накладывать друг на друга спрайты с разными цветовыми палитрами, создавая графику из большего количества цветов.
![](https://habrastorage.org/webt/gz/ea/qk/gzeaqkl15bokhf0qd_q7qlrratu.png)
Наложенные друг на друга спрайты, используемые для создания многоцветной графики. Лица Мегамена не видно, потому что спрайт не отрисовывается из-за ограничения, связанного с растровыми строками. Обратите внимание, что отсутствующая область не квадратная, потому что применена прозрачность, благодаря которой смешиваются спрайты лица и шлема.
Фоны
Спрайты обладают большой гибкостью ценой того, что они могут покрывать только небольшую площадь экрана. В фонах реализован противоположный компромисс. Фон может заполнить весь экран — 960 тайлов размером 8x8, но тайлы фона должны быть привязаны к сетке, и тому же страдают от дополнительных ограничений применения палитр. Слой фона можно прокручивать (скроллить) с инкрементом в 1, но все тайлы движутся вместе. Отдельные части экрана нельзя прокручивать по-разному, если только не использовать хитрые обновления посередине кадра (например, IRQ растровых строк «Sprite Zero Hit»).
Таблицы имён
Фоны задаются при помощи таблиц имён (nametables), находящихся в памяти PPU. Каждая таблица имён имеет размер 960 байт, и каждый из этих байт хранит номер тайла одного из 256 тайлов из таблицы паттернов фона. В таблице распределения памяти PPU есть место под четыре таблицы имён, выстроенные в виде квадрата, что теоретически означает, что одновременно можно задать фон размером в четыре экрана телевизора.
![](https://habrastorage.org/webt/zp/s6/5j/zps65jlefqt6pyqkmtaqt4mhs0s.png)
Четыре таблицы имён с указанным для каждой первым адресом памяти PPU.
Я написал «теоретически», потому что Famicom проектировалась дешёвой (как говорилось в Главе 1), а в то время память была очень дорогой. В качестве компромисса у Famicom/NES есть достаточно физической памяти для размещения двух таблиц имён. Это «реальные» таблицы имён, работающие ожидаемым образом. Диапазоны памяти для двух остальных таблиц имён используются в качестве «зеркал» реальных таблиц имён, поэтому запрос байта памяти из зеркала возвращает байт из соответствующей реальной таблицы имён. Разработчик может задавать, какие две таблицы имён являются «реальными», а какие «зеркальными». На аппаратном картридже NES это реализовано площадкой припоя на одном или двух контактах платы картриджа. Для эмуляторов параметр отзеркаливания игры является частью её заголовка iNES.
![](https://habrastorage.org/getpro/habr/post_images/f3a/d1d/623/f3ad1d6233a784edda7945263aefa8fd.jpg)
Внутренности картриджа Balloon Fight. Красным прямоугольником показаны контакты «V»/«H»; в зависимости от того, какая из пар контактов соединена припоем, игра использует вертикальное или горизонтальное зеркалирование. Изображение взято из NES Cart Database.
Зеркалирование может быть вертикальным или горизонтальным. При вертикальном зеркалировании таблицы имён 1 и 2 являются «реальными», а 3 и 4 — зеркалами. Это обеспечивает разработчику два экрана, левый и правый, что идеально подходит для игр с горизонтальным скроллингом. Горизонтальное зеркалирование делает таблицы имён 1 и 3 «реальными», а таблицы имён 2 и 4 зеркалами. При горизонтальном зеркалировании получается два экрана, верхний и нижний, что подходит для игр с вертикальным скроллингом.
[В старых играх для NES зеркалирование пропаивалось аппаратно, а в более новых картриджах с добавленными чипами мэпперов разработчик имеет возможность менять зеркалирование в любой момент. Например, чип MMC1 позволяет игре Metroid переключаться между вертикальным и горизонтальным зеркалированием, когда игрок проходит через дверь, благодаря чему есть части уровня с горизонтальным и вертикальным скроллингом.]
Таблицы атрибутов
Таблица имён — это всего лишь список номеров тайлов. Чтобы раскрасить каждый тайл палитрой, нужен второй тип таблицы. В конце каждой таблицы имён есть 64-байтная область, называемая таблицей атрибутов (attribute table), которая задаёт палитру, используемую для каждого тайла фона. 960 + 64 = 1024 байт, то есть каждая пара таблицы имён/таблицы атрибутов занимает один килобайт памяти.
Так как таблица атрибутов занимает всего 64 байта, в ней недостаточно места для задания палитры для каждого тайла фона. Каждый байт таблицы атрибутов задаёт цвета палитры для шестнадцати тайлов фона квадратом четыре на четыре. Биты 0-1 каждого байта задают палитру, используемую для верхних левых тайлов размером два на два, биты 2-3 — верхние правые, биты 4-5 — нижние левые, а биты 6-7 задают палитру для нижних правых. Это означает, что кроме привязки тайлов фона к сетке есть и привязка цветовой информации к собственной сетке.
![](https://habrastorage.org/getpro/habr/post_images/2bb/2a9/0a4/2bb2a90a47aceecd01c315c29cb6f64f.png)
Иллюстрация того, как каждый байт таблицы атрибутов определяет палитры для шестнадцати тайлов фона.
Из-за ограничений таблиц атрибутов объекты фона обычно отрисовываются частями по 2x2 тайла. Такие крупные объекты называются метатайлами.
![](https://habrastorage.org/getpro/habr/post_images/d39/0df/fc7/d390dffc77c74c88e4e8f56932b6f853.png)
Блок с вопросительным знаком (?) из Super Mario Bros. — пример метатайла.
Также из-за сетки таблицы атрибутов в очень немногих играх для NES изометрическая графика. Отрисовка фонов под углом может вызывать ошибки цветов, когда большая часть фона пересекает границу таблицы атрибутов.
![](https://habrastorage.org/getpro/habr/post_images/334/66b/aa3/33466baa3adb13df7e89cdc293e2b83a.jpg)
Фоны из Snake, Rattle 'n Roll (1990 год). Из-за границ таблицы атрибутов один тайл голубой «стены» отображается как тёмно-синий, потому что является частью той же палитры 2x2, что и тёмно-синий треугольник.
Создание игр для NES на ассемблере 6502: спрайтовая графика
staticmain
Возможно стоит упомянуть, что спрайты необязательно могут храниться внутри CHR. Вполне рядовая ситуация, когда и спрайты и данные находятся внутри PRG