Ранее я уже писал статью о выводе спрайтовой графики на экрана с использованием регистра OAMDATA ($2004), но это подходит лишь для экспериментов и вывода некой статической картинки. Но движение либо анимация будет приводить к появлению различных артефактов наслоению спрайтов и так далее. Что бы этого избежать следует сформировать для каждого кадра буфер спрайтов и вывести на экран.
Object Attribute Memory (OAM) — память атррибутов объектов
OAM — это область памяти PPU страница 256 байт способная хранить информацию о 64 спрайтах, эта область памяти имеет специальные адреса которые CPU может использовать для одновременного обновления всего содержимого с высокой скоростью. Как правило буфер OAM расположен по адресам $0200 — $02FF.
Для того что бы заполнить буфер данными о спрайтах необходимо вспомнить предыдущую статью.
Данные спрайта
Для вывода спрайта PPU необходимо 4 байта данных, по порядку это данные о:
-
Координата Y верхнего левого угла спрайта
-
Номер тайла из таблицы паттернов спрайта (памяти chr)
-
8-битный флаг атрибутов (#%00000001) где с лева на право:
-
Отразить спрайт по вертикали (7-й бит)
-
Отразить спрайт по горизонтали (6-й бит)
-
Отображение спрайта за фоном если 1 (5-й бит)
-
3-бита не используются (4-й — 2-й)
-
И последние 2 бита палитра всего их может быть 4-ре: 00, 01, 10, 11
-
-
Координата 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 и отрисованы на экране.
Дополнительная информация
-
Адреса буфера хранятся на картридже по этому этот адрес может быть любым $XX00 — $XXFF для их использования надо будет определять область памяти при компиляции, а так же в OAM DMA записать старший байт $XX.
-
Некоторые маперы в частности MMC3, способны менять не только зеркалирование вертикальное/горизонтальное но и генерировать irq прерывание на определенный hblank тем самым во многих играх реализована разная скорость прокрутки фона, или меню как фиксированный фон.
-
Идея формировать следующий кадр во время отображения текущего кадра используется до сих пор при написание современных игр.
Полезные ссылки
-
https://www.youtube.com/channel/UCzgRrIXX4QDiaWkISx6DKFw — канал на ютубе где выходят аудио/видео формат статей по программированию на nes/famicom/dendy
-
https://github.com/lnroma/newGameNes — репозиторий с кодом игры
-
https://habr.com/ru/post/715994/ — считывание контроллера
-
https://habr.com/ru/post/551488/ — вводная статья
-
https://habr.com/ru/post/715994/ — прокрутка фона
-
https://habr.com/ru/post/553848/ — создание графики nes/dendy
-
https://habr.com/ru/post/716254/ — Программирование assembler 6502 nes/famicom/dendy векторы прерывания, процедуры и их вызов
ссылка на оригинал статьи https://habr.com/ru/post/719636/
Добавить комментарий