Как я заново открыл свою первую игровую консоль и написал для нее небольшую игру.

Детские воспоминания

Я родился в начале восьмидесятых и большую часть детства провел в Венесуэле. Моему отцу предложили работу в быстроразвивающейся нефтяной промышленности, и мы все переехали туда и жили в парке трейлеров для экспатов в Пуэрто-Ордасе. Это был своего рода закрытый поселок с забором и охраной, с дешевым жильем и безопасностью для иностранного персонала, такого как мой отец. В общине была американская школа, детская площадка и теннисный корт. Мы особо не гуляли, за исключением продуктов.

Время от времени мой отец или коллега возвращались из заграничной поездки и привозили домой что-то из внешнего мира. Наверное, где-то в 1988 или 1989, мне должно было быть восемь лет, в нашей гостиной появилась волшебная шкатулка. Atari VCS 2600 Jr.

Atari 2600 имеет 8-битный процессор MOS 6507, работающий на частоте 1,17 МГц, и 128 байт оперативной памяти, что даже тогда было ничтожным. Примечательно, что в системе не было кадрового буфера, из-за чего приходилось рисовать картинку, когда электронная пушка перемещалась по экрану. Это называется гонка по лучу, и я очень рекомендую увлекательную книгу с тем же названием, если вам интересна история Atari. Если вы больше относитесь к типу TL; DR, ознакомьтесь с статьей WIRED.

Позже я узнал, что то, что теперь украшало нашу маленькую гостиную, на самом деле было миниатюрной версией 2-го поколения оригинального Atari 2600 в более стильном деревянном корпусе. Тогда мне было бы наплевать, но я уверен, что хипстеры середины восьмидесятых отнеслись бы к младшему с пренебрежением.

Мой брат и я, а также другие мальчики из нашего района провели много-много счастливых часов, играя во всевозможные игры на Atari. Когда я стал старше, мы переехали в Швейцарию, и на смену Atari пришли NES, Sega Mega Drive, Super NES,… значительно превосходящие в техническом отношении консоли. Atari с нами не ездил, я предполагаю, что мы подарили ее местным жителям.

Перенесемся на пару лет вперед, когда мне было около четырнадцати или пятнадцати, почти через десять лет после моего первого знакомства с Atari. Я учился в старшей школе и записался на факультатив для детей, интересующихся компьютерами. Главным призывом для меня и моих товарищей были получасовые занятия Command & Conquer после занятий на компьютерах Intel, подключенных к сети Ethernet, в классе. Фактическая цель класса заключалась в том, чтобы научиться пользоваться программным обеспечением Office и приобрести базовые навыки программирования. Выбранной IDE была Q-Basic, IDE и интерпретатор для языка программирования BASIC.

С помощью Q-Basic я написал свою первую компьютерную игру. Я назвал его Hund, что в переводе с немецкого означает собака. Игра была невероятно простой и показывала двух собак, которые случайным образом бегали по экрану слева направо. Побеждает первая собака, которая достигнет правой стороны экрана. Никакого взаимодействия с пользователем не требуется.

Вы можете играть с другом и размышлять, какая собака выиграет, даже ставить на это деньги. Излишне говорить, что это была довольно скучная игра. Хотел бы я сказать, что с того момента я увлекся разработкой игр и создал бестселлер. Но, как уже упоминалось, в первую очередь мы хотели поиграть в Command & Conquer. Тем не менее Hund стала первой игрой, которую я написал.

В наши дни я зарабатываю на жизнь написанием программного обеспечения и до сих пор наслаждаюсь им. Но с возрастом, женитьбой, отцовством и прочим приходит сентиментальность… совсем недавно я испытал приступ ностальгии и захотел заново пережить некоторые из своих детских переживаний. Я решил, что хочу узнать больше об Atari, моей первой игровой консоли, и написать для нее игру. Что может быть лучше для начала, чем Hund?

Получение консоли

Поскольку у меня больше нет оригинала, мне сначала пришлось купить подержанный Atari 2600 Jr. Это оказалось на удивление легко. Их было произведено в таком количестве, что они до сих пор легко доступны. Поиск на Ebay дает около 500 результатов. Я также купил копию River Raid, моей любимой игры в то время.

Когда все было доставлено, я взволнованно распаковал его и подключил к телевизору в своем офисе. К моему ужасу, я не получил изображения. Вскоре я понял, что наше офисное телевидение слишком современное и больше не имеет аналогового ТВ-тюнера. К счастью, у меня дома был телевизор 10-летней давности. Этому телевизору удалось настроиться на канал, на который Atari отправляла свою картинку, но почему-то не смог удержать картинку. Иногда канал вообще не находил.

В этот момент я был крайне разочарован собой и миром в целом. Как бы я мог написать игру для Atari, если бы я вообще не умел играть в нее?

Фаза 0 - Незначительные модификации

В первые тихие дни нового года я встретил своего друга за пивом и рассказал ему свою историю. Имея некоторый опыт работы с винтажными консолями, он указал мне на статью (только на немецком языке), описывающую, как модифицировать Atari 2600, чтобы подключить его к современному телевизору. Потребуется некоторая пайка и фрезерование. К счастью для меня, материнская плата Atari была легко доступна и оказалась очень удобной для пайки.

Модификация предполагает удаление материала с корпуса и материнской платы, а также просверливание трех отверстий для крепления разъемов типа «тюльпан».

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

Через пару часов модификация была завершена, и мой Atari оснастили терминалами cinch для композитного аудио / видео выхода. Я с тревогой снова подключил его к телевизору, изменил его входной источник на композитный и включил консоль. К моему удовольствию, я получил приемлемую картинку и, наконец, смог поиграть в River Raid.

Это ознаменовало завершение нулевой фазы моего проекта Atari 2600. Как и в реальной жизни, я изначально не думал, что будет фаза 0.

Разработка для Atari 2600

Среда разработки

Полный позитивной энергии я приступил к установке инструментов, необходимых для написания игры для Atari. Все, что вам нужно, это ассемблер dasm (универсальный макроассемблер с поддержкой нескольких 8-битных микропроцессоров) и эмулятор Atari 2600 Stella. Оба могут быть установлены через Homebrew на Mac, так что эта часть была очень простой.

$ brew install dasm stella

Кроме того, нужен только текстовый редактор.

Гонка по лучу

Atari 2600 - необычная машина для программирования. Среда очень ограничена, всего с тремя регистрами (только с одним регистром общего назначения) на MOS6057 и жалкими 128 байтами ОЗУ. Но это не главная проблема.

Что делает разработку для Atari 2600 такой интересной (или, лучше сказать, раздражающей), так это отсутствие буфера кадра. Проще говоря, буфер кадра позволяет отображать картинку, записывая значения цвета по адресам памяти. По сути, это растровое изображение всего экрана где-то в памяти. Измените содержимое растрового изображения, и отображаемые пиксели изменятся. И что очень важно, после записи в буфер кадра изображение остается таким.

На Atari это не работает.

В Atari рендеринг должен происходить во время прорисовки изображения телевизором. Вся программная модель игры Atari вращается вокруг движения электронного луча внутри классического телевизора. Луч движется сверху вниз, образуя так называемые линии сканирования. Линии развертки прослеживаются слева направо. Когда электронный луч достигает дна, его необходимо выключить и переместить вверх. Это известно как интервал вертикального гашения. В конце каждой строки развертки также должно происходить горизонтальное изменение положения в интервале горизонтального гашения. Схема ниже представляет собой отрывок из руководства по программированию Atari 2600 и наглядно демонстрирует ситуацию.

Логика игры должна находиться в областях вертикального холостого хода и развертки, которые выполняются при выключенном электронном луче. Графическому чипу Atari 2600 необходимо указать, что рисовать для каждой строки развертки в течение горизонтального интервала пустого изображения, который длится около 22 циклов процессора. Для сравнения: даже простые операции, такие как сложение, занимают два цикла процессора. На счету каждый цикл, и фактически подсчет циклов является неотъемлемой частью программирования Atari.

По сути, ваш код всегда пытается опередить электронный луч, отсюда и термин «гоночный луч».

Привет мир

Чтобы получить представление о структуре типичной игры для Atari 2600, поможет пример hello world. Я не буду здесь вдаваться в подробности, так как есть много отличных руководств, особенно в AtariAge.

В примере рисуется спрайт игрока с одной строкой (значения пикселей 01010101) на каждой строке развертки. Подсчет количества сгенерированных видимых строк развертки хранится в регистре x, а также используется в качестве значения цвета спрайта игрока, создавая вертикальный радужный узор.

ЦП MOS6057 взаимодействует с графическим чипом через специально назначенные адреса памяти. В этом примере «sta WSYNC» означает «хранить содержимое регистра a (накопитель a) по адресу WSYNC (0x02)». Это заставит графический чип останавливать ЦП до тех пор, пока линия сканирования не закончится, а затем активировать его в начале горизонтального пустого интервала следующей строки сканирования. Это позволяет процессору синхронизироваться с движением электронного луча. ЦП и графический чип постоянно выполняют это рукопожатие, чтобы синхронизировать выполнение программы с тем, что отображается на экране.

«Sta RESP0» указывает графическому чипу «нарисовать спрайт игрока 0». Инструкция должна выполняться точно в нужное время, когда луч находится в горизонтальном положении, в котором мы хотим, чтобы наш спрайт появился. В этом примере мы используем серию инструкций nop (no-operation или dummy), которые предназначены для того, чтобы тратить два цикла процессора на позиционирование луча по направлению к центру экрана.

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

Запуск в эмуляторе

Теперь, когда у нас готов наш пример hello world, давайте скомпилируем его в двоичный файл с помощью dasm и запустим его в эмуляторе.

$ dasm hello.asm -ohello.bin -f3
$ Stella hello.bin

Это основной рабочий процесс разработки Atari 2600.

Запуск на оборудовании

Чтобы запустить игру на Atari 2600, нам нужно сначала загрузить ее в картридж. Картридж Harmony имитирует тележку Atari и позволяет запускать нашу игру на реальном оборудовании, просто копируя ее на SD-карту в формате FAT.

Написание «Hund»

Вооружившись работающим оборудованием и функциональной средой разработки, я приступил к внедрению «опыта Hund» в Atari 2600.

Hund на Atari 2600 - это игра для двоих, в которой каждый игрок продвигает свою собаку вперед, многократно нажимая одну кнопку джойстика. Также есть экран приветствия, вызывающий эпилепсию, на котором отображается название игры, и экран, на котором отображается победитель матча.

Я быстро резюмирую, как реализован каждый экран. Если хотите подробностей, полный исходный код доступен на Github.

Экран приветствия

Этот экран отображается при запуске игры и просто отображает «СТО» по вертикали с чередованием цветов.

Текст сохраняется в виде растрового изображения ближе к концу источника ПЗУ.

WelcomePhrase:
    .BYTE %00000000 ; H
    .BYTE %01000010
    .BYTE %01000010
    .BYTE %01111110
    .BYTE %01000010
    .BYTE %01000010
    .BYTE %01000010
    .BYTE %00000000
... and so so on ...

Цикл строки развертки сохраняет количество видимых строк в регистре x (как в примере hello world) и смещение в битовой карте в регистре y. Каждая строка растрового изображения повторяется через четыре строки развертки. Регистр игрового поля Atari PF1 (своего рода фоновый спрайт) используется для рисования растрового изображения.

Экран закрывается, когда оба игрока одновременно нажимают кнопку джойстика.

Экран игры

Это фактический экран игры.

  • Два спрайта игроков GRP0 и GRP1 используются для отображения собак.
  • Горизонтальные позиции обоих игроков хранятся в переменных (P0_X и P0_Y).
  • В вертикальном пустом поле запрашиваются состояния кнопок джойстика. Собаки продвигаются вперед всякий раз, когда кнопка отпускается (переменные P0_PRESSED и P1_PRESSED меняются от 1 до 0)
  • Спрайты располагаются горизонтально путем записи в регистры строба RESP0 и RESP1 в нужное время (как в примере). Цикл деления на 15 тратит впустую соответствующее количество циклов ЦП перед отрисовкой спрайта. Таблица содержит значения остатка от деления на 15. Положение точно настраивается с помощью регистров HMP0 и HMP1, которые позволяют сдвигать спрайт по горизонтали от -8 до +7 пикселей. Просмотрите это объяснение, если вам интересно, что означают эти предложения.
  • Когда позиция любого из игроков достигает 160 (количество пикселей по горизонтали), этот игрок выигрывает, и отображается экран выигрыша.

Экран победы

Этот экран почти идентичен экрану приветствия, за исключением того, что отображается другой текст в зависимости от того, какая собака выиграла («WIN1» или «WIN2»).

Вывод

Когда я писал Hund, я поверхностно коснулся разработки Atari 2600. Удивительно и невероятно унизительно видеть, какие игры разработчикам того времени удавалось писать, учитывая ограничения консоли.

Еще раз, всем, кто интересуется платформой Atari 2600 и типами игр, которые она породила, я настоятельно рекомендую прочитать книгу Racing the Beam.

Для меня это был забавный способ потакать собственной ностальгии.