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


Давайте разберем алгоритм как сделать прокрутку к примеру на 4 экрана:

  1. Переключить  nametable на следующий экран от позиции аппаратной прокрутки

  2. Проверить сместился ли персонаж в право (или лево) на 8 пикселей и нужно ли отрисовать (буду писать отрисовать на самом деле имея ввиду заполнить) новый столбец в nametable

  3. Заполнить attribute table для нового столбца (то есть указать его цвет)

  4. Обновить карту коллизий на экран (более подробно будет описано в следующей статье)

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

  1. Нам надо перерисовать следующий за позиции аппаратного скролинга (либо перед ним через 32 тайла, а проще говоря в следующим nametable 2 ($2400-$27FF), либо если позиция скрола находиться в nametable 2 то надо перерисовать фон в nametable 1 ($2000-$23FF).

  2. Nametable - как следует из названия представляет с собой таблицу, но в ней 4 строки и 255 столбцов, если можно так выразиться возьмем к примеру nametable 1:

    1. $2000 - $20FF

    2. $2100 - $21FF

    3. $2200 - $22FF

    4. $2300 - $23FF

  3. Разрешение экрана nes 32х32 тайла ( с допущением последние просто обрезаются)

Давайте наглядно посмотрим на nametable и выведем формулу по которой будем вычислять

nametable в проекции на экран
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 и обновляет атрибуты для новых колонок, так же алгоритм вычисления колизий с картой. Все это будет описано в следующих статьях.

Ссылки:

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


  1. shiru8bit
    00.00.0000 00:00
    +3

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


    1. himysay Автор
      00.00.0000 00:00
      +1

      Да согласен особого скила объяснять нет, но есть желание поделиться знаниями, вдруг пригодится, Я сам искал информацию как реализовать, в итоге только сотни статей по духу а вот были игры и у них была система камер. В итоге где то подсмотрел где то допилил. Где то разобрался что nametable не 32х32 а 4х255 и в итоге допилил формулу определения плитки, опять же анимация сменой атрибута плитки из 4-х спрайтов, коллизии на спрайт фона, что бы с экономить место в RODATA и ещё много моментов которые вот просто так не описаны, Я конечно постараюсь писать более понятно, надеюсь у меня это получится.