← Все статьи

MarioKart64JS переходит в 3D: карты из Wii, 3D-титульный экран и облёты камеры после финиша

Mario Kart 64 рисует гонщиков плоскими спрайтами. Нажмите 3 в MarioKart64JS, и каждый карт станет 3D-моделью, включая Lakitu, с настоящими тенями, заново построенным 3D-титульным экраном и камерой, которая после финиша облетает ваш карт. Вот как собирался 3D-режим, в скриншотах.

3D-титульный экран MarioKart64JS: Wario, Bowser, Mario, Peach и Toad в 3D-картах едут на камеру под логотипом Mario Kart 64.
3D-титульный экран. В оригинале это одна плоская картина. Здесь небо, холмы и дорога построены в Three.js, а пятеро гонщиков — 3D-карты, которые едут прямо на камеру.

Прошлый пост про MarioKart64JS был о том, чтобы как можно точнее повторить Mario Kart 64: геометрия трасс, спрайты и музыка из картриджа и новый JavaScript, делающий то же, что делает код оригинала на C. Этот пост — о движении в обратную сторону. Одна клавиша, 3, включает 3D-режим, которого у N64 никогда не было.

Спрайты становятся моделями

Гонщики в Mario Kart 64 — не модели. Каждый из них — это 321 заранее отрендеренный кадр 64×64, и игра выбирает кадр, соответствующий углу камеры. Именно это рисует и MarioKart64JS, и по умолчанию делает так до сих пор.

3D-режим заменяет эти спрайты моделями Standard Kart из Mario Kart Wii, взятыми из экспортов Collada, которые переносит в проект tools/build-wii-karts.py. Всё началось вечером 9 октября с одного теста: в 21:22 клавиша 3 поставила под игрока красный Standard Kart с Mario внутри. К 22:02 такой был у всех восьми гонщиков, с разделением по весовым классам Wii: маленький карт для Toad, средний для Mario, Luigi, Peach и Yoshi и большой для D.K., Wario и Bowser, у каждого своя раскраска.

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

  • Глаза. Каждая текстура глаза — это один глаз. На Wii матрица текстуры удваивает его, а сэмплер зеркально отражает в пару. ColladaLoader из Three.js отбрасывает и то и другое, поэтому один глаз растягивался на всё лицо. kart3d.js возвращает повтор и отражение для каждого персонажа.
  • Шины. Задние шины — это увеличенный меш передней шины. Их размер и положение осей измеряются по собранной модели каждого карта из меню, благодаря чему задние шины стали в 1,29× больше и встали туда, где они находятся на Wii.
  • Сиденья. Каждый гонщик получает позицию сиденья, чтобы сидеть в откинутом кресле, а не парить над ним.

Agent! не может смотреть на картинки, поэтому проверял модели числами: ASCII-видами каждого карта сбоку, сзади и сверху, листами поворотного стола с 12 ракурсами и измеренными зазорами между руками каждого гонщика и рулём.

Гонки в 3D

Гонка в MarioKart64JS в 3D-режиме на Mario Raceway с 3D-картами.
Mario Raceway в 3D-режиме. Каждый карт в гонке — 3D-модель, и у каждого персонажа свой Standard Kart.

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

Тени. У спрайтов под ними плоское пятно тени. 3D-карты отбрасывают на трассу настоящие тени из карты теней. Для этого понадобился трюк: трасса рисуется неосвещёнными материалами, которые не могут принимать тени, поэтому у трассы есть прозрачные копии её поверхностей — ловцы теней, видимые только там, куда падает тень.

Lakitu. Судья становится Lakitu из Mario Kart Wii (src/lakitu3d.js). Его руки позируются через собственные кости модели: одна держит удочку, другая машет флагом. Стартовые огни, таблички кругов и знак неправильного направления висят на крючке удочки, а кадр анимации исходного спрайта по-прежнему задаёт тайминг, так что отсчёт «красный, красный, синий» загорается тогда же, когда и всегда.

У 3D-карта есть ещё и корпус, которого не было у спрайта. При столкновении со стенами это капсула — по кругу над каждой осью, — и он движется и поворачивается как твёрдое тело. У этого были побочные эффекты. На Koopa Troopa Beach передний круг доставал до кромки трамплина раньше центра карта, и тыльная грань кромки сбрасывала карт с прыжка. Исправление проверяет каждую ось относительно земли под ней. Более поздний коммит запретил стенам поворачивать карт игрока при лобовом ударе, потому что в Mario Kart 64 стена отражает движение карта и никогда не меняет направление, куда он смотрит.

3D-титульный экран

Фон титульного экрана Mario Kart 64 — одна плоская картинка 320×240. Небо, холмы, дорога и пятеро гонщиков на ней нарисованы. Нажатие 3 на титульном экране перестраивает его (src/title3d.js): небо, холмы и дорога сделаны в Three.js, а гонщики — те же 3D-карты, что и в гонках.

Расположение повторяет картину. Wario впереди слева, за ним Bowser, Mario впереди справа, за ним Peach, а Toad выезжает из правого поворота. Каждый карт расставлен так, что его рамка на экране попадает примерно в 10 пикселей от рамки его гонщика в оригинальном рисунке. Камера стоит низко на дороге перед ними с широким объективом 64°, а дорога прокручивается под ней, поэтому кажется, что карты едут прямо на вас. Клетчатый флаг, логотип, PUSH START и копирайт — по-прежнему 2D-оверлеи из оригинального титульного экрана.

Меню за ним тоже получили подходящие фоны. Включение 3D на титульном экране заодно переключает на уровень текстур 4×, и гонки начинаются в 3D, пока вы снова не нажмёте 3.

Экран выбора игры MarioKart64JS с фоном для 3D-режима.
Экран выбора игры с фоном для 3D-режима.

Облёты после финиша

Когда вы пересекаете линию, Mario Kart 64 передаёт камеру короткой кинематографической сцене. MarioKart64JS следует для этого коду камеры игры (PLAYER_CINEMATIC_MODE и функции кинематографических планов в декомпиляции): камера разворачивается к передней части карта и смотрит, как он пересекает линию, а затем переключается между планами, пока ваш карт берёт под управление компьютерный гонщик. Последовательность повторяется: облёт носа, обочина, высокий кран, обочина, низкий план сзади, обочина. Одна оговорка из комментариев в исходниках: длительность и дистанции планов подобраны на глаз, а не взяты из таблиц ROM.

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

Облёт после финиша на Mario Raceway: камера впереди 3D-карта Mario смотрит назад на него.
План с носа на Mario Raceway: камера облетает карт сзади наперёд и едет впереди него.
Облёт после финиша на Mario Raceway: высокий план с крана, смотрящий вдоль дороги позади карта.
Высокий план с крана, над картом и позади него, с видом вдоль дороги.
Облёт после финиша на Royal Raceway: камера у обочины, мимо которой проезжает карт.
План с обочины на Royal Raceway. Камера стоит у дороги впереди и держит кадр, пока карт не проедет мимо.

Как делались скриншоты

Каждая картинка в этом посте снята в headless Chrome в разрешении 1280×960. Для кадров титульного экрана и выбора игры на титульном экране нажимается 3, так же как это сделал бы игрок. Гонки идут на автопилоте с сохранённым 3D-режимом, а облёты снимались так: игрок ставился на последний отрезок финального круга, и после финиша кадр сохранялся каждые 0,7 секунды. Agent! выбирал кадры с помощью измерений: положение камеры относительно карта подсказывало, к какому плану относится каждый кадр, а статистика пикселей отсеивала тёмные или малодетальные кадры. Как и в прошлом посте, сам он на них не смотрел.

Попробовать

Клонируйте AgentiLoop/MarioKart64JS, выполните npm install && npm run dev, откройте http://localhost:5173 и нажмите 3 на титульном экране или в гонке. Веб-сборка тоже включает 3D-карты, Lakitu и 3D-титульный экран. Перенесённые модели лежат в public/wii/; tools/build-wii-karts.py и tools/build-wii-lakitu.py — скрипты, которые перенесли их из файлов Collada.

MarioKart64JS — фанатский исследовательский проект для проверки того, насколько далеко ИИ может зайти в копировании игры. Mario Kart 64 и Mario Kart Wii — © Nintendo, и их ассеты принадлежат Nintendo. Проект не связан с Nintendo и не одобрен ею.