Разработка игры Frogger для компьютера Vectrex

Какое-то время назад я переводил рассказ Chris Salomon о его разработке игры Frogger для компьютера Vectrex. Тот рассказ, написанный им в 1998 году, является, на мой взгляд, очень интересным документом, позволяющим проникнуться как духом этой необычной платформы, так и спецификой разработки на ассемблере вообще.

Chris не забросил Vectrex и, сравнительно недавно, довёл до релиза собственный эмулятор Vectrex для Windows (лучший, на данный момент) под названием «Vide». Мне, к слову, приятно, что мои исходники Electric Force помогли ему реализовать приличную поддержку отображения векторных кривых в этом эмуляторе.

И вот, получив в руки такой мощный инструмент, Chris не смог отказать себе в удовольствии вернуться к своему старому Frogger’у, чтобы доработать и улучшить его. Об этом он рассказал в своём блоге.

Таким образом, здесь я публикую две части моего перевода — первую, об оригинальной разработке 1998 года (из файла progger.txt, бродившего вместе с исходниками игры) и вторую — о её продолжении (из блога автора).

Год 1998. Начало

631fabe7e828465196e129841858601c.jpg

Этот файл содержит заметки, которые могут быть интересны другим разработчикам под Vectrex.
Также он включает «краткую» историю разработки Vectrex Frogger.

Обновление изображения в Vectrex Frogger всегда происходит с частотой 50Гц. Всё отрисовывается не более, чем за 30000 тактов. Ниже приводятся некоторые заметки о техниках оптимизации, позволяющих этого достичь.
На самом деле, я очень город достигнутой скоростью! Вполне возможно, что мои функции для работы со спрайтами — быстрейшие из всех существующих в играх под Vectrex (как минимум, мне нравится так думать…)

Спрайты смотрятся немного странно на настоящем Vectrex — слегка «пятнистыми», однако, к этому можно привыкнуть. Пятнисты они потому, что я использовал для каждого из них фиксированный масштаб (scale) 6. Длинные линии приходилось составлять из более коротких. Vectrex, по своей природе, не всегда выключает луч когда нужно (двумя-тремя тактами позднее, чем нужно). И он всегда включает луч за несколько тактов перед началом рисования…
Это приводит к яркой точке (можно было бы написать новые функции для рисования векторов, не использующие таймер и вычисляющие время иначе, более точно, однако кому захочется этим заниматься?).

Vectrex Frogger, среди прочего, должен был служить хорошо документированной программой для обучения программированию на этом компьютере. Мне кажется, этого мне сделать не удалось.
Из-за гигантского количество векторов, которые должны были отрисовываться в крохотные промежутки времени, необходимо было оптимизировать каждый такт. Из-за этих оптимизаций, как мне кажется, программа перестала быть хорошо читаемой. В основном цикле остался вызов единственной функции BIOS (опрос состояния цифрового джойстика).
Все остальные вызовы были последовательно удалены и заменены оптимизированными (для конкретных ситуаций) макросами или подпрограммами.
Во всё это чрезвычайно трудно вникнуть, если только не вы сами это писали.

Одна из применённых техник оптимизации:

Объяснение, как нарисовать вектор.

Так или иначе, чтобы переместить луч к нужной позиции, вы задаёте его скорость, направление и время, в течении которого эта скорость применяется. Затем ждёте, когда это время истечёт.
Время — это масштаб, который вы задали. Если устанавливаете масштаб $90, это значение должно быть записано в таймер 1 (VIA_t1_cnt_lo. Прим. пер.) и затем, в «бесполезном» цикле, ожидается, когда оно истечёт.
Что я предпринял, так это сделал этот цикл полезным. Существенная часть вычисления делается в Vectrex Frogger как раз в это, обычно бесполезное, время.
Я использовал этот подход, преимущественно, при перемещении луча. Большой плюс здесь в том, что как бы много я не делал в это время, это ни на что не влияет! Единственное, за чем вы должны следить, это не занимать времени МЕНЬШЕ, чем значение таймера. Впрочем, это легко решается тем же циклом опроса таймера (VIA_int_flags — прим. пер.) в конце ваших вычислений.

Тот же подход можно использовать и во время рисования вектора. Рисование и перемещение вектора — одно и то же. Единственная разница — «паттерн», записываемый в сдвиговый регистр. Для перемещения туда записывается 0, для рисования — $FF (если, конечно, хотите нарисовать сплошную линию).
Однако, при рисовании нужно удостовериться, что вы выключили луч (записью 0 в сдвиговый регистр) вовремя. В противном случае получите в конце вектора яркую точку!

Ещё одним способом оптимизации было написание специальной функции (макроса) для отрисовки списка векторов. Каждый выигранный там такт был на вес золота.
Допустим, вывожу 30 спрайтов и каждый спрайт состоит из, скажем, 20 векторов. Это составляет около 600 векторов. Если я напишу оптимизированную функцию, которая сэкономит около 10 тактов на вектор, мы одним ударом выиграем около 6000 тактов!!!

И еще — разворачивание циклов!
На самом деле, я использовал этот подход постоянно с моими программами на Watcom C, однако никогда не думал, что буду реализовывать такое сам. Сегодня я попытал счастья, разворачивая функцию отрисовки списка векторов. Удача! Я выиграл более 10 тактов на КАЖДЫЙ вектор. Это дало мне еще одно ускорение в почти 3000 тактов! Я раскрутил лишь один цикл — главный цикл рисования спрайта.
Другой важный цикл рисования спрайтов (для спрайтов домов) оставался нетронутым, хотя, если бы я оптимизировал таким образом все циклы, я бы вероятно выиграл от 500 до 1000 тактов. Но здесь есть и отрицательная сторона — это делает программу значительно длиннее. Ну, мы не можем получить всё, не так ли?

Ещё одна техника не использована, поскольку в ней, кажется, больше нет нужды:
Реализация нового типа векторного списка, который включал бы информацию о масштабе, может дать выигрыш в несколько циклов, так как многие длинные линии теперь рисовались бы короткими. Это может снизить потери, если список будет включать вторую переменную для длины.

Кстати:
Ассемблер который я использовал — ОЧЕНЬ хорош. Для оптимизации обязательно смотрите в создаваемый им *.lst файл. В нём не только код, но и количество тактов для каждой инструкции и их длина… Правда, очень здорово :-)

Немного про определения объектов (специфично для подпрограмм, использованных в Vectrex Frogger):

Все изображаемые в игре «предметы» теперь называются объектами. Эти объекты состоят из двух разных сущностей — списка векторов и определения структуры объекта. Список векторов — обычный для Vectrex. Сначала идёт количество векторов (минус 1), затем относительные координаты векторов (смещения).

Для облегчения фиксации столкновений, все эти списки векторов должны начинаться с крайней левой точки. В ранней версии подпрограммы они должны были быть обязательно замкнутыми, однако больше этого не требуется.
Ниже приведён пример структуры объекта (взят из Vectrex Frogger):

otter1a_object:
DB 0 ; скорость + направление
DW otter1a_sprite ; определение графики
DB 0 ; длина в SCALE_FACTOR_GAME
DB 5 ; задержка для анимации
DW otter1b_object ; указатель на следующий анимированный объект
DB -4 ; смещения y в 'SCALE_IN_HOME', поскольку это объект "дом"
DB 0 ; смещения x в 'SCALE_IN_HOME', поскольку это объект "дом"
DB $60 ; яркость
DB 0 ; специальный флаг

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

Из-за чрезвычайно оптимизированных подпрограмм рисования это не может (и не должно) быть изменено. Фактически, подпрограммы оптимизированы настолько, что они больше не рисуют в точности то, что нужно.

Существуют две проблемы:

  • Последний вектор в списке обрезается на 2–4 такта раньше необходимого.
  • Между объектами в списке объектов (пока ещё не упоминавшемся) мы всегда обнуляем луч (речь об обнулении интеграторов и установке луча в центр вызовом Reset0Ref — прим. пер.)
    Это обнуление делается не на 100% корректно (только на 98% или типа того :-))
    В действительности, подпрограмма слегка быстровата. Лучу не хватает времени, чтобы достичь нуля (это с учётом того факта, что я уже замедлил подпрограмму на 4 или 5 циклов, однако этого недостаточно…), что приводит к чуть заметной неточности в позиционировании объектов.

Замечания по эмулятору:

Vectrex Frogger нормально работает на эмуляторе (тут речь про DVE — прим. пер.), однако:

Оптимизация делалась с использованием настоящего Vectrex, эмулятор пока не может справиться с некоторыми мелочами, которые необходимы для проверки кода. Они работают нормально такими, какие они сейчас. Но если вы будете сами что-то оптимизировать, нужен Vectrex — вы расстроитесь, если на реальном железе всё заработает совершенно иначе. Особенно это касается точного соблюдения временных интервалов.
Эмулятор делает многие вещи немедленно, в то время как настоящему Vectrex-у требуется на это время. К примеру, это верно для обнуления луча и доступа к любому регистру VIA…
Существует много интересных экспоненциальных функций для просмотра на экране вашего Vectrex-a:-)
(такие как зарядка и разрядка конденсаторов интеграторов).

История

d6b470295aec47c8a028abf246d4bda1.jpg

Моя история программирования под Vectrex и, в особенности, Vectrex Frogger.

Впервые я заинтересовался Vectrex в 1983, но денег чтобы его купить у меня не было. В 1996 я подумал, что могу проверить свои способности программиста и написать эмулятор Vectrex. Однако, в том же направлении думал и Кейт Уилкинс. Он выложил свои исходники в тот момент, когда у меня были лишь первые наброски моего эмулятора, так что я забросил их и стал смотреть в исходники Кейта.
За следующие 2 года я улучшил его эмулятор в некоторых аспектах и думаю, что я теперь, типа, несу за него ответственность.
Несколько месяцев назад я подумал, что было бы прикольно превратить DVE (упомянутый эмулятор — прим.пер.) в среду разработки для Vectrex, поскольку мне казалось, что эмуляция теперь достаточно хороша для этого (хотя теперь я знаю, что это не так и, возможно, никогда не будет так, если только вскоре не выпустят Pentium III 500МГц, причём по приемлимой цене).
Я ещё раз осмотрел Internet на предмет всего, что касается программирования Vectrex чтобы разобраться, что понадобилось бы программисту.
Найдено было довольно мало. Немного дизассемблированных образов игр, впрочем, хорошо документированных. Кое-что от Клэя и Джона, хотя не сказал бы, что хорошо документированное (особенно твоё, Джон). «Настоящей» информации по программированию не было. Так что я решил, что должен разбираться сам.
Как-то, в один из дней, я сел и попытался написать мою первую игру для Vectrex — VPONG. Она была написана примерно за 4–5 часов и для новичка работала на удивление хорошо, хотя здесь не имеется ввиду достижения в плане играбельности (там один игрок и играть довольно сложно). Однако всё, что нужно, там было — звук, вектора, текст, джойстик, кнопки, масштабирование, фейдинг и т.д.
На следующей неделе мне в голову пришла идея Vectrex Frogger, откуда и родился первый прототип спрайтового движка. Тогда я сделал «открытие», что масштабирование большими значениями — плохая идея.

Я размышлял над скоростью моей программы и целью было одно обновление (здесь и далее речь идёт о полной отрисовке одного условного «кадра» — до очередного вызова Wait_Recal. Прим.пер.) как минимум за 70000 тактов, иначе бы я бросил это дело. Я полагал, что если это будет происходить за 70000 тактов, частота обновления будет больше 20 Гц — т.е. с приемлимым мерцанием.
На тот момент у меня уже был Vectrex, однако он ещё не был подключен к параллельному порту моего компьютера (и хорошо, так как если бы я тогда разобрался с соединением, то забросил бы всю затею довольно быстро).

Экспериментируя со значениям масштаба, я узнал, что большие значения приводят к тому, что обновления длятся более 100000 тактов. Дальнейшие проверки выявили, что малые значения работают отлично и я пришёл к, примерно, 50000 тактам (кстати то, что я отвечал за эмулятор, иногда очень помогало. Я написал функцию эмулятора для измерения числа тактов — мне достаточно было набрать 'rc' и она выдавала такты требуемые на обновление:-))
(теперь-то совершенно ясно, что масштаб на самом деле влияет, поскольку это таймер… однако тогда, тоже зная это, я на самом деле не представлял, что это значит).

В любом случае у меня в руках был работающий спрайтовый движок и всякие странные векторные рисунки, двигающиеся по экрану на манер лягушки. Мои подпрограммы работы со спрайтами тогда уже поддерживали анимацию, однако, сделав два или три спрайта, я совсем запарился. Рисовал спрайты карандашом на бумаге и переводил координаты в координаты для Vectrex. Делать такое для кучи спрайтов — полный !$»$»&!»$&=(!!!
К счастью, я нашёл приятеля, ищущего, чем бы заняться и убедил его сделать маленькую программу для рисования векторов, которая был сохраняла координаты таким способом, с которым я бы легко мог работать в своей программе. Джеймс (мой приятель) сделал такую программу, а я занимался основной.
Довольно быстро были скомпонованы подпрограммы работы с джойстиком и определения столкновений.
В первую версию Frogger (начальные уровни) можно было играть через три дня программирования, хотя вы не могли войти в дом и не было реализовано никаких других специальных возможностей.

Повторюсь, я несколько запарился делать спрайты. Над некоторыми подпрограммами я думал даже в автобусе по дороге с работы домой. Подпрограммами, которые я щедро окрестил «подпрограммами морфинга».
У меня была идея, что один список векторов может быть медленно изменён в другой, и что в результате получится классный эффект. Я немного поразмышлял над этим и обнаружил, что в 6809 не было инструкции DIV, а MUL была беззнаковой.
Я написал две таких функции и две (до сих пор существующие) подпрограммы морфинга (setup и do_one_step). Ох, они были очень медленные — за деление и умножение много раз за кадр через самописные подпрограммы пришлось дорого заплатить, скажу я вам. Вскоре я заменил функцию деления оптимизированной версией, которая работала как надо.
До сих пор у меня имелась функция морфинга, которая могла морфить за любое (8 бит) число шагов. Но она всё еще была слишком медленная для практического применения. После многочисленных оптимизаций я пришёл к функции морфинга с фиксированным числом шагов — 16. Это та функция, которая используется во время запуска игры и в некоторых промежуточных эпизодах.

Закончив с этим, я переключился на звук. Меня всегда интересовало, какие ноты были какими, пока наконец я в этом не разобрался (имеется ввиду, относящиеся к BIOS Vectrex-a). Я не могу читать ноты и я кто угодно, но не музыкант.
Поискав в Интернете ноты «The yankee doodle», я не смог ничего найти. Всё кончилось midi файлом. Затем я стал искать midi плейер/редактор. В итоге нашёлся один, который выводил ноты мелодии в читаемой форме. В конце концов, я «написал» музыку yankee doodle для Frogger.
Для меня это было большим подвигом, правда!!!
В начале, я хотел чтобы тему задавал барабан, однако это звучало довольно плохо. В способе, которым мелодия играется сейчас, используются три голоса, со сдвигом октавы в каждом. Звучит по мне так нормально — в любом случае, ошибок я распознать не могу :-(

Теперь настало время добавить пару деталей. Это было то, чего я опасался, поскольку это означало развлечение с уже готовым кодом и внедрением в него новых вещей (конечно, наполненных багами). Новыми деталями были мухи, крокодилы, змеи, девушки, таймер, часы, выдра, плавающие черепашки…
Я запрограммировал их всех с закрытыми глазами, однако мне не понравилось ни минуты этого занятия. Сказать по правде, я это ненавидел. Однако, это были вещи которые я должен был сделать, чтобы Frogger был Frogger’ом. Эти чёртовы игры с идиотскими переменными времени, когда появляются мухи, проверками координат снова и снова, если лягушка близко к голове змеи и т.п.… Терпеть это не мог.
Но, как вы можете видеть, я это сделал. Однако, всё имеет свою цену. Frogger стал медленным — он снова стал близок к тем 70000 тактам.
В то время я как раз получил картридж с ППЗУ для моего Vectrex-a и запустив несколько разных игр, обнаружил, что что медленные игры сильно мерцают :-(. Мне это очень не понравилось. Я пока ещё не мог перенести мой Frogger в Vectrex, поскольку заказанный эмулятор ППЗУ ещё не приехал, но у меня было несколько ППЗУ и я на них посматривал…
В тот момент меня очень беспокоило, как будет выглядеть Frogger на настоящем Vectrex, но я не мог найти никого с программатором.
Тем не менее, я осознал, что Frogger в том виде, как он есть, был слишком медленным, так что я начал подумывать об оптимизации и компромиссах. Первое, что я сделал — удалил с главного экрана подсчёт очков, жизней и индикатор уровня. Можете ли вы поверить, что один вызов этой идиотской функции «print string' занимает более 10000 тактов?!
Далее, я отредактировал все спрайты, урезал их так, чтобы использовалось не больше 20, а только 10–15 векторов, что в результате дало выигрыш порядка 10000 тактов (терпеть не мог редактировать все эти спрайты снова!)
(к слову, черепашка которую вы видите в скроллящемся тексте — предок тех черепашек, которые ползают в самой игре).

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

(Всё ещё жду эмулятор ППЗУ…)

Так как пока что имелось немного свободного времени, я решил, что хорошо было бы реализовать скроллящийся текст. Имея практически готовый Frogger с десятью полосами движущихся спрайтов, я полагал, что сделать одну полосу движущихся букв не будет слишком сложным. И действительно, после рисования всех букв по 3 или 4 раза (программа рисования векторов имела серьёзный баг) и потратив на это некоторое количество нервов, я с этим разделался.
Однако, теперь возникла новая проблема. У Vectrex так «много» ОЗУ! (Один килобайт. Прим. пер.). Иметь информацию об уровнях и спрайтах, необходимость хранить много других переменных в ОЗУ, делать морфинг и ещё иметь скроллящийся текст — это был ещё один вызов. Я изменил распределение ячеек ОЗУ и стал использовать некоторые из них дважды и трижды. Это также было сделано, хотя позднее мне пришла мысль, что в то время программирование под Vectrex было ещё лёгким.

(Всё ещё жду эмулятор ППЗУ…)

Таким образом, я просто ждал и время шло без каких-либо изменений в коде.
За пару недель я не сделал ничего, поскольку окончательная проверка должна была быть сделана на настоящем Vectrex.…

Наконец он приехал, подключён и… не работает :-(
Проверил кое-что… Да, работать-то работает, однако единственный ППЗУ картридж, который у меня был, имел адреса только до 8Кб, а Frogger к тому времени уже превысил этот размер, так что я не смог поиграть в мою игру :-(
(хотя, поиграл почти во все остальные игры, впервые).
Поскольку я сам не разбираюсь в электронике и не знаю, как переделать картридж чтобы он работал с 16Кб (или 32Кб) ППЗУ, я громко воззвал о помощи. Джон услышал мои крики и прислал мне пару ППЗУ картриджей, которые работали отлично (на это ушли ещё две недели).
Возможность иметь доступ к 8Кб для начала была тоже не так уж плоха, так что я смог разбить Frogger на пару частей и проверить их одна за другой. Хотя это не позволяло оценить все возможности игры, для частичной проверки вполне достаточно.

Тут я обнаружил сильный эффект «шатания» (wobble), который эмулятор воспроизвести не мог. После изучения вопроса я понял, что это было связано с таймингами. Шатание было, по большей части, заметно при запуске. И только когда рисовались «неподвижные» векторы, анимированные или движущиеся каким-то образом приобретали что-то типа иммунитета к этому (странно?)
Так или иначе, поимев несколько седых волос, я разобрался, что если обновление кадра занимает более 30000 тактов, неподвижные векторы начинают шататься (как минимум, на моём Vectrex-e). Это касалось не только моей игры, а почти всех игр, которые имели неподвижные векторы где-либо и требовали более 30000 тактов на обновление кадра.
Это было верно даже для Minestorm — встроенной в Vectrex игры. Когда игра запускалась, через несколько секунд, когда первые мины появлялись на экране, точки шатались (естественно на моём Vectrex, т.к. я мог проверить только на нём).

Как только вы узнали, как выглядит этот эффект, то начинаете обнаруживать его довольно часто.
Нечего и говорить, что мне, как перфекционисту, это совсем не нравилось.
Другие игры могут шататься сколько им угодно, но не моя игра. И уж точно не на стартовом экране, который вы видите, когда её запускаете.
Я до сих пор не знаю, что вызывает этот эффект — любая техническая информация по этой теме приветствуется. Я думал, что единственное, что случится если обновление будет занимать слишком много времени, это мерцание. Но даже если я устанавливаю таймер «ожидания рекалибровки» в большее, чем стандартные 30000 тактов, значение, неподвижные векторы ШАТАЮТСЯ! (обычно значение этого таймера инициализируется при включении Vectrex и проверяется в Wait_Recal, чтобы все «кадры» занимали по времени не меньше 30000 тактов. Прим. пер). Я думаю, существует второе «фиксирующее» значение, где-то в районе 50000, однако мерцание при этом получается неприемлимое.

Я изменил код так, чтобы надпись 'VECTREX FROGGER' и всё остальное не появлялись на экране одновременно (этот дурацкий текст также требует около 5000–6000 тактов для отрисовки). Шатание (при старте) ушло в прошлое.
Но теперь я обнаружил ещё один эффект :-((который всё ещё можно видеть на настоящем Vectrex, поскольку я не смог исправить это никаким разумным способом).
Координаты всегда медленно дрейфуют к +,+ (вверх вправо).
Если вы оставите луч в покое на достаточно долгое время, он уйдёт вверх вправо. Рисование большой лягушки в начале игры прохождением по большому списку векторов, с использованием большого масштаба — довольно длительное. Чтобы полностью нарисовать гигантскую лягушку, требуется около 2000–3000 тактов. За это время луч уплывает примерно на полсантиметра. Нужно рекалибровать луч между отрисовками, что решило бы проблему. Но на это нет достаточно времени :-((лягушка состоит из 63 векторов, а позиционирование 63 векторов при данном масштабе невозможно никаким образом, который не был бы критичен по времени!

(63 * $90 = 9000 чистое время движения луча, без накладных расходов
63 * $70 = 7000 чистое время рисования векторов, без накладных расходов
накладные расходы = 1000 тактов. Это уже около 17000 тактов только на лягушку. А я ещё хотел скроллящийся текст, плюс должен быть обсчитан морфинг лягушки!)).

Кроме того, фиксированное смещение не может быть задано, поскольку упомянутый дрейф не постоянен. Он зависит от настроения моего Vectrex (в конце концов, это старая аналоговая машина). Так что, лягушка хорошо выглядит на эмуляторе (если в нём не задан дрейф), но на настоящем Vectrex у неё теряется кончик. Это меня слегка беспокоит, но не может быть легко исправлено!

Не считая этого, кажется, всё работает просто замечательно. Я провёл пару недель не занимаясь Vectrex Frogger’ом (и не особенно скучал по нему — поработав какое-то время, я был рад оставить его, в конце концов, в покое).

В один прекрасный день картриджи Джона наконец прибыли.
Попробовал Frogger… И он заработал!!! (отвратительно :-()

Каким-то образом я избегал этого ранее, но в самой игре также были неподвижные «шатающиеся» векторы. И они ШАТАЛИСЬ :-(В тот момент я был готов всё бросить. Я был рад, что достиг цели в 50000 тактов учитывая, что сначала хотел 70000. Однако, этого оказалось далеко недостаточно. Чтобы избавиться от шатания, не было другого способа чем достичь 30000 тактов (на самом деле, это можно понять из исходника Wait_Recal в BIOS. Прим. пер.)

Некоторое время я экспериментировал, чтобы избавиться от неподвижных векторов, изобретал «движущиеся» дома, пунктирные линии и т.п. Но это практически не помогло. Я СОВСЕМ расстроился. Моя подруга пыталась утешить меня говоря «это не так уж и плохо», однако с моей точки зрения это было плохо. Я хотел сделать полноценную полную игру Vectrex Frogger БЕЗ компромиссов с «шатанием».

Я раздумывал поменять все спрайты снова с целью снизить использование векторов, однако это привело бы к спрайтам как в Atari 2600 — скорее угадываемым, чем различимым. Так что, от этой идеи я отказался.
Тут я впервые попытался использовать регистр DP (который ранее был всегда установлен в D0). Более или менее разумное его использование, вкупе с другими оптимизациями, сэкономило мне от 1000 до 2000 байт кода. Это позволило мне снова перенести некоторые функции BIOS в свой код и оптимизировать их. Я выиграл ещё 10000 тактов и достиг 40000, однако терялся в догадках, что ещё можно соптимизировать.
Шаг за шагом проходя по коду и просматривая *.lst файл создаваемый ассемблером, я выискивал узкие места в критичных участках… занимался этим дня полтора… двигаясь маленькими шажками и экономя по 10–20, иногда даже по 100 тактов, я достиг примерно 37000. Такой долгий путь и каждый следующий шаг был всё сложнее. Нужен перерыв…

Отлично поиграл в Diddy Kong на моей N64 (да, у меня кроме Vectrex есть и другие консоли) и у меня созрела идея…
Почему бы не использовать циклы ожидания в функциях позиционирования луча и рисования векторов для чего-то более полезного, чем просто ожидания когда истечёт время в таймере?
Сказано-сделано — после двух дней нервотрёпки, реализации слишком быстрых функций, появления экспонент на экране Vectrex, странных эффектов и… и… и… я сэкономил ещё 5000 тактов! (это было легко сказать, но нервы попортило изрядно — подробности см. в исходниках).
Так или иначе, я был на 32000 (это в худшем случае — с четырьмя занятыми домами, девушкой, крокодилом, выдрой и змеями на экране…)
После этого я не мог и думать больше обо всех этих оптимизациях. Я редактировал все уровни, менял их, удалял спрайты, переставлял спрайты, выключал всякие фишки, … и т.д., хотя уровни всё еще имели тот характер, который я задал им в начале, несмотря на то, что большая их часть слегка изменилась. В конце концов, я это сделал!!!

КАЖДЫЙ УРОВЕНЬ ОБНОВЛЯЛСЯ МЕНЕЕ ЧЕМ ЗА 30000 ТАКТОВ!!!

Я и правда очень горд своим достижением. И теперь считаю Vectrex Frogger выдающейся программой для Vectrex.

Возможно, это лишь тщеславие с моей стороны, однако я вложил много усилий в Vectrex Frogger и надеюсь, что люди получат удовольствие от игры, а некоторые, быть может, найдут исходники полезными и/или поучительными.

СТОП СТОП СТОП СТОП СТОП СТОП СТОП СТОП СТОП СТОП СТОП СТОП СТОП СТОП СТОП

Ещё один успех в оптимизации! Сегодня я раскрутил пару циклов, что дало выигрыш в скорости около 3000 тактов! Гип-гип ура! Я откатил изменения всех уровней и они всё равно не требуют более 30000 тактов…
Теперь у меня даже есть примерно 1500 тактов в запасе:-)

ОКОНЧАНИЕ ФАЙЛА (Наконец-то)

Год 2016-й. Свежий взгляд на Frogger

7a1e5e3ef38540e581f70bc596271435.gif

… на этой неделе я вплотную приступил к программированию Frogger’a. В моих планах:

  • Музыка и звуковые эффекты
  • Более гладкая графика
  • Вписаться при этом в 30000 тактов
  • Анимация
  • Вид «сверху вниз» (многим не нравится вид сбоку)
  • Режим двух игроков (по-очереди)
  • Вероятно, сохранение рекордов
  • Заставку покрасивше

В последние недели я, время от времени, редактировал в Vide отдельные списки векторов, но только на этой неделе собрал их вместе и перенёс во Frogger (см. гифку).

Менять оригинальные исходники пришлось сильнее, чем я ожидал, поскольку «спрайт» игрока не имел анимации. И въезжание в старые исходники, которым уже исполнилось 18 лет — тоже не упрощало дело.

Так или иначе, как вы можете видеть, анимированная лягушка в ракурсе «сверху вниз» — реализована.

Как я писал ранее (напомню — почти 20 лет назад), одной из моих главных задач было уложиться в 30 тысяч тактов, чтобы избежать колебаний изображения.

757e42d4b53b4f6e908c593c1ed9a55f.jpg

ce104f0a964c4d12b739bbc7ee15d4db.jpg

Поскольку теперь я хотел добавить дополнительных фич, пришлось всматриваться в код снова.

Я менял и подкручивал часть, которая генерировала списки векторов, до тех пор, пока она не стала мегаэффективной. Правда, первый уровень игры (наиболее загруженный) стал отрисовываться всего за 22 тысячи тактов!

Одна проблема — графика была того же типа, что и раньше — «типичной» векторной графикой Vectrex’a, когда частенько на одном из концов линии можно видеть «точку», поскольку луч зажигается слишком рано.

Вуаля!

Посмотрита на нового лягушонка: разве он не прекрасен?

Хотя я и сделал это сам, но всё равно чрезвычайно взволнован тем, как выглядят вектора без точек:-)

Для тех, кто в теме:

  • Сначала я пытался идти обычным путём, используя таймер T1 для scale и ramp, а shift для гашения.
  • Как ни старался, всё равно оставался некоторый «нахлёст».
  • Тогда я попытался использовать вручную PB7 (ramp) и CB2 (blank) — и это действительно помогло. Примерно настолько, насколько можно видеть на фото, однако я не смог придумать, как ускорить этот код так, чтобы он был полезен для Frogger’a.
  • Левое [так в тексте, прим. пер.] изображение получено с помощью кода смешанного типа — я использовал таймер T1 чтобы переключать ramp, но сигнал blank (CB2) устанавливал вручную (т.е. сдвиговый регистр не использовался вообще).
  • Тщательно согласовывая задержки, можно сделать гладкие вектора и соптимизировать всё так, чтобы получить от этого практическую пользу для ситуации с большим количеством данных.
  • Первый уровень Frogger’a перерисовывается с этим кодом (и с играющей музыкой) менее чем за 25 тысяч циклов.

Излишне говорить что, несмотря на то, что Vide очень хороший эмулятор, такого рода эксперименты с точным выдерживанием временных интервалов требуют настоящего Vectrex.

Ещё одна отличная новость в том что, похоже, этот код безглючно работает на всех трёх моих Vectrex’ах.

И наконец, немного видео — первые шаги в качестве уровня как в игровых автоматах (однако, как вы можете заметить, переходы ещё не доделаны).

Продолжение…

Я добавил в код Frogger’a «диспетчер». Из диспетчера вызывается старый код для «MoveTo-InBetween». Диспетчер использует подпрограммы так что, теоретически, такты расходуются на JSR и RTS, однако код выглядит настолько приличнее, что я оставил это как есть (поскольку мне не были особо нужны дополнительные такты).

46a8571c9f844a0593949d4692edbefc.png

Декодирование YM [музыка, прим пер.] сделано через диспетчер — я разбил декодирование на 9 частей, каждая из которых независима от других и они вызываются в разных частях MoveTo. Таким образом, декодирование YM почти не использует дополнительных тактов.

Почти закончил редактирование спрайтов для вида сверху (см. картинку) — спрайты двух разных машин, фургона, гоночной машины и трактора более-менее похожи на оригиналы.

В целом, списки векторов примерно на треть длинее чем те, что были раньше. Хотя, я думаю что сейчас нарисовано примерно на 40 векторов больше, чем в оригинальном Frogger’e для Vectrex. Я считал только нарисованные объекты, однако в некоторых сценах количество векторов имеет значение. Один только трактор состоит из 26 отдельных векторов. Для Vectrex это гигантское число.

Также я несколько улучшил играбельность, немного растянув игровую область. Frogger теперь не так легко выходит за границы. До крайнего правого дома добраться было очень нелегко. Теперь прыжки чуть медленнее (из-за анимации) и это даже ещё сложнее. Тем не менее, добавить анимацию было хорошей идеей. Прыжки ощущаются более похожими на Frogger, чем это было раньше.

Необходимо ещё много тестировать геймплей, сделать звуковые эффекты и другие вещи, однако дела с новой версией идут отлично.

Продолжение продолжается…

Снова позанимался Frogger’ом. Дважды проверил первые 7 уровней и изменил их структуру, чтобы отразить сделанные в спрайтах изменения.

696177dc5a0947fbab4b041bed08839d.jpg

Измерения в Vide показали, что Frogger снова стал чуточку медленнее — 7-й уровень с 4-мя занятыми домами требует около 32 тысяч тактов. Чёрт — я должен буду снова бороться за такты — думал, с этим уже покончено.

Если посмотреть на доступные ресурсы, у меня за плечами теперь гораздо больше, чем в то время, когда я пошёл в университет (посмотрите на картинку справа — это самодельный игровой автомат на базе MAME — догадайтесь, что за Frogger:) [здесь интрига, см. далее — прим. пер.]

Выйдя за лимит в 30 тысяч тактов я уже не рассматриваю возможность добавить отображение информации о рекордах во время игры и это меня беспокоит!

Ниже ещё один пример текущего геймплея Frogger’a. Думаю, я добавлю отсутствующие звуковые эффекты последними — когда дважды проверю и исправлю уровни.