Как в 1995 году писали игры для Sega Saturn

Это документ, написанный мной в 1995 году, когда я работал над первой игрой студии Neversoft: Skeleton Warriors. Это была первая игра, в которой я не использовал язык ассемблера 68K.

Фото сделано примерно в то время. Комплект разработчика (dev kit) («Small Box» и ICE) стоит справа от меня.


Состояние игры
В представленном ниже документе вкратце описывается состояние кода Skeleton Warriors для Sega Saturn, а также упоминаются некоторые из множества аспектов, которые нужно было ещё сделать.

Документ нужен был, чтобы ускорить Дэну, Кену и Джеймсу знакомство с уже готовым кодом, объяснить им назначение каждого модуля и взаимодействие между ними. Также он позволил мне оценить печальное состояние этого кода, и, надеюсь, заставил меня взяться за ум.

Также я немного рассказываю о встраивании данных (файлы .GOV и .GOB) в программу, и о том, что мы будем делать в будущем.

Оборудование для разработки
Наша целевая платформа — Sega Saturn, имеющая два Risc-микропроцессора SH2 и один 68000. Пока мы используем только основной процессор Master SH2, вспомогательный slave SH2 будет использоваться, когда мы разберёмся, как это сделать. 68000 применяется для управления звуковым чипом, нам не пришлось писать для него код, потому что он будет использовать предоставленную Sega звуковую библиотеку.

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

В качестве системы разработки мы пользуемся PsyQ. Это не стандартная система разработки Sega, однако она все, кто с ней работал, считают её лучшей. Альтернативой ей является SNASM, созданная принадлежащей Sega студии Cross Products. Основная часть поставляемых Sega примеров кода должна работать в системе разработки SNASM, но легко конвертируется под PsyQ.

Система PsyQ состоит из платы интерфейса SCSI, которая устанавливается в PC, картриджа, вставляемого в Saturn и соединяющего их кабеля. Исходники компилируются на PC и скачиватся на Saturn, где и запускается программа. Код можно отлаживать с PC.


Система разработки PsyQ

Связь контролируется резидентной программой (PSYBIOS), обрабатывающей коммуникации между машинами. Это позволяет консоли Saturn загружать файлы с PC почти так же, как она загружала бы их с CD. Мы используем эту функцию для загрузки файлов каждого уровня.

У меня в комнате есть пара больших и громких ящиков, а ещё два PC. Меньший из двух ящиков — это E7000PC, являющийся встроенным эмулятором SH2. Он помогает разобраться, где вылетает программа, если отладчик PsyQ её не остановил. Также он полезен для отслеживания записи в память, но пока я почти не пользовался этой функцией.

Второй из громких ящиков — это нечто под названием «Small Box» (первый «Large Box» был размерами примерно с небольшой холодильник). По сути это Saturn с дополнительными интерфейсами для E7000 и эмулятора CD. На передней панели у него есть переключатели ID стран и переключатель между PAL и NTSC.

Внутри второго компьютера находится эмулятор CD — большая плата, благодаря которой жёсткий диск компьютера притворяется CD-приводом. В нём можно собрать образ CD и эмулировать его в реальном времени, чтобы посмотреть, как будет выглядеть игра, когда доберётся до настоящего CD. Эмулятор более-менее работает, хотя у него есть некоторые проблемы, над решением которых мы сейчас работаем вместе с Sega.


Dev kit самой компании Sega

Компилирование и компоновка
Общая сборка готовой программы управляется одним makefile: MAKEFILE.MAK. Он содержит зависимости и целевые объекты для всего проекта, в том числе компиляцию файлов .GOB и .GOV.

Отдельные модули исходного кода C (файлы .C) компилируются программой CCSH в объектные модули SH2 (.OBJ). Она сначала вызывает препроцессор GNU C под названием CPPSH (находится в C:GNUSH2BIN), затем вызывает CC1SH для его выходных данных, чтобы создать ассемблерный код SH2, и, наконец, вызывает ASSH (в C:PSYQ) для сборки его в готовый объектный формат.

Мы не используем C++, потому что мне сказали, что он создаёт огромные объектные файлы. Однако я не работал с ним, можете поэкспериментировать.

Несколько файлов на языке ассемблера SH2 (с расширением .S) просто собираются при помощи ASMSH напрямую в файлы .OBJ (это не то же самое, что ASSH, а более сложный макроассемблер). В настоящее время они используются только для встраивания данных, и не содержат машинно-зависимого кода.

ОЗУ Saturn, в которое можно загружать код, разделено на два блока по 1МБ. Один начинается по адресу $06000000, а другой — по $00200000. Блок по адресу $00200000 используется исключительно для хранения графики главного персонажа. Код программы записывается по адресу $06010000 (первые $10000 байт используются для системного пространства, стека и тому подобного.)

Код позиционно-зависимый и компилируется так, чтобы выполняться по этому конкретному адресу ($06010000) и никак иначе.

Файлы .OBJ компонуются вместе при помощи программы PSYLINK для создания файла MAIN.CPE — исполняемой программы с небольшим заголовком, который можно скачать в Saturn командой RUN. PSYLINK использует файл TEST.LNK для указания того, какие файлы .OBJ нужно включать и куда их помещать.

Данные
Игра разбита на несколько уровней, во многих уровнях используются одинаковые данные, но в основном они для каждого уровня свои. Все данные для каждого уровня собираются в два огромных файла .GOV и .GOB. (в случае шахты это MINE.GOV и MINE.GOB). Файл GOV содержит короткий заголовок, а затем идут все данные, которые должны находиться в видеопамяти. Файл .GOB содержит все данные, которые должны находиться в ОЗУ.

Уровень состоит из части показанных ниже файлов данных.

.SSQ — файл секвенсора спрайтов
.SBM — файл битовой карты, используемый для битовых фонов
.MAP — обе карты для заполняемых символами фонов.
.TIL — тайлсеты и палитры для заполняемых символами фонов.
.PTH — данные точек дороги и триггеров.
.TEX — текстуры для дороги.

Файлы .SSQ и .SBM созданы моим становящимся всё более неудобным секвенсором «SEQ». Файлы .MAP, .TIL, .PTH и .TEX созданы становящимся всё более потрясающим редактором карт «TULE», написанного Дэном.

Эти файлы при помощи ассемблера ASMSH собираются в соответствующие файлы .GOV и .GOB. Чтобы посмотреть, как это делается, см. файлы LEVEL.S и LEVEL1.S. В файл .GOV также включается часть данных конкретного уровня.

Модули
TEST.S — ничего особенного, задаёт несколько меток.

MAIN.C — верхний уровень программы. Содержит инициализацию оборудования, настройку уровней, код прохождения уровней и различные другие мелкие элементы, которые на самом деле стоило бы поместить в более подходящие модули. В нём довольно много мусора, потому что в этот модуль проще всего добавлять что-то новое для быстрого тестирования. Содержит код загрузки с CD или файлового сервера на PC. Содержит флаг для включения и отключения цветных полос TIMING.

GFXLIB.C — различные процедуры для доступа к оборудованию и выполнения различных графических функций. Почти все они написаны с нуля Дэном и часто очень неэффективны. Если вы часто используете процедуру отсюда, то неплохо было бы взглянуть, что она делает и написать более быструю версию в своём коде.

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

SMP_PAD.C — различные процедуры для считывания с джойстика Saturn, очень зависимые от оборудования.

GLOBALS.C — все глобальные переменные и несколько общих функций. Использование глобальных переменных — приемлемая практика программирования. Однако по различным причинам реализация глобальных переменных в SH2 довольно медленная, поэтому со временем я возможно преобразую часть в глобальные структуры, если понадобится. Содержит переменные, описывающие состояние MAN и PATH.

MAN.C — обрабатывает движение и отображение человека (Prince Lightstar, Talyn, Guardian или Grimskull — персонажа, которым управляет игрок). Пока это в основном логика движения и коллизий с дорогой. Кроме того, он обеспечивает соответствующую анимацию для каждого действия. Тут ещё нужно сделать много работы.

OB.C — обрабатывает движение и отображение объектов в игре, в особенности объектов врагов, например, воинов-скелетов и маленьких инопланетян. Здесь программируется основная часть геймплея: вражеский ИИ, основные движения и срабатывание триггеров. Структура данных готова ещё не полностью, в частности, не совсем проработаны проблемы с коллизиями и анимацией. Предстоит ещё куча работы.

DATA.S — различные таблицы, в настоящее время в основном анимации основных персонажей игрока.

LAYER.C — скроллинг фонов с параллаксом. Обновляет состоящие из символов фоны и скроллит битовые карты. Также выполняет скроллинг линий (эффект волн) в слое тумана. Пока таблицы для слоёв карт символов хранятся без сжатия. Их нужно сжать в формат RLE, который я использовал для версии на Genesis. Эта задача может перейти к Кену, если мы получим систему разработки под Saturn раньше, чем для Sony.

PAL.C — палитра. Можно выбирать из 2048 цветов. Любой пиксель на экране может быть одного из этих цветов. Я логичным образом разделил палитру на восемь палитр по 256 цветов. В PAL.C содержится код для их инициализации, подготовки и код для их циклической смены. Также им понадобится затемнение и более сложная циклическая смена, а также вспышки яркости и т.д.

BUL.C — примитивная система для обработки снарядов (бросок меча, удар рукой, выстреливаемые из рук ракеты и т.д.) как отдельных объектов. По-прежнему требуется довольно много работы для более сложного использования снарядов. Также нужен правильный код коллизий и анимаций.

PAD.C — простой модуль для запоминания состояния джойстика в более удобном формате. Запоминает, была ли недавно нажата кнопка, и нажата ли она сейчас.

START.C — одна строка, сообщающая, какой уровень будет первым, для простоты его смены в командном файле.

PANEL.C — простые процедуры для вывода полоски силы.

PATH.C — чудовищные процедуры для отрисовки дороги, а также обработки коллизий с дорогой.

MATH.C — простые синус, косинус и поворот точки на угол.

[Обновление] Вот пример кода из MAN.C. Всё жёстко прописано в коде и ссылается на глобальную структуру данных Man. Куча прописанных в коде чисел.

/**************************************************************/
/* Trigger jumping if needed, also variable height jump logic */

Man_JumpTrigger()
{
if ( Man.JumpFudge )
{
Man.JumpFudge—;
}

if ( Man.Mode != M_Crouch || Man_StandingRoom() ) // ok if not crouched, or there is headroom
{
if (Pad_Jump->Pressed) /* jump button pressed */
{
if ((Man.Contact || (Man.Mode == M_Hang) || Man.JumpFudge) && Pad_Jump->Triggered && !Man.Blocking) /* and not already jumping */
{
if (Man.Mode == M_Hang && Pad1.Down.Pressed)
{
Man.Contact=0;
Man.Mode=M_Jump;
Man.AnimBase = LS_Jumping; /* Change base anim to jumping */
Man_TriggerSeq(LS_Jump); /* start the jumping start anim */
Man.YV.f = 0x10000; /* and have no YV */
Man.Y.i += 4; /* and have no YV */
}
else
{
Pad_Jump->Triggered = 0;
if ( !JetPacCheat )
Man.YV.f = -0x00080000; /* Initial jump speed */
else
Man.YV.f = -0x00008000; // Initial speed in Jetpac mode
Man.Contact = 0; /* not on the ground any more */
Man.JumpTime = 0; /* just started jumping */
Man.AnimBase = LS_Jumping; /* Change base anim to jumping */
Man_TriggerSeq(LS_Jump); /* start the jumping start anim */
Man.XV.f+=Man.FlyVel;

if (Man.HangEnd && Man.Mode == M_Hang) // if hanging
{ // and on the end of a path
Man.HangEnd = 0;
Man.X.i += 12*Man.Facing; // the move past end of path
Man.JumpTime = -3; // bit more fixed v jump time
}
Man.Mode = M_Jump; /* change mode to jumping */

}
}
else /* Already jumping */
{
if (Man.JumpTime++ < MaxJumpTime) /* Still in initial jump period */
Man.YV.f -= 0x0005000; /* So can maintain jump YV */
}
}
else /* jump button not pressed */
{
Man.JumpTime = MaxJumpTime+1; /* so can’t alter YV again until landed */
}

}

}
OB.C разросся до чудовищного файла из 9000 строк, в которые включены все паттерны поведений отдельных объектов в игре. Также там есть огромное количество прописанных в коде чисел, например таких:

Drop_Arac(S_Ob *pOb)
{
int t;
if (pOb->Jump==1)
{
pOb->yv.f+=0x7fff;
pOb->y.f+=pOb->yv.f;
t=Path_GetYZ(pOb->x.i,pOb->y.i,pOb)-15;
if ((t>pOb->y.i)&&(t<pOb->y.i+20))
{
pOb->Jump=0;
pOb->y.i+=15;
Turn_Around(pOb);
pOb->SeqFile=Sprites[SpriteMap[34]];
Object_TriggerSeq(Arac_JumpLand,pOb);
}
}
else
{
if (pOb->Frame==16)
pOb->Jump=1;
if (pOb->AnimStat==AnimDone)
{
pOb->t1=0;
pOb->Mode=&Pattern_Arac;
}
}
Command_Arac(pOb);
}
Неприятное зрелище. Такой стиль кода пришёл из времён, когда игры были очень маленькими и наработал я его при работе с 68K.

Оставить комментарий