В 1980-х годах, когда приставки только появлялись, вышла NES — Nintendo Entertainment System. В Россию она попала в виде китайского клона «Денди», «Кенги» и прочих, поэтому если у вас была восьмибитная приставка, то это была NES.
У NES было очень мало памяти и очень медленный по нынешним меркам процессор. Эта статья о том, как сделать крутую игру в очень ограниченных условиях.
Для разбора мы взяли видео из канала Morphcat Games — How we fit an NES game into 40 Kilobytes. Там разработчики повторяют опыт геймдизайнеров прошлого и пишут игру для старого железа. Как обычно, если знаете английский, то лучше посмотрите видео целиком, а если нет — держите наш текстовый вариант.
Почему именно 40 килобайт
В 1980-х объём памяти на цифровых устройствах измеряли в килобайтах, потому что ещё не было таких продвинутых её технологий. В большинстве картриджей для восьмибитных приставок было по 40 килобайт памяти. Для сравнения, это в сто тысяч раз меньше, чем на флешке в 4 гигабайта. Даже эта статья весит больше, чем 40 килобайт, так что по современным меркам этого действительно мало.
Чтобы использовать больше памяти, нужно было идти на всякие ухищрения — ставить расширители памяти или отдельные блоки для работы с несколькими картриджами одновременно. Так как почти ни у кого из геймеров такой роскоши не было, то разработчики использовали только 40 доступных килобайт.
Когда у тебя мало памяти, у тебя мало возможностей: уровни однообразные, враги однообразные, геймплей одинаковый. Но иногда разработчики шли на безумные ухищрения, и в игру получалось запихнуть много «миров», секретов и вариантов геймплея.
Одна из игр, которая взорвала мозг всем в своё время, была та самая «Супер Марио»: в ней было огромное количество разнообразных уровней разной сложности, боссы, секретные уровни и непростой, очень насыщенный геймплей. Были уровни на земле, под землёй, под водой и даже на небе; у героя было несколько режимов — низкий, высокий, в белом комбинезоне. А как вам идея разрушаемого мира? А как вам атаки с воздуха? Короче, «Марио» была безумной, невероятной игрой для своего времени, а всё благодаря оптимизациям.
В видеоролике разработчики поставили себе похожую цель: сделать насыщенную, разнообразную игру с большим количеством уровней, миров и настроений. И они показали, как этого добиться с помощью жёстких оптимизаций.
Логика игры
Чтобы в игру было интереснее играть дольше пяти минут, разработчики поставили такие требования:
- Это будет платформер — игра, где главному герою нужно бегать и прыгать по платформам, залезать наверх и скакать через препятствия.
- Герой сможет ловко двигаться и стрелять по врагам.
- Чтобы можно было играть компанией, делают мультиплеер на четырёх человек.
Так как у нас ограничения по памяти, всю игру пишут на Ассемблере — это язык, который работает напрямую с процессором. С одной стороны, код Ассемблера исполняется очень быстро; с другой — в нём работа идёт тупо с перекладыванием данных из одной ячейки процессора в другую. Это примерно как готовить суши, работая с индивидуальными рисинками.
Память распределили так:
- 8 килобайт на графику,
- 32 килобайта на сам код игры и хранение данных.
Персонажи
В игре есть два вида графики: статичный фон и движущиеся предметы — игроки, противники, боссы и выстрелы. Всё, что движется, называется спрайтами. Разработчики делят всю графическую память на две части — одну под спрайты, вторую под фон:
Большой босс и оптимизация памяти
Если с персонажем всё стало проще, когда его уменьшили, то с боссом всё немного сложнее. Он большой, занимает много места и у него много анимации. Задача — сделать так, чтобы боссы занимали как можно меньше места в памяти.
Карта
Для карт у нас столько же памяти, сколько и на спрайты (то есть мало), поэтому разработчики будут действовать так же:
- разбивать фон на отдельные ячейки;
- смотреть, как можно оптимизировать эти ячейки для хранения в памяти;
- смотреть, можно ли что-то использовать повторно, для экономии памяти.
Главная задача на этом этапе — максимальная экономия видеопамяти. Для этого каждый экран с уровнем игры разбивается не на метаплитки 2 × 2, как в примере выше, с персонажем, а на метаметаплитки или суперплитки — 4 × 4 ячейки. Вот для чего это нужно:
Рисуем карты (и оптимизируем их)
Даже 60 байт на экран, которые у нас получились, — это всё равно очень много, ведь нужно сделать много разных карт, написать логику поведения персонажей и сделать меню, заставки и титры. Каждый байт на счету.
Первый вариант — уменьшить количество памяти для отрисовки карты: сделать их симметричными, что даст нам 30 байт вместо 60. Мы рисуем одну половинку карты, а потом просто отзеркаливаем её. Сравним с картой, которую мы бы хотели получить:
И вот тут разработчики делают очередной хитрый ход, который даст им немного дополнительной памяти для графики. Смотрите:
- Они дают для хранения одной суперплитки один байт.
- Считают по картинке, сколько получилось суперплиток в прошлом разделе — 96.
- Так как программисты начинают считать с нуля, то самое большое число, которое получится, — 95, а это 1011111 в двоичной системе счисления.
- В этом длинном числе всего 7 цифр, а в байте их 8, поэтому остаётся один лишний бит из каждого числа.
- 4 суперплитки дадут 4 бита.
- Эти 4 бита можно использовать, чтобы сдвинуть по кругу ряд с зеркальным отражением и получить как бы новый ряд, уже без видимой симметрии.
Если вы не знаете, что такое двоичная система счисления, — почитайте нашу статью об этом, а потом вернитесь сюда.
Действуя таким образом, разработчики могут менять уровни до неузнаваемости, не затрачивая при этом вообще лишней памяти. Помним, что наш экран — это ещё не весь уровень, сверху нужно нарисовать ещё много раз по столько же.
Добавляем в игру сложный режим
Когда игрок прошёл все уровни, ему можно дать возможность поиграть на повышенной сложности: он уже знает всю игру и может пройти более сложные ловушки и боссов. Например, сложный режим может отличаться дизайном уровней и поведением противников.
Чтобы и этот режим поместился в оставшуюся память, снова используют трюки с памятью и графикой.
В чем оптимизация, брат
- Уменьшили персонажей: маленькие спрайты — меньше памяти.
- Оптимизировали графику: вместо больших повторяющихся картинок — много маленьких повторяющихся картинок.
- Оптимизировали архитектуру уровней: сделали их симметричными, но сдвинули ряды по кругу влево-вправо, чтобы добавить разнообразия.
- Для дополнительного разнообразия ввели новые цветовые палитры.
- Более сложные уровни не хранили в памяти целиком. Для них хранились лишь дополнительные ловушки и враги. А на фоне лежали те же старые уровни.
- И всё это на чистом Ассемблере.