Как писали игры для приставок: чудеса оптимизации и жёсткий кодинг
medium

Как писали игры для приставок: чудеса оптимизации и жёсткий кодинг

Для всех, кто вырос, проходя восьмибитного Марио.

В 1980-х годах, когда приставки только появлялись, вышла NES — Nintendo Entertainment System. В Россию она попала в виде китайского клона «Денди», «Кенги» и прочих, поэтому если у вас была восьмибитная приставка, то это была NES.

У NES было очень мало памяти и очень медленный по нынешним меркам процессор. Эта статья о том, как сделать крутую игру в очень ограниченных условиях.

Та самая приставка, справа пока ещё две кнопки вместо четырёх.
Для разбора мы взяли видео из канала Morphcat Games — How we fit an NES game into 40 Kilobytes. Там разработчики повторяют опыт геймдизайнеров прошлого и пишут игру для старого железа. Как обычно, если знаете английский, то лучше посмотрите видео целиком, а если нет — держите наш текстовый вариант.

Почему именно 40 килобайт

В 1980-х объём памяти на цифровых устройствах измеряли в килобайтах, потому что ещё не было таких продвинутых её технологий. В большинстве картриджей для восьмибитных приставок было по 40 килобайт памяти. Для сравнения, это в сто тысяч раз меньше, чем на флешке в 4 гигабайта. Даже эта статья весит больше, чем 40 килобайт, так что по современным меркам этого действительно мало.

Два блока памяти в картриджах, 8 и 32 килобайта, в сумме — 40 килобайт.

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

Когда у тебя мало памяти, у тебя мало возможностей: уровни однообразные, враги однообразные, геймплей одинаковый. Но иногда разработчики шли на безумные ухищрения, и в игру получалось запихнуть много «миров», секретов и вариантов геймплея.

Одна из игр, которая взорвала мозг всем в своё время, была та самая «Супер Марио»: в ней было огромное количество разнообразных уровней разной сложности, боссы, секретные уровни и непростой, очень насыщенный геймплей. Были уровни на земле, под землёй, под водой и даже на небе; у героя было несколько режимов — низкий, высокий, в белом комбинезоне. А как вам идея разрушаемого мира? А как вам атаки с воздуха? Короче, «Марио» была безумной, невероятной игрой для своего времени, а всё благодаря оптимизациям.

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

«Супер Марио» — игра, в которую играл каждый, у кого была приставка.

Логика игры

Чтобы в игру было интереснее играть дольше пяти минут, разработчики поставили такие требования:

  1. Это будет платформер — игра, где главному герою нужно бегать и прыгать по платформам, залезать наверх и скакать через препятствия.
  2. Герой сможет ловко двигаться и стрелять по врагам.
  3. Чтобы можно было играть компанией, делают мультиплеер на четырёх человек.

Так как у нас ограничения по памяти, всю игру пишут на Ассемблере — это язык, который работает напрямую с процессором. С одной стороны, код Ассемблера исполняется очень быстро; с другой — в нём работа идёт тупо с перекладыванием данных из одной ячейки процессора в другую. Это примерно как готовить суши, работая с индивидуальными рисинками.

Память распределили так:

  • 8 килобайт на графику,
  • 32 килобайта на сам код игры и хранение данных.

Персонажи

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

Каждая клеточка — это мини-квадратик 8 на 8 пикселей.
В каждом таком квадратике можно что-то нарисовать, но использовать при этом только три цвета.
Если объединить несколько квадратиков в один, получится метаспрайт. В нашем случае — персонаж.
Приставка может использовать одновременно только 4 вида палитры, поэтому у нас получается 4 цветных главных героя и нераскрашенный злодей.
Новое ограничение: на экране одновременно может быть только 8 спрайтов — на большее не хватает памяти. Поэтому для злодея места не остаётся. Можно пойти на хитрость и показывать их быстро-быстро по очереди, но тогда картинка будет мерцать и выглядеть хуже.
Разработчики радикально уменьшили размеры героев и злодея до одного спрайта. Теперь они выглядят более условно, зато помещаются на экран.
Меньше размер героя — больше свободного места для дизайна злодеев, боссов и спецэффектов. Сейчас в табличке собраны все варианты того, как может выглядеть персонаж в игре — и в прыжках, и на бегу.

Большой босс и оптимизация памяти

Если с персонажем всё стало проще, когда его уменьшили, то с боссом всё немного сложнее. Он большой, занимает много места и у него много анимации. Задача — сделать так, чтобы боссы занимали как можно меньше места в памяти.

Большой босс и все его варианты анимации.
Если мы распределим все спрайты по таблице один в один, то у нас быстро закончится место и один кусочек не поместится. Запомните эту картинку как пример неоптимизированной работы с памятью.
Для начала разработчики разбили босса горизонтально на три части, и каждая анимируется отдельно. Видно, что анимация причёски состоит из трёх картинок, каждая из которых немного отличается от остальных.
Если разбить картинки с причёской на отдельные квадратики, то мы заметим, что у них есть повторяющиеся части. Поэтому достаточно нарисовать одну деталь, а потом использовать её во всех трёх вариантах причёски.
Находим оставшиеся одинаковые части и тоже оставляем только одну из них.
А вот тут видно, что это один и тот же спрайт, только в зеркальном виде. Компьютеру несложно нарисовать его отражённым, поэтому тоже можно смело оставить только один из них. С последними треугольничками в каждой картинке — то же самое: это отзеркаленные первые спрайты.
В итоге вся верхняя часть босса вместе с анимацией поместилась в четырёх спрайтах. Это и есть оптимизация: было 16 спрайтов, стало 4.
То же самое делают для средней части. Сейчас она занимает 3 × 8 = 24 спрайта.
А сейчас — 7.
После полной оптимизации босс занимает всего 21 спрайт. Из этих кусочков собирается итоговый вид босса.
Сравните с первоначальным вариантом до оптимизации :)

Карта

Для карт у нас столько же памяти, сколько и на спрайты (то есть мало), поэтому разработчики будут действовать так же:

  • разбивать фон на отдельные ячейки;
  • смотреть, как можно оптимизировать эти ячейки для хранения в памяти;
  • смотреть, можно ли что-то использовать повторно, для экономии памяти.

Главная задача на этом этапе — максимальная экономия видеопамяти. Для этого каждый экран с уровнем игры разбивается не на метаплитки 2 × 2, как в примере выше, с персонажем, а на метаметаплитки или суперплитки — 4 × 4 ячейки. Вот для чего это нужно:

Если разбить просто на квадратики 8 × 8, как в памяти, то вся видимая на экране часть уровня займёт 960 байт. Это почти килобайт, и это очень много.
Разбивают уровень на метаплитки 16 × 16. Теперь на одну карту нужно 240 байт, чтобы пометить каждую такую метаплитку, но это всё равно много. Уменьшаем дальше.
Теперь уровень делится на супербольшие плитки по 16 ячеек в каждой. В итоге для того, чтобы пронумеровать каждую такую суперплитку, нужно всего 60 байт. Уже можно работать.
Вот так собираются метаплитки — из четырёх ячеек в памяти.
Теперь можно собирать такие метаплитки в виртуальные наборы и каждой присвоить какой-то код. Но и это ещё не всё.
Вот теперь получилась суперплитка. Это готовый блок для уровня, и чтобы собрать такое, нужно совсем немного памяти.
Коллекция виртуальных суперплиток. С ними можно сделать любые уровни и фоны.

Рисуем карты (и оптимизируем их)

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

Первый вариант — уменьшить количество памяти для отрисовки карты: сделать их симметричными, что даст нам 30 байт вместо 60. Мы рисуем одну половинку карты, а потом просто отзеркаливаем её. Сравним с картой, которую мы бы хотели получить:

Вроде всё на месте, а выглядит плохо — сразу видна симметрия и доступ наверх закрыт блоками.

И вот тут разработчики делают очередной хитрый ход, который даст им немного дополнительной памяти для графики. Смотрите:

  1. Они дают для хранения одной суперплитки один байт.
  2. Считают по картинке, сколько получилось суперплиток в прошлом разделе — 96.
  3. Так как программисты начинают считать с нуля, то самое большое число, которое получится, — 95, а это 1011111 в двоичной системе счисления.
  4. В этом длинном числе всего 7 цифр, а в байте их 8, поэтому остаётся один лишний бит из каждого числа.
  5. 4 суперплитки дадут 4 бита.
  6. Эти 4 бита можно использовать, чтобы сдвинуть по кругу ряд с зеркальным отражением и получить как бы новый ряд, уже без видимой симметрии.

Если вы не знаете, что такое двоичная система счисления, — почитайте нашу статью об этом, а потом вернитесь сюда.

4 суперплитки дают 4 бита. Посмотрим, что можно с ними сделать.
Сначала делают симметричный уровень…
А затем сдвигают верхнюю полосу вправо по кругу. 1100 — это 12 в десятичной системе счисления, именно столько сдвигов вправо нужно сделать, чтобы получилось как на картинке.
То же самое делают с третьей строкой и получают уже приемлемое начало уровня.

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

Добавляем в игру сложный режим

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

Чтобы и этот режим поместился в оставшуюся память, снова используют трюки с памятью и графикой.

Чтобы игрок понял, что начались трудности, просто меняют палитру. Это почти столько же по памяти, но выглядит сложнее.
Уровень можно поменять так: берут исходную картинку, накладывают сверху новые детали и получают сложную локацию. В среднем на это уходит по 7 байт на каждый экран.

В чем оптимизация, брат

  1. Уменьшили персонажей: маленькие спрайты — меньше памяти.
  2. Оптимизировали графику: вместо больших повторяющихся картинок — много маленьких повторяющихся картинок.
  3. Оптимизировали архитектуру уровней: сделали их симметричными, но сдвинули ряды по кругу влево-вправо, чтобы добавить разнообразия.
  4. Для дополнительного разнообразия ввели новые цветовые палитры.
  5. Более сложные уровни не хранили в памяти целиком. Для них хранились лишь дополнительные ловушки и враги. А на фоне лежали те же старые уровни.
  6. И всё это на чистом Ассемблере.

Обложка:

Даня Берковский

Корректор:

Ирина Михеева

Вёрстка:

Маша Климентьева

Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию
Вам может быть интересно
Большой разбор: как ИИ играет в змейку
Большой разбор: как ИИ играет в змейку

Современные технологии против игры прошлого века.

medium
Большой разбор: ИИ научился играть в динозаврика из Chrome
Большой разбор: ИИ научился играть в динозаврика из Chrome

Тот редкий случай, когда хочешь остаться без интернета.

medium
Функции. Зачем они нужны и как их писать, чтобы вас уважали программисты
Функции. Зачем они нужны и как их писать, чтобы вас уважали программисты

Сложная важная статья для тех, кто хочет стать крутым программистом.

medium
Как разработчику наконец-то зафиналить зависшую задачу
Как разработчику наконец-то зафиналить зависшую задачу

Когда очень надо упереться

easy
Одностраничник, лендинг, сайт и портал — в чём разница
Одностраничник, лендинг, сайт и портал — в чём разница

Разница — в структуре

easy
Тестировщик: кто это такой, что он делает и как им стать
Тестировщик: кто это такой, что он делает и как им стать

Что нужно уметь и сколько можно заработать поиском ошибок

easy
Что такое RAD-разработка
Что такое RAD-разработка

Быстро-быстро пилим новую версию и сразу проверяем её в работе

easy
Вас догонят. Как реклама ловит нас в интернете
Вас догонят. Как реклама ловит нас в интернете

Конспект подкаста «Запуск завтра»

easy
Вакансия на 210 тысяч: что такое .NET и зачем он нужен
Вакансия на 210 тысяч: что такое .NET и зачем он нужен

Для тех, кто любит программировать и точка

easy
medium