Ранее я уже писал статью о выводе спрайтовой графики на экрана с использованием регистра OAMDATA ($2004), но это подходит лишь для экспериментов и вывода некой статической картинки. Но движение либо анимация будет приводить к появлению различных артефактов наслоению спрайтов и так далее. Что бы этого избежать следует сформировать для каждого кадра буфер спрайтов и вывести на экран.

Object Attribute Memory (OAM) - память атррибутов объектов

OAM — это область памяти PPU страница 256 байт способная хранить информацию о 64 спрайтах, эта область памяти имеет специальные адреса которые CPU может использовать для одновременного обновления всего содержимого с высокой скоростью. Как правило буфер OAM расположен по адресам $0200 — $02FF.

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

Данные спрайта

Для вывода спрайта PPU необходимо 4 байта данных, по порядку это данные о:

  1. Координата Y верхнего левого угла спрайта

  2. Номер тайла из таблицы паттернов спрайта (памяти chr)

  3. 8-битный флаг атрибутов (#%00 000 001) где с лева на право:

    1. Отразить спрайт по вертикали (7-й бит)

    2. Отразить спрайт по горизонтали (6-й бит)

    3. Отображение спрайта за фоном если 1 (5-й бит)

    4. 3-бита не используются (4-й — 2-й)

    5. И последние 2 бита палитра всего их может быть 4-ре: 00, 01, 10, 11

  4. Координата X верхнего левого угла спрайта

Если вспомним предыдущую статью там код примера вывода спрайта был следующим:

Пример кода с использованием регистра $2004
LDA #100 ; загружаем в акумулятор A значение 100
STA $2004 ; сохраняенм значение координаты Y в порт $2004
LDA #$01 ; спрайт под номером 1 (0-я строка, 1-й спрайт) 
STA $2004 ; записываем спрайт в порт
LDA #%00010110 ; маска 
STA $2004
LDA #100 ; x координата 
STA $2004

Теперь для того что бы спрайт записать в буфер OAM необходимо заменить регистр $2004 — PPUDATA на адреса буфера начиная с $0200 и увеличивать этот адрес на 1 при каждой записи, каждый 4-ре байта и будут описывать спрайт в буфере. По этому я и говорил что 64 спрайта в 256 байт памяти умещаются, 256/4 = 64. Давайте рассмотрим на конкретном примере:

Модифицированный пример записи спрайта в буфер
LDA #100 ; загружаем в акумулятор A значение 100
STA $0200 ; сохраняенм значение координаты Y в порт $2004
LDA #$01 ; спрайт под номером 1 (0-я строка, 1-й спрайт) 
STA $0201 ; записываем спрайт в порт
LDA #%00010110 ; маска 
STA $0202
LDA #100 ; x координата 
STA $0203

Теперь когда мы научились записывать данные о спрайтах в буфер остается вывести данный буфер на экран при отрисовки каждого кадра игры, для этого в NES предусмотрено прерывание NMI (Non‑Maskable Interrupts) как раз его событие можно использовать для отображение буфера.

NMI (Non-Maskable Interrupts) - немаскируемое прерывание

Если поверхностно посмотреть на событие данного прерывание оно срабатывает каждый раз при отрисовки кадра, возьмем средне 60 кадров в секунду (NTSC) в системе PAL это будет 50 кадров. Но в принципе не важно, важно то что данное прерывание(событие) срабатывает при отрисовки каждого кадра.

Если углубиться чуть глубже то на телевизорах с ЭЛТ, есть два блока строчной и вертикальной развертки, луч ЭЛТ бежит с лева на право и с верху вниз рисуя каждый кадр. В ассемблере для nes как раз есть такие понятия как hBlank (горизонтальная пауза) и vBlank (вертикальная пауза), и как раз на vBlank то есть на отрисовки последней строки кадра ЭЛТ и срабатывает NMI.

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

     LDA #$00
     STA $2003 ; указываем ppu использовать DMA
     LDA #$02
     STA $4014 ; указываем старший байт адреса буфера в регистр OAM DMA

В регистр OAM DMA ($4014) необходимо записать лишь старший байт адреса страницы буфера, у нас это $0200 — $02FF и эта страница с CPU «буфер спрайтов» все 256 байт будут скопированы в PPU OAM и отрисованы на экране.

Дополнительная информация

  1. Адреса буфера хранятся на картридже по этому этот адрес может быть любым $XX00 — $XXFF для их использования надо будет определять область памяти при компиляции, а так же в OAM DMA записать старший байт $XX.

  2. Некоторые маперы в частности MMC3, способны менять не только зеркалирование вертикальное/горизонтальное но и генерировать irq прерывание на определенный hblank тем самым во многих играх реализована разная скорость прокрутки фона, или меню как фиксированный фон.

  3. Идея формировать следующий кадр во время отображения текущего кадра используется до сих пор при написание современных игр.

Полезные ссылки

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