В этой части появляется первая играбельная демка в стиле Марио. Для этого надо разобраться с прокруткой и способами отладки. <<< предыдущая следующая >>>  Источник Регистр $2005 управляет прокруткой фона. Первая запись туда выставляет положение горизонтальной прокрутки, а вторая — вертикальной. Если неизвестно, какая прокрутка была выставлена, можно сбросить на горизонтальную чтением из регистра $2002. 99% игр используют только 2 таблицы имен — фактически, это фон экрана. Еще две доступных таблицы зеркалируют первые две. Эмулятор получает информацию о настройках таблиц из заголовка iNES-образа картриджа. Байты 6 и 7 описывают маппер — сопроцессор в картридже. Младший бит байта 6 описывает направление прокрутки. 0 — скролл по вертикали, таблиц имен зеркалируются по горизонтали. 1 — наоборот. В итоге мы получаем доступную для работы область 1х2 экрана (или 2х1, в зависимости от выбранной прокрутки) и скользящее по этой области окно, отрендеренное на телевизоре. Игра Gauntlet использует четырехстороннюю прокрутку. Это требует 2k дополнительной RAM на картридже. Игры с маппером MMC3 могут переключать режимы прокрутки в середине игры. Но в большинстве случаев режим прокрутки единый для всей игры, и используется всего 2 таблицы имен. В первом примере мы настроим горизонтальную прокрутку. Это выставляется в файле reset.s. Стрелки на джойстике будут двигать фон. Спрайтами реализован показометр положения фона: H для горизонтального сдвига и V для вертикального. Настоятельно рекомендую запустить этот образ в FCEUX и посмотреть дебаггером таблицы имен во время движения.  После пересечения $FF по горизонтали вызывается смена таблицы имен через обращение к регистру PPU_CTRL — он расположен по адресу $2000. Для пользователя это незаметно. Для подготовки демки использовались такие инструменты: буквы рисовались в Фотошопе, потом индексировались в четырехцветное изображение и копипастились в YY-CHR. Затем их надо сохранить в chr-файл и открыть его в NES Screen Tool, скомпоновать фон, а потом экспортировать с RLE-сжатием как .h файл. Теперь его можно загрузить при запуске приставки. Движение персонажа реализовано через сдвиг фона, а позиция спрайта не меняется. А при обновлении кадра происходит обновление спрайтов и выставление положения прокрутки: В втором примере прокрутка вертикальная, и таблица имен "закольцовывается" через левый и правый край экрана. Это выставлено все в том же reset.s. Для вертикальной прокрутки используются таблицы имен 0 и 2. Максимальная позиция вертикальной прокрутки равна $EF, потому что экран высотой 240 пикселей. Это обрабатывается аналогично предыдущему примеру. Еще одно отличие — переключение таблиц имен из нулевой во вторую и обратно: Исходный код:
Дропбокс
Гитхаб А сейчас будем делать демку с горизонтальной прокруткой и прыжками по платформам. Карты коллизий для 2 страниц фона будут храниться в памяти и займут там $200 байт. Сначала сделаем гравитацию. Каждый кадр спрайты должны падать на (++Y), если они не стоят на платформе. Будем считать, что низ метаспрайта выравнен с фоном. Так что можно проверять, не провалились ли нижние углы метаспрайта в платформу: Дальше надо поработать над плавностью движений и прыжков. Понадобится много переменных для координат позиции спрайта и фона, скорости, ускорения и пару констант для максимально допустимых скоростей. Но я на это забил. В итоге скорость прокрутки хранится в старшем полубайте X_speed.
Horiz_scroll += (X_speed >> 4);
Обычно прокрутка фона начинается, когда персонаж приближается к краю экрана. А когда он в центральной части, то движется сам по себе со статичным фоном. Здесь такая техника не используется, опять же для упрощения. Возможно, когда-нибудь сделаю рефакторинг. Исходный код:
Дропбокс
Гитхаб Sprite Zero Hit — это один из способов отследить событие в середине кадра, например изменение позиции горизонтальной прокрутки. Это позволит нам сделать статичный верх экрана, например счетчик очков, и прокрутку нижней части экрана. Есть несколько способов реализации: Нам годится только первый способ — он самый простой и безглючный. Нулевой спрайт хранится в OAM по адресам $0-$3. Если он содержит непрозрачный пиксель и этот пиксель отрисуется поверх непрозрачного пикселя фона, то в регистре $2002 выставится бит 0x40. Если же спрайт рисуется поверх прозрачного фона, то игра уходит в бесконечный цикл. Мы можем воспользоваться этим для настройки прокрутки. Процедура написана на Ассемблере. Сначала сделаем все что надо в V-blank. Потом выставим в ноль горизонтальную прокрутку и включим нужную таблицу имен. Затем вызовем SpriteZero(), и она уйдет в ожидание события — отрисовки строки, где наложатся нужные пиксели. Потом мы можем переключить прокрутки и таблицу имен — это произойдет посреди отрисовки экрана. В нашем примере нулевой спрайт содержит символ нуля, просто для наглядности. И еще сделал, чтобы он исчезал при нажатии Start.  В первой версии туториала урок здесь и заканчивался, но потом решил расширить тему. Так что сейчас запилим демку с фоном шириной 4 экрана и динамической генерацией фона на метатайлах, без RLE-сжатия. При перемещении персонажа на 16 пикселей демка будет дорисовывать 2 столбца тайлов за границей экрана, в нужную таблицу имен. Это можно уместить в V-blank. Для ускорения процедуру записи PPUupdate пришлось написать на Ассемблере и развернуть циклы. Таблица атрибутов фона тоже изменяется в ходе работы. Получилось сложно и громоздко, и на отладку ушло много времени. Так что будет хороший пример, чтобы показать техники отладки. Во-первых, реализация прокрутки медленная и не вкладывается во время. Чтобы понять это, пришлось вставить команду  в main() перед ожиданием V-blank. Начиная с этого момента, экранные строки будут рендериться в черно-белом цвете. Этот хак совместим не со всеми эмуляторами, например в FCEUX надо включить опцию ‘old PPU’. Получилось вот так:  Половина доступного процессорного времени уже потрачена, и это без музыки и противников. Для профилирования функций сделал запись в переменную до и после выполнения функции, и включил в дебаггере отслеживание записи по адресу этой переменной. А FCEUX умеет считать такты процессора между остановами. Получилось как-то так: Оказалось, что тормозит работа с буфером. Ее можно разбить на две функции покороче, и выполнять их через кадр. Теперь загрузка процессора выглядит получше:  Дальше убираю прокрутку влево. Теперь хорошо бы реализовать, чтобы можно было побежать налево и упереться в край экрана. Сразу это не получилось, и отладка методом аналитического тупления в код ((с) DIHALT ) не помогла. Пришлось генерировать карту адресов. Для этого надо вызывать линковщик с опцией:
ld65 ... -Ln “labels.txt”
И компилятор с транслятором с опцией -g. По этим файлам видно, что подозрительная функция move_logic() находится по адресу $C5B2, так что ставлю туда брейкпоинт. В принципе, можно расставить метки прямо в сишном коде и отключить оптимизацию, но я делал вызов пустой функции в нужном месте (движение персонажа влево) и отслеживал ее точное расположение по карте меток. Но перехват записи переменной компактней и удобней. Отладку все равно пришлось делать по ассемблерному листингу, но неправильное сравнение ‘if (X_speed < 0)’ нашлось довольно быстро. В этом месте X_speed обнулялась даже если нажать Влево. Изменил сравнение на <=, и все стало хорошо. В FCEUX для обработки джойстика с включенным отладчиком надо замапить опцию ‘auto-hold’ на кнопку клавиатуры и сначала включить холд, нажать Влево, и потом уже ставить брейкпоинт в отладчике. Юзер Rainwarrior из Nesdev сделал, а я слегка подправил скрипт на Python, который конвертирует метки ca65 в файл для отладчика FCEUX. На вход он берет label.txt. Пример использования есть в мейкфайле и бат-файле в исходнике к уроку.  Ссылка на скрипт(rainwarior разрешил использовать и распространять):
Дропбокс Оригинальная версия, без моих правок:
Форум Еще одна версия, пригодная для сборки бат-файлом:
Дропбокс Теперь прокрутка на 4 экрана работает, но сложнее, чем я себе это представлял. Вариант В аналогичен, но в нем вырезана вся отладка. Рекомендую посмотреть таблицы имен отладчиком и разобраться, как работает прокрутка.
Дропбокс
Гитхаб В некоторых случаях линкер может отказаться вносить все метки в карту, тогда надо добавить эту строку в каждый ассемблерный файл:
.debuginfo Иначе придется добавлять -g при каждом вызове cc65 и ca65.