В интернете много статей показывающих как скролить фон на два экрана (скажем так это стандартно), а вот как скролить фон на несколько экранов вперед как например в марио, или чип и дейле мало где можно найти а если и находиться такое в интернете то объяснение как это сделать? Довольно сложное для понимания человеку который только начинает свой путь в nes...
Давайте разберем алгоритм как сделать прокрутку к примеру на 4 экрана:
Переключить nametable на следующий экран от позиции аппаратной прокрутки
Проверить сместился ли персонаж в право (или лево) на 8 пикселей и нужно ли отрисовать (буду писать отрисовать на самом деле имея ввиду заполнить) новый столбец в nametable
Заполнить attribute table для нового столбца (то есть указать его цвет)
Обновить карту коллизий на экран (более подробно будет описано в следующей статье)
Позволю сделать себе, несколько важных замечаний, прежде чем мы перейдем к реализации задуманного плана.
Нам надо перерисовать следующий за позиции аппаратного скролинга (либо перед ним через 32 тайла, а проще говоря в следующим nametable 2 ($2400-$27FF), либо если позиция скрола находиться в nametable 2 то надо перерисовать фон в nametable 1 ($2000-$23FF).
-
Nametable - как следует из названия представляет с собой таблицу, но в ней 4 строки и 255 столбцов, если можно так выразиться возьмем к примеру nametable 1:
$2000 - $20FF
$2100 - $21FF
$2200 - $22FF
$2300 - $23FF
Разрешение экрана nes 32х32 тайла ( с допущением последние просто обрезаются)
Давайте наглядно посмотрим на nametable и выведем формулу по которой будем вычислять
На самом деле, замечание номер 2 было для большего понимания как заполняется nametable в первую очередь рассмотрим реализацию алгоритма определения того что прокрутка сместилась на 8 пикселей. Тут надо вспомнить что 8 а точнее 7 (0-7) это %00000111 то есть в последних цифрах числа содержится цифра 7 и если ее отфильтровать через операцию END и если в этом случае будет установлен флаг Z то получается что позиция прокрутки увеличилось на 8 (либо содержит 8)
LDA scrollPosition
AND #%00000111
BNE NewColumnCheckDone
JSR drawNewCollumnNametable
Алгоритм заполнения nametable тоже довольно прост, нам всего лишь надо вычислить младший и старший байт текущего nametable, оговорюсь что надо определить в каком nametable находиться прокрутка и просто переключить текущую таблицу имен на следующую и обратно. В момент когда прокрутка находиться в позиции $FD это из разрешения nes по горизонтали у нас 253 точки.
LDA #%1000100 ; set to increment +32 mode
EOR nameTable
STA $2000
LDA $2002 ; read PPU status to reset the high/low latch
LDA colHig
STA $2006 ; write the high byte of column address
LDA colLow
STA $2006 ; write the low byte of column address
Пока все просто, но надо вычислить еще позицию столбца для заполнения нового nametable. Формула довольно простая scroll/8 номер тайла это младший байт, старший байт будет равен nametable*4 + $24 в ассемблере нету операций умножения и деления но можно смещать вправо и влево что приведет к умножение на 2 и соответственно деление на 2 каждое смещение числа, в коде это выглядит так:
LDA scrollPosition
LSR A
LSR A
LSR A
STA colLow
STA columnNumber
LDA nameTable
ASL A
ASL A
CLC
ADC #$24
STA colHig
Карту я определил в секции RODATA как массив байт пример вы сможете увидите в репозитории на github.com где есть полный листинг программы, ссылку приведу ниже. Я не стал заморачиваться и писать еще несколько десятков строк что бы переворачивать массив таким образом что бы можно было и человеку читать и программе. По этому определил его как повернутым на 90% и в зеркальном отражение. В общем строка в массиве ровна столбцу в таблице имен. Для того что бы его прочитать с определенного момента и загрузить в таблицу имен, нам просто необходимо вычислить младший и старший байт да бы начать чтение с младшего байта и загрузить 32 байта в память приставки. Формула тоже простая collumnNumber * 32 = sourceLow и старший бит, и тут я посмотрел в код и он не нужен на данный момент мне. Реализация данной формулы тоже проста:
LDA columnNumber
ASL A
ASL A
ASL A
ASL A
ASL A
STA sourceLow
И на последок загружаем все в память
LDX #$1E ; copy 30 bytes
LDY #$00
DrawColumnLoop:
LDA (sourceLow), y
STA $2007
INY
DEX
BNE DrawColumnLoop
LDA #%10010000
EOR nameTable
STA $2000
В этой статье я привел основные моменты перерисовки фона для динамической его прокрутки, в исходном коде вы найдете еще несколько строк обвязки которые подготавливают данные. Увеличивают сначала х координату игрока и если игрок на середине экрана то увеличивается прокрутка, есть математика которая определяет загрузку attribute table и обновляет атрибуты для новых колонок, так же алгоритм вычисления колизий с картой. Все это будет описано в следующих статьях.
Ссылки:
shiru8bit
К сожалению, даже точно зная, как именно это работает, из статьи я понять ничего не смог. Всё-таки уметь что-то делать и уметь понятно объяснять, как это делать - это два очень разных скилла.
himysay Автор
Да согласен особого скила объяснять нет, но есть желание поделиться знаниями, вдруг пригодится, Я сам искал информацию как реализовать, в итоге только сотни статей по духу а вот были игры и у них была система камер. В итоге где то подсмотрел где то допилил. Где то разобрался что nametable не 32х32 а 4х255 и в итоге допилил формулу определения плитки, опять же анимация сменой атрибута плитки из 4-х спрайтов, коллизии на спрайт фона, что бы с экономить место в RODATA и ещё много моментов которые вот просто так не описаны, Я конечно постараюсь писать более понятно, надеюсь у меня это получится.