Разработка игр под NES на C. Главы 22-23. Приложение 1 — мапперы и цифровой звук

в 21:47, , рубрики: C, cc65, Nes, Nintendo Entertainment System, ненормальное программирование, разработка, разработка игр

Здесь будет информация, не вошедшая в основной цикл, но слишком ценная, чтобы ее игнорировать.

<<< предыдущая следующая >>>

image
Источник

Из нашего обсуждения почти полностью выпала тема мапперов — сопроцессоров в картридже. Если надо сделать игру размером больше 0x8000 байт, то стандартных возможностей консоли для этого не хватит. Маппер позволяет переключать банки памяти в игре, и cc65 умеет с этим работать. Самый популярный маппер — MMC3. Кроме переключения банков памяти, он имеет счетчик строк.

Чтобы поиграть в собственную игру на настоящей приставке, можно использовать флеш-картридж наподобие Everdrive или PowerPak. Еще можно прошить чип EPROM и сделать под него собственную плату или принести в жертву другой картридж, но это требует специфических навыков, и мне сейчас недоступно.

3 старших бита в регистре $2001 включают усиление яркости цветовых каналов. Биты инвертируются — установка всех битов равномерно снизит яркость всего экрана. Все варианты палитры на картинке.
image

Стандартная библиотека С

Ульрих фон Бассевиц (Ullrich von Bassewitz) портировал под cc65 всю стандартную библиотеку С и кое-что еще. Теперь есть деление и умножение чисел, хоть и очень медленное. Возможно, табличное вычисление будет быстрее
#include “..includestdlib.h”
Теперь можно использовать работу с памятью: calloc, malloc, free и realloc. При тестировании этих возможностей обнаружились некоторые сложности — необходимо определять и __STACK_SIZE__, и __STACKSIZE__ в конфиге. Очень похоже на опечатку в библиотеке, но оба варианта встречаются в документации. Для работы с аллокатором также надо определить HEAP:
http://www.cc65.org/mailarchive/2009-05/6581.html
Настоятельно рекомендую со всем этим не связываться, оно медленно даже в сравнении с кодом на С, где память выделяется статично. Еще из интересного в библиотеке функции rand и srand для псевдослучайных чисел и qsort для сортировки
#include “..includecc65.h”
Так импортируются cc65_sin и cc65_cos — синус и косинус
#include “..includezlib.h”
А так — работа со сжатием. Не испытывал совсем.

Прерывания — IRQ

Принцип работы такой же как и с NMI: при его срабатывании управление передается в обработчик прерывания. Указатель на обработчик находится в векторе прерываний по адресам $FFFE-$FFFF. Есть три способа вызвать прерывание:

  • инструкция BRK в коде, опкод #00
  • прерывание звукового канала DMC включено, оно срабатывает при окончании семпла
  • прерывание может вызываться маппером, например MMC3 умеет так обрабатывать счетчик строк

Полезным выглядит только третий способ, он действительно позволяет менять настройки PPU в процессе отрисовки кадра — можно комбинировать фон и все такое.

Некоторые эмуляторы отрезают по 8 пикселей сверху и снизу экрана. NES выдает 240 строк, но телевизоры той эпохи часто теряли края экрана. Короче, не стоит помещать в эту область что-то критично важное для игры.

Чтение и запись данных PPU работает примерно одинаково. Пишем старший байт адреса в $2006, потом младший байт туда же, и ЧИТАЕМ из $2007 (LDA $2007). Но надо помнить, что первое чтение из PPU всегда дает мусор. Кроме работы с палитрами по адресам $3F00-$3FFF, там первое чтение дает правильные данные. Так что надо читать два раза. Я не разработчик NES и не имею ни малейшего понятия, почему оно так работает. Еще раз напомню, что всю работу PPU надо осуществлять во время V-blank.

Работа с DMC

Чуть подробней о том, как с помощью Famitone2 и Famitracker добавить музыку в игру. Если хочется обойтись без библиотек, то последовательность действий примерно такая:

Как проиграть семпл из памяти

*((unsigned char*)0x4015) = 0x0f; // выключить DMC
// регистр управляет включением каналв, DMC управляется битом 0x10
*((unsigned char*)0x4010) = 0x0f; // частота семплирования, 0x0f - максимальная
ADDRESS = 0xf000; // адрес DMC-семпла в ROM
// семплы должны быть в диапазоне адресов $C000-$FFFF в ROM
*((unsigned char*)0x4012) = (ADDRESS & 0x3fff) >> 6; // 0xf000 => 0xc0
LENGTH = 0x0101; // длина семпла
*((unsigned char*)0x4013) = LENGTH >> 4; // 0x0101 => 0x10
*((unsigned char*)0x4015) = 0x1f; // снова включить DMC
//при включении канала начинает играть семпл

Famitone2 все это делает сам. В любом случае, семплы должны быть короткие — я обычно использую 0.1-0.5 секунд. Если нужно увеличить длину, то придется снижать частоту, а это резко портит качество. Еще надо учитывать, что канал DMC играет примерно вдвое тише других каналов.

Попробуем добавить все это в игру. Буду использовать лицензированные для некоммерческого использования семплы из какого-то старого сборника. Я порезал их по длине и импортировал в Famitracker:
image

Дальше надо сохранить их как DMC-файлы. Каждый семпл привязывается к клавише инструмента, и можно компоновать их в трек в колонке DPCM. Потом его надо экспортировать в .txt. Программа text2data из Famitone2/tools преобразует его в файлы .s и .dmc.
text2data DMCmusic.txt -ca65

Дальше надо включить каналы DMC и SoundFx в Famitone2. Это в секции .define файла reset.s. Семплы будут располагаться в памяти начиная с адреса $F000, это опция FT_DPCM_OFF. Сами семплы располагаются в конце файла reset.s, в .segment “SAMPLES”. Его адрес тоже надо указать в .CFG. Дальше Famitone2 справляется своими силами и переключает семплы процедурой из файла famitone.s. Я также добавил не-DMC эффекты для озвучки прыжка и конвертировал их через famitone2/tools/nsf2data — эта техника уже рассматривалась раньше.

Дропбокс
Гитхаб

А теперь можно добавить DMC-эффекты. Оставляю тот же трек, но переношу ударные в канал шума — он не будет прерываться при эффектах. В Famitone2 это делается примерно так же.
image

Для вызова этих эффектов из кода, надо вызывать процедуры из famitone2.s по меткам, функцией fastcall:
void __fastcall__ DMC_PLAY(unsigned char effect);

Номер эффекта должен вложиться в один байт и соответствовать номеру DMC-семпла. Их надо сверить с сгенерированным файлом DMCmusic2.s, могут быть сюрпризы:
image
25 и 27 получились, потому что семплы привязаны к 25 и 27 ноте ряда.
А дальше очень просто:
DMC_PLAY(27);

Один играет при прыжке, а второй при нажатии Старт.

Дропбокс
Гитхаб

Автор: Вадим Марков

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js