DOOM-платформер на чистом JavaScript: даем вторую жизнь старой курсовой
Предыстория: как один проект разделился надвое
Несколько лет назад перед нами с одногруппником встала задача сдать зачёт по C++. Вместо банальных калькуляторов или генераторов паролей мы решили замахнуться на полноценную игру — как раз вовремя подвернулась новость о 30-летнем юбилее легендарной DOOM. Опыта командной разработки у нас не было, поэтому распределение ролей вышло простым: товарищ взял на себя написание псевдо-3D-движка на классическом рейкастинге, а я занялся 2D-платформером. Замысел был эффектным: платформер планировался как мини-игра на аркадном автомате прямо внутри трёхмерного мира, где собранные боеприпасы переносились бы в основную игру.
Проблема возникла не на уровне архитектуры или геймдизайна, а на базовом этапе, который мы благополучно проглядели: мы не согласовали технологический стек. По совету преподавателя я выбрал SFML — библиотека была мне знакома по лабораторным, она предлагает удобную работу со спрайтами, окнами и звуком сразу из коробки. Одногруппник же остановился на SDL, вдохновившись туториалом по рейкастингу: прямой доступ к буферу пикселей позволял рисовать вертикальные полосы стен без лишних слоев абстракции.
Каждое решение по отдельности было полностью оправданным, но вместе они не стыковались. Объединить SDL и SFML в одном приложении технически возможно, но согласование двух независимых циклов событий (event loop), различных подходов к окнам и таймингу превратило бы зачётную работу в полноценную курсовую, на которую оставалось буквально два-три дня. Мы трезво оценили ресурсы и защитили проект как две автономные программы. Преподаватель оценил задумку, резюмировав: «Идея отличная, жаль, что не свели воедино». На тот момент главное было получить зачёт, и мы успокоились.
С тех пор платформер почти три года пылился в архиве: исходник на 900 строк с зашитой прямо в код ASCII-картой и пачка DLL от SFML. Недавно я наткнулся на старый репозиторий напарника и подумал: раз объединять движки на C++ уже неактуально, почему бы не портировать свою часть на чистый JavaScript и Canvas — без сторонних фреймворков и библиотек, как я обычно и практикую. Заодно было интересно взглянуть на старые костыли и баги, которые мы так старательно маскировали на защите.

Ретроспектива: 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 ООО «МТ ФИНАНС»
Сравнение GPT-6 Astra и Opus 5.5 в создании игры «Ну, погоди!» («Электроника ИМ-02»)
Создатель Final Fantasy VII прошел Revelation 46 раз и заявил, что игра масштабнее Rebirth
Blizzard рассказала о главной особенности Diablo V, которая предложит игрокам сразу три сюжетные линии
Появились первые отзывы игроков о Silent Hill Townfall с оценкой производительности на различных устройствах
Бывший сценарист The Witcher 3 назвал главным условием идеального квеста момент, когда игрок откладывает геймпад
Разработчики Stellar Blade едва не отменили игру до того, как PlayStation поддержала корейский проект
Microsoft уволила нарративного директора Gears of War: E-Day спустя четыре дня после ухода игры на золото
Из Steam удаляют бюджетный аналог Rocket League, который временно раздают бесплатно