Главная » Хабрахабр » [Перевод] Создание игры для Game Boy

[Перевод] Создание игры для Game Boy

image

Несколько недель назад я решила поработать над игрой для Game Boy, создание которой доставило мне большое удовольствие. Её рабочее название «Aqua and Ashes». Игра имеет открытые исходники и выложена на GitHub.

Как мне пришла в голову эта идея

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

Это был 48-часовой (ну, на самом деле чуть больше) джем, в котором ограничением было создание графики в стиле Game Boy. Мне на почту пришёл еженедельный дайджест itch.io о гейм-джемах, в котором объявлялось начало Mini Jam 4. Темой джема были «времена года» и «пламя». Моей первой вполне логичной реакцией стало желание создать homebrew-игру для Game Boy.

Немного подумав над сюжетом и механиками, которые можно реализовать за 48 часов и вписывающиеся в ограничения темы, я придумала клон новую интерпретацию уровня из игры для SNES 1993 года Tiny Toon Adventures: Buster Busts Loose!, в которой игрок в роли Бастера играет в американский футбол.

Мне всегда нравилось, как создатели этого уровня взяли невероятно сложный вид спорта, избавились от всех хитростей, позиций и стратегических элементов, в результате получив чрезвычайно интересную и лёгкую игру. Очевидно, что такой упрощённый взгляд на американский футбол не заменит вам Madden, так же, как NBA Jam (аналогичная идея: всего 4 игрока на гораздо меньшем поле с более прямолинейным геймплеем, чем в обычной игре) не заменит серию 2K. Но у этой идеи есть определённое очарование, и цифры продаж NBA Jam подтверждают это.

Я задумала взять этот футбольный уровень и переделать его, чтобы он оставался похожим на оригинал и в то же время был свежим. Как всё это относится к моей идее? В основном это сделано из-за ограничений «железа», но в то же время это позволит мне немного поэкспериментировать с более умным ИИ, не ограничивающимся принципом «беги влево и иногда подпрыгивай» из игры на SNES. Во-первых, я урезала игру всего до четырёх игроков — по одному защитнику и нападающему на команду.

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

Сначала я подумала об огненных муравьях и каком-нибудь водном жуке, богомоле и тому подобном, но изучив вопрос, не нашла насекомых, активных зимой, поэтому заменила их на полярных лис и гекконов. Разумеется, при создании двух команд нужны были два животных, которые любят и не любят огонь. В конце концов, это просто игра для Game Boy. Полярные лисы любят снег, гекконы любят лежать на солнце, поэтому всё кажется логичным.

Ну да ладно, всё равно было весело. Кроме того, на случай, если это ещё непонятно, к концу джема игра и близко не была к завершению.

Подготовка Game Boy

Для начала нужно определиться с требованиями. Я решила писать для DMG (внутреннее название модели Game Boy, сокращение от Dot Matrix Game). В основном для того, чтобы соответствовать требованиям гейм-джема, но ещё и потому, что мне так хотелось. Лично у меня никогда не было игр для DMG (хотя и есть несколько игр для Game Boy Color), но я нахожу 2-битную эстетику очень милым и интересным ограничением для экспериментов. Возможно, я добавлю дополнительный цвет для SGB и CGB, но пока над этим не думала.

У CatSkull, опубликовавшего несколько игр Game Boy, например Sheep it Up!, есть в продаже очень дешёвые 32-килобайтные флеш-картриджи, которые идеально мне подойдут. Также я решила использовать картридж с 32K ПЗУ + без ОЗУ, просто на случай, если мне захочется создать физическую копию игры. Сложнее всего будет с графикой, и если всё будет совсем плохо, то я попытаюсь её сжать. Это ещё одно дополнительное ограничение, но я не считаю, что в ближайшее время смогу с такой простой игрой преодолеть объём в 32K.

Однако, честно говоря, из всех ретроконсолей, с которыми мне приходилось работать, Game Boy была самой приятной. Что касается самой работы Game Boy, то тут всё достаточно сложно. Я знала, что лучше всего писать на ASM, как бы мучительно это иногда ни было, потому что «железо» не рассчитано на C, и я не была уверена, что упоминаемый в туториале крутой язык Wiz окажется применимым на долгосрочную перспективу. Я начала с превосходного туториала (по крайней мере, на первое время, потому что он так и не был дописан) автора «AssemblyDigest». Плюс, я делаю это в основном потому, что могу работать с ASM.

Сверяйтесь с коммитом 8c0a4ea

Если по смещению $104 не будет найден логотип Nintendo, а остальная часть заголовка не будет настроена правильно, то оборудование Game Boy предположит, что картридж вставлен неправильно и откажется загружаться. Первое, что нужно было сделать — заставить Game Boy загружаться. Вот, как решила проблему с заголовком я. Решить эту проблему очень просто, потому что об этом написана уже куча туториалов. Здесь нет ничего достойного особого внимания.

Очень просто заставить систему перейти в бесконечный цикл занятости, в котором она снова и снова выполняет одну строку кода. Сложнее будет выполнять осмысленные действия после загрузки. Например: Выполнение кода начинается с метки main (куда указывает переход по адресу $100), поэтому туда нужно вставить какой-нибудь простой код.

main:
.loop: halt jr .loop

и он не делает ровным счётом ничего, кроме как ждёт запуска прерывания, после чего возвращается к метке .loop. (Здесь и далее я буду опускать подробное описание работы ASM. Если вы запутаетесь, то изучите документацию по ассемблеру, которую я использую.) Возможно, вам любопытно, почему я просто не возвращаюсь к метке main. Это сделано потому, что я хочу, чтобы всё до метки .loop было инициализацией программы, а всё, что после неё, происходило каждый кадр. Таким образом мне не придётся обходить в цикле загрузку данных с картриджа и очищать память в каждом кадре.

Используемый мной ассемблерный пакет RGBDS содержит конвертер изображений. Давайте сделаем ещё один шаг. С помощью RGBGFX я преобразовала её в формат Game Boy и воспользовалась ассемблерной командой .incbin, чтобы вставить её после функции main. Так как на этом этапе я пока не нарисовала никаких ресурсов для игры, то решила использовать в качестве тестового битового изображения монохромную кнопку с моей страницы About.

image

Чтобы отобразить её на экране, мне необходимо следующее:

  1. Отключить ЖК-дисплей
  2. Задать палитру
  3. Задать позицию скроллинга
  4. Очистить видеопамять (VRAM)
  5. Загрузить во VRAM тайловую графику
  6. Загрузить во VRAM тайловую карту фона
  7. Снова включить ЖК-дисплей

Отключение ЖК-дисплея

Для начинающих это становится самым серьёзным препятствием. На первом Game Boy невозможно просто в любое время записывать данные во VRAM. Необходимо дождаться момента, когда система ничего не отрисовывает. Имитируя свечение фосфора в старых ЭЛТ-телевизорах, интервал между каждым кадром, когда открыта VRAM, назван Vertical-Blank, или VBlank (в ЭЛТ это импульс для гашения луча кинескопа во время обратного хода кадровой развёртки). (Существует также HBlank между каждой строкой дисплея, но он очень короткий.) Однако можно обойти эту проблему, отключая ЖК-экран, то есть мы можем выполнять запись во VRAM вне зависимости от того, где находится «фосфорный след» ЭЛТ-экрана.

В нём вопрос рассматривается с точки зрения SNES, поэтому не забывайте, что пучка электронов нет, а числа отличаются, но во всём остальном он вполне применим. Если вы запутались, то этот обзор должен многое вам объяснить. По сути, нам нужно задать флаг «FBlank».

То есть нам придётся ждать VBlank. Однако хитрость Game Boy в том, что отключать ЖК-дисплей можно только во время VBlank. Прерывания — это сигналы, которые «железо» Game Boy отправляет центральному процессору. Для этого необходимо использовать прерывания. Game Boy поддерживает пять прерываний, и одно из них запускается при начале VBlank. Если обработчик прерывания задан, то процессор останавливает свою работу и вызывает обработчик.

Первый, и наиболее распространённый — задание обработчика прерываний, который работает так, как я объяснила выше. Обрабатывать прерывания можно двумя разными способами. Обычно он ничего не делает, но имеет побочный эффект выхода из опкода HALT, останавливающего ЦП до возникновения прерывания. Однако мы можем включить определённое прерывание и отключить все обработчики, задав флаг включения этого прерывания и воспользовавшись опкодом di. Поскольку в ОЗУ у нас пока ничего не задано, попытка вызова обработчика VBlank может привести к сбою системы. (Это также происходит и при включенных обработчиках, что позволяет нам выходить из цикла HALT в main.) На случай, если вам интересно, мы со временем создадим и обработчик VBlank, но в нём многое будет зависеть от определённых значений по определённым адресам.

Существуют специальные адреса памяти, непосредственно связанные с различными частями оборудования, в нашем случае — с ЦП, которые позволяют изменять образ его работы. Чтобы задать значения, мы должны отправлять команды аппаратным регистрам Game Boy. Список этих регистров можно найти на страницах, связанных с разделом «Documentation» списка Awesome Game Boy Development. Особо нас интересуют адреса $FFFF (битовое поле включения прерывания), $FF0F (битовое поле активированного, но необработанного прерывания) и $FF40 (управление ЖК-дисплеем).

Для отключения ЖК-дисплея мы включаем только прерывание VBlank, присвоив $FFFF значение $01, выполняем HALT пока не выполнится условие $FF0F == $01, а затем присваиваем биту 7 адреса $FF40 значение 0.

Задание палитры и позиции скроллинга

Это сделать просто. Теперь, когда ЖК-дисплей отключен, нам не нужно волноваться о VBlank. Для задания позиции скроллинга достаточно задать регистрам X и Y значения 0. С палитрой всё немного хитрее. В Game Boy можно присвоить оттенкам с первого по четвёртый графики любой из 4 оттенков серого (или болотно-зелёного, если хотите), что полезно для выполнения переходов и тому подобного. Я задаю в качестве палитры простой градиент, определяемый как список битов %11100100.

Очистка VRAM и загрузка тайловой графики

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

(Также мне понадобится аналог memcpy для копирования данных графики.) Функция memset задаёт указанному фрагменту памяти равенство определённому байту. Для этого мне потребуется функция наподобие memset из C. Это мне будет легко реализовать самой, но в туториале AssemblyDigest уже есть эти функции, поэтому я использую их.

Конкретнее мне нужно скопировать её по адресу $9000, потому что это тайлы, используемые только для фоновой графики. На этом этапе я могу очистить VRAM с помощью memset, записав в неё $00 (хотя в первом коммите использовалось значение $FF, которое тоже подходило), а затем загрузить во VRAM тайловую графику с помощью memcpy. (адреса $8000-$87FF используются только для спрайтовых тайлов, а адреса $8800-$8FFF являются общими для обоих типов.)

Задание тайловой карты

Game Boy имеет один слой фона, разделённый на тайлы 8x8. Сам слой фона занимает около 32x32 тайлов, то есть имеет общий размер 256x256. (Для сравнения: экран консоли имеет разрешение 160x144.) Мне необходимо было строка за строкой вручную указывать тайлы, из которых состоит моё изображение. К счастью, все тайлы были расположены по порядку, поэтому мне всего лишь нужно было заполнять каждую строку значениями с N*11 по N*11 + 10, где N — это номер строки, а остальные 22 элемента тайлов заполнить $FF.

Включение ЖК-дисплея

Здесь нам не нужно ждать VBlank, потому что экран всё равно не включится до VBlank, поэтому я просто снова выполнила запись в регистр управления ЖК-дисплеем. Также я включила слои фона и спрайтов, а также указала правильные адреса тайловой карты и тайловой графики. После этого я получила следующие результаты. Также я снова включила обработчики прерываний с помощью опкода ei.

Добавив по адресу $40 опкод перехода, я могу сделать обработчиком любую нужную мне функцию. На этом этапе, чтобы было ещё интереснее, я написала очень простой обработчик прерывания для VBlank. В данном случае я написала простую функцию, выполняющую скроллинг экрана вверх-влево.

[Дополнение: только что поняла, что GIF зациклен неправильно, он должен постоянно переносить изображение.] Вот готовые результаты.

Пока ничего особо удивительного, но всё равно здорово, что теоретически я могу достать свой старый Game Boy Color и увидеть, как на нём выполняется мой собственный код.

Забавы с листами в клетку

Чтобы отрисовывать что-нибудь на экране, мне, естественно, нужны какие-то спрайты. Изучив PPU (Picture Processing Unit) консоли Game Boy, я решила остановиться на спрайтах размером 8x8 или 8x16. Вероятно, мне понадобится последний вариант, но просто чтобы ощутить размеры, я быстро набросала на клетчатой бумаге скриншот игры в масштабе 1:8.

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

Это особенно справедливо с учётом того, что в игре почти не будет движения по вертикали, за исключением прыжков. Итак, если один квадрат был одним тайлом 8x8, то одного спрайта не будет достаточно, какой бы размер я ни выбрала. Исключение составил хвост лисы, занимающий два спрайта 8x16. Поэтому я решила создавать спрайты из четырёх спрайтов размером 8x16. (Спрайты размером 8x8 быстро бы исчерпали мой лимит, чего не хочется делать на ранних этапах разработки.) После простых подсчётов стало понятно, что две лисы и два геккона займут 20 из 40 спрайтов, то есть можно будет добавить ещё много дополнительных спрайтов.

Ниже представлены грубые эскизы на клетчатой бумаге. Пока мне нужно только отрисовать спрайты. Я планировала ещё сделать спрайты бегущих персонажей, прыгающих персонажей и персонажей, которых хватают противники. У меня есть спрайт ожидания, «думающий» спрайт для выбора, нужно ли сделать пас или бежать, как в игре на SNES… и на этом всё. Остальные я по-прежнему не сделала, надо этим заняться. Но для начала я нарисовала только ожидающий и думающий спрайты, чтобы не усложнять.

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

Не отличишь. (Для протокола: только что снова посмотрев на эти картинки, я осознала, что между гекконами и ящерицами есть огромная разница. Не знаю, что с этим делать, кроме как считать себя глупой...) Думаю, можно догадаться, что источником вдохновения для головы лисы служил Blaze the Cat из серии игр про Соника.

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

И, наконец, я нарисовала экран заставки, потому что для него осталось место на листе клетчатой бумаги.

Да, позы действий ещё далеки от идеала. Полярный лис должен быть более расстроенным и бежать, а геккон выглядеть угрожающе. Защитник-лис на заднем плане — забавная отсылка к арту на коробке Doom.

Оцифровка спрайтов

Затем я приступила к превращению бумажных рисунков в спрайты. Для этого я использовала программу GraphicsGale, которую недавно сделали бесплатной. (Знаю, можно было пользоваться и asesprite, но я предпочитаю GraphicsGale.) Работа над спрайтами оказалась гораздо сложнее, чем я ожидала. Каждый из этих квадратов из показанных выше спрайтов занимает до 4 пикселей в сетке 2x2. И в этих квадратах часто было НАМНОГО больше деталей, чем в 4 пикселях. Поэтому мне пришлось избавиться от множества деталей эскизов. Иногда даже было сложно придерживаться простой формы, потому что нужно было оставить место допустим для глаз или носа. Но мне кажется, что всё выглядит неплохо, даже если спрайт стал совершенно другим.

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

В GraphicsGale также есть удобные функции слоёв и анимаций. Это значит, что я могу анимировать хвост лисы отдельно от её тела. Это очень помогает экономить драгоценное пространство VRAM, потому что мне не нужно дублировать хвост в каждом кадре. Кроме того, это означало, что можно вилять хвостом с переменной скоростью, замедляясь, когда персонаж стоит, и ускоряясь при беге. Однако при этом немного усложняется программирование. Но я всё же возьмусь за эту задачу. Я остановилась на 4 кадрах анимации, потому что этого достаточно.

На GameBoy это допустимо, потому что хотя в спрайте может быть всего три цвета, консоль позволяет задавать две палитры. Можно заметить, что в полярном лисе используются три самых светлых оттенка серого, а в гекконе — три самых тёмных. На этом весь доступный набор палитр закончился, но я не думаю, что мне понадобятся другие. Я сделала так, что для лисы используется палитра 0, а для геккона — палитра 1.

Я не стала заморачиваться его эскизами, потому что планировала, что он будет сплошным цветом или простым геометрическим узором. Также мне нужно было позаботиться о фоне. Экран заставки я тоже пока не оцифровала, потому что не хватило времени.

Загрузка спрайтов в игру

Сверяйтесь с коммитом be99d97.

Оказалось, что в RGBDS для этого есть очень удобная утилита под названием RGBGFX. После того, как каждый отдельный кадр графики персонажей был сохранён, можно было начинать преобразовывать их в формат GameBoy. (Ключ -h задаёт режим тайлов, совместимый с размером 8x16, чтобы преобразование выполнялось сверху вниз, а не слева направо.) Однако он не обеспечивает привязки и не может отслеживать дублирующиеся тайлы, когда каждый кадр является отдельной картинкой. Её можно вызвать командой rgbgfx -h -o output.bin input.png и она создаст совместимый с GameBoy набор тайлов. Но эту проблему мы оставим на потом.

Чтобы держать всё вместе, я создала общий файл «gfxinclude.z80», в котором содержится вся добавляемая графика. После генерации выходных файлов .bin достаточно просто добавить их в ассемблере с помощью incbin "output.bin".

Поэтому я отредактировала файл build.bat, добавив строку for %%f in (gfx/*.png) do rgbds\rgbgfx -h -o gfx/bin/%%f.bin gfx/%%f, которая преобразует каждый файл .png в папке gfx/ в файл bin и сохраняет его в gfx/bin. Тем не менее, было очень скучно каждый раз вручную заново генерировать графику, когда что-нибудь изменится. Это сильно упростило мою жизнь.

У RGBASM есть директива dw `. Для создания графики фона я использовала гораздо более ленивый способ. Так как спрайты фона были очень простыми, оказалось проще копировать и вставлять простой геометрический узор для создания сплошного, полосатого или шахматного узора. За ней следует строка 8 значений от 0 до 4, равных одной строке пиксельных данных. Вот, например, как выглядит тайл земли.

bg_dirt: dw `00110011 dw `00000000 dw `01100110 dw `00000000 dw `11001100 dw `00000000 dw `10011001 dw `00000000

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

bg_grass: dw `12121112 dw `12121212 dw `22112211 dw `11121212 dw `22112211 dw `21212121 dw `12121212 dw `12211222

Рендеринг графики

В памяти GameBoy спрайты хранятся в области под названием OAM, или Object Attribute Memory. Она содержит только атрибуты (направление, палитру и приоритет), а также номер тайла. Мне было достаточно было заполнить эту область памяти, чтобы отобразить спрайты на экране.

Во-первых, необходимо загрузить графику из ПЗУ во VRAM. Хотя здесь есть небольшие особенности. К счастью, для копирования из ПЗУ во VRAM достаточно выполнить memcpy на этапе инициализации программы. GameBoy может рендерить только те тайлы, которые хранятся в особой области памяти, называемой VRAM. (VRAM обычно разделена на области фона и спрайтов, а 128 байт являются общими для них.) При этом выяснилось, что всего 6 спрайтами персонажей и 4 тайлами хвостов я уже заняла четверть выделенной под спрайты области VRAM.

Я начала с того, что перед выполнением вычислений спрайтов дожидалась VBlank, но столкнулась с проблемами, потому что вычисления спрайтов растянулись на всё выделенное VBlank время и их невозможно было закончить. Кроме того, доступ к OAM возможен только во время VBlank. Решение здесь заключается в том, чтобы выполнять запись в отдельную область памяти за пределами VBlank и просто копировать их в OAM во время VBlank.

Выполнив запись в определённый регистр и перейдя к циклу занятости в HiRAM (потому что во время DMA ПЗУ недоступно), можно скопировать данные из ОЗУ в OAM гораздо быстрее, чем с помощью функции memcpy. Как оказалось, у GameBoy есть специальная аппаратная процедура копирования, своего рода DMA (Direct Memory Access, прямой доступ к памяти), которая занимается именно этим. Если интересно, то сочные подробности можно узнать здесь.

Для этого мне нужно было хранить где-то в другом месте состояние объектов. На этом этапе мне оставалось только создать процедуру, определяющую, что же в конце концов будет записано в DMA. Как минимум, требовалось следующее:

  1. Тип (геккон, полярная лиса или переносимый предмет одной из команд)
  2. Направление
  3. Позиция по X
  4. Позиция по Y
  5. Кадр анимации
  6. Таймер анимации

В первом, очень неряшливом решении я проверяла тип объекта, и в зависимости от него выполняла переход к процедуре, поспрайтово отрисовывающей данный тип объекта. Процедура полярной лисы, например, брала позицию по X, в зависимости от направления прибавляла или вычитала 16, добавляла два спрайта хвоста, а затем перемещалась вверх и вниз по основному спрайту.

Левая часть — это отдельные спрайты, шестнадцатеричные числа рядом с ними, сверху вниз — позиция по вертикали и горизонтали, тайл и флаги атрибутов. Вот скриншот того, как выглядел во VRAM спрайт при отрисовке на экране. Справа видно, как всё это выглядело после сборки.

С анимацией хвоста всё было немного сложнее. В первом решении я просто выполняла в каждом кадре инкремент таймера анимации и производила логическое and со значением %11 для получения номера кадра. Затем можно было просто прибавить к первому тайлу хвоста во VRAM 4 * номер кадра (каждый кадр анимации состоит из 4 тайлов), чтобы получить 4 разных кадра, хранящихся во VRAM. Это работало (особенно та часть с поиском тайла хвоста), но хвост вилял безумно быстро, и мне нужно было найти способ его замедлить.

Таким образом, каждый отдельный объект мог выполнять отсчёт своего таймера анимации с любой нужной ему скоростью. Во втором, более качественном решении, я выполняла в каждом кадре инкремент глобального таймера, и когда значение операции and с ним и выбранной мной степенью двойки равнялось 0, выполнялся инкремент таймера объекта. Это сработало отлично и позволило мне замедлить хвост до разумного уровня.

Сложности

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

Для отрисовки одного кадра необходимо было жонглировать достаточно большим количеством регистров и временем ЦП. Это была совершенно неустойчивая система. Поверьте, это был настоящий хаос. Добавить поддержку других кадров было почти невозможно, и даже если бы мне это удалось, поддержка системы была бы очень мучительной. Мне требовалась система, в которой код рендеринга спрайтов был бы обобщённым и прямолинейным, чтобы он не представлял из себя переплетение условий, манипулирования регистрами и математических операторов.

Об этом я расскажу в следующей части статьи. Как мне удалось это исправить?

image


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

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

Древности: невероятная видеокассета

Сейчас, в 2019 году видеокассета потеряла всякую актуальность. Когда год назад я решил оцифровать свои старые записи, и не без труда вывел картинку с видеомагнитофона на современную метровую ЖК-панель, это был опыт, сравнимый с прослушиванием грамофонных пластинок на 78 оборотов. ...

Я прочитал 80 резюме, у меня есть вопросы

У нас не очень простое собеседование. Нужно пройти 3 шага: Прислать резюме, программист его посмотрит, лайкнет если всё хорошо. Рекрутер позвонит, задаст несколько вопросов Встретиться или созвониться с нами. Узнаем, какой вы специалист. Прийти на тестовый день. Познакомиться с командой ...