DOOM-платформер на чистом JavaScript: даем вторую жизнь старой курсовой

Прокомментировать Просмотры: 5

Предыстория: как один проект разделился надвое

Несколько лет назад перед нами с одногруппником встала задача сдать зачёт по C++. Вместо банальных калькуляторов или генераторов паролей мы решили замахнуться на полноценную игру — как раз вовремя подвернулась новость о 30-летнем юбилее легендарной DOOM. Опыта командной разработки у нас не было, поэтому распределение ролей вышло простым: товарищ взял на себя написание псевдо-3D-движка на классическом рейкастинге, а я занялся 2D-платформером. Замысел был эффектным: платформер планировался как мини-игра на аркадном автомате прямо внутри трёхмерного мира, где собранные боеприпасы переносились бы в основную игру.

Проблема возникла не на уровне архитектуры или геймдизайна, а на базовом этапе, который мы благополучно проглядели: мы не согласовали технологический стек. По совету преподавателя я выбрал SFML — библиотека была мне знакома по лабораторным, она предлагает удобную работу со спрайтами, окнами и звуком сразу из коробки. Одногруппник же остановился на SDL, вдохновившись туториалом по рейкастингу: прямой доступ к буферу пикселей позволял рисовать вертикальные полосы стен без лишних слоев абстракции.

Каждое решение по отдельности было полностью оправданным, но вместе они не стыковались. Объединить SDL и SFML в одном приложении технически возможно, но согласование двух независимых циклов событий (event loop), различных подходов к окнам и таймингу превратило бы зачётную работу в полноценную курсовую, на которую оставалось буквально два-три дня. Мы трезво оценили ресурсы и защитили проект как две автономные программы. Преподаватель оценил задумку, резюмировав: «Идея отличная, жаль, что не свели воедино». На тот момент главное было получить зачёт, и мы успокоились.

С тех пор платформер почти три года пылился в архиве: исходник на 900 строк с зашитой прямо в код ASCII-картой и пачка DLL от SFML. Недавно я наткнулся на старый репозиторий напарника и подумал: раз объединять движки на C++ уже неактуально, почему бы не портировать свою часть на чистый JavaScript и Canvas — без сторонних фреймворков и библиотек, как я обычно и практикую. Заодно было интересно взглянуть на старые костыли и баги, которые мы так старательно маскировали на защите.


Wolfenstein 3D engine
Wolfenstein 3D engine

Ретроспектива: DOOM 1993 года и студенческий рейкастинг

Релиз DOOM от студии id Software состоялся 10 декабря 1993 года. В крошечной команде Джон Кармак отвечал за архитектуру движка, Джон Ромеро проектировал уровни, Эдриан Кармак рисовал графику, а Бобби Принс писал саундтрек. После успеха Wolfenstein 3D новая игра должна была доказать, что персональные компьютеры того времени способны отображать реалистичное трехмерное окружение в реальном времени.

Стоит оговориться: фраза «мы делали свой DOOM» — лишь красивое обобщение. То, что реально написать за семестр силами двух студентов, функционально куда ближе к Wolfenstein 3D, а пропасть между ними как раз и составляет главное технологическое достижение id Software.

Описание изображения
Рейкастинг

Wolfenstein 3D генерирует картинку с помощью рейкастинга: из каждого столбца экрана выпускается луч по регулярной ортогональной сетке до ближайшей стены, а расстояние определяет высоту экранной полосы. Метод гениален своей простотой и экономичностью, но имеет жесткие рамки: стены строго под 90 градусов, фиксированная высота уровня, никаких лестниц и многоярусных пространств.

В DOOM эти ограничения обошли принципиально иной структурой — BSP-деревьями (Binary Space Partitioning). Локация еще на этапе компиляции карты разбивается на выпуклые секторы, поэтому движок отрисовывает полигоны в строгом порядке от ближних к дальним без покадровой сортировки. Это позволило строить стены под произвольными углами, варьировать высоту потолков и полов в смежных секторах и создавать убедительную иллюзию сложной архитектуры, хотя полы оставались плоскими, а настоящая многоэтажность появилась лишь к Doom 3.

Наш студенческий рейкастинг был скромнее, но все же интереснее, чем мне запомнилось. Заглянув в репозиторий товарища, я нашел описание реализованной механики: два отдельных бинарника — 2D-часть на SFML и псевдо-3D на SDL — связывались через общий файл. Платформер записывал количество собранных патронов в `bullets.txt`, а 2.5D-игра считывала его при старте и умножала запас на количество сохраненных жизней. Это был рабочий обмен данными на уровне файловой системы вместо монолитного приложения.

Собрать SDL и SFML в едином коде это не помогло бы — проблемы с event loop и таймингами оставались, — но стало ясно, что идея интеграции была реализована на практике, пусть и через промежуточный файл.

Часть 1. Анатомия старого кода

В исходнике Source.cpp содержалась матрица символов 67 на 150, описывающая тайловую структуру карты.

вот, как выглядела карта
вот, как выглядела карта

Символ «z» кодировал стену, «k» — платформу, «b/B» — монеты, «c» — интерактивный блок с сердечком, а «p» и «9» — порталы между секциями. Проверка коллизий шла банальным перебором тайлов под прямоугольником героя — наивный, но безотказный способ, который я без изменений перенес в JS. Сама сеточная концепция ближе к структуре Wolfenstein 3D, где уровень строго привязан к матрице блоков. В DOOM от этого отказались: карты строились вручную из произвольных полигонов в редакторе DoomEd.

Спрайты считывались из общего атласа hero1.png через структуру `IntRect`. В SFML отрицательная ширина автоматически зеркалит изображение без дополнительного кода. В Canvas такой прием не работает (отрицательные размеры в drawImage вызывают ошибку), поэтому горизонтальное отражение пришлось реализовывать программно. Примечательно, что в оригинальном DOOM для экономии дискового пространства спрайты врагов также хранились лишь для части ракурсов, а недостающие углы зеркалились движком на лету.

Часть 2. Поведение и характер врагов

В старой версии противники вели себя примитивно: двигались по прямой и разворачивались при контакте со стеной. В DOOM для каждого монстра применялся полноценный конечный автомат (состояния патрулирования, преследования, атаки, получения урона и гибели): Lost Soul стремительно таранил игрока, а Cacodemon парил в воздухе и стрелял снарядами. Для JS-порта я ускорил мобов на 50%, добавил им полноценную гравитацию и прописал дифференцированные модели поведения.

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

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

Часть 3. Процедурная генерация вместо статичной карты

Переписывая проект, я решил отказаться от жестко закодированной матрицы символов. Здесь я пошел вразрез с подходом id Software: в оригинальном DOOM уровни создавались исключительно вручную, я же написал процедурный генератор. Перепады высот грунта ограничены одним тайлом, что исключает появление непроходимых участков, а платформы с наградами генерируются на безопасном расстоянии друг от друга, не перегружая верхнюю часть уровня.


Итог

От старой кодовой базы на C++/SFML в новой реализации не осталось ничего, однако исходная логика, тайловая сетка и концепция мини-игры сохранены полностью — теперь без зависимостей от внешних DLL и платформенных ограничений.

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

Главные выводы

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

Опробовать веб-версию можно здесь.

P.S. Если у вас есть предложения по оптимизации механик или вы обнаружили баг — делитесь своими мыслями в комментариях.

© 2026 ООО «МТ ФИНАНС»

 

Источник

Поделиться:

Похожие статьи

Поиск по играм, новостям и статьям…

Введите не менее двух символов

Введите не менее двух символов