на каком языке написан doom

Анализ исходного кода движка Doom: рендеринг

на каком языке написан doom. Смотреть фото на каком языке написан doom. Смотреть картинку на каком языке написан doom. Картинка про на каком языке написан doom. Фото на каком языке написан doom

От экрана дизайнера к экрану игрока

Карты разрабатывались дизайнером уровней в 2D с помощью редактора Doom Editor (DoomED). LINEDEFS описывали замкнутые секторы (SECTORS в исходном коде), а третье измерение (высота) указывалась посекторно. Первый уровень Doom E1M1 выглядит так:

на каком языке написан doom. Смотреть фото на каком языке написан doom. Смотреть картинку на каком языке написан doom. Картинка про на каком языке написан doom. Фото на каком языке написан doom

После завершения работы над картой она нарезается методом двоичного разбиения пространства (Binary Space Partitioning, BSP). LINEDEF рекурсивно выбирались и их плоскости превращались в секущие плоскости. То есть LINEDEF разрезались на сегменты (SEGS) до тех пор, пока не оставались только выпуклые подсектора (SSECTOR в коде).

Интересный факт: И DoomED, и iBSP писались на… Objective-C на рабочих станциях NextStep. Пятнадцать лет спустя тот же язык почти в той же операционной системе выполняет игру на мобильном устройстве! [прим. пер.: в 2010 году Doom вышел на iPhone] Я немного поработал веб-археологом и мне удалось найти исходный код idbsp. На него стоит посмотреть.

Ниже представлен пример рекурсивного разделения карты первого уровня.

Уровень рекурсии 1

на каком языке написан doom. Смотреть фото на каком языке написан doom. Смотреть картинку на каком языке написан doom. Картинка про на каком языке написан doom. Фото на каком языке написан doomна каком языке написан doom. Смотреть фото на каком языке написан doom. Смотреть картинку на каком языке написан doom. Картинка про на каком языке написан doom. Фото на каком языке написан doom

Синим отмечена выбранная стена, превращённая в секущую плоскость (красная). Секущая плоскость выбиралась таким образом, чтобы сбалансировать BSP-дерево, а также для ограничения количества создаваемых SEGS. Зелёные ограничивающие прямоугольники использовались позже для отбрасывания целых фрагментов карты.

Уровень рекурсии 2 (только для правого подпространства)

на каком языке написан doom. Смотреть фото на каком языке написан doom. Смотреть картинку на каком языке написан doom. Картинка про на каком языке написан doom. Фото на каком языке написан doomна каком языке написан doom. Смотреть фото на каком языке написан doom. Смотреть картинку на каком языке написан doom. Картинка про на каком языке написан doom. Фото на каком языке написан doom

В результате секторы SECTORS разделялись на выпуклые подсекторы (обозначаемые как SSECTORS), а LINEDEFS разрезались на сегменты (обозначаемые как SEGS):

на каком языке написан doom. Смотреть фото на каком языке написан doom. Смотреть картинку на каком языке написан doom. Картинка про на каком языке написан doom. Фото на каком языке написан doom

Процесс работы в целом

Вот как выглядит главный метод рендеринга ( R_RenderPlayerView ):

Здесь выполняется четыре операции:

Сортировка двоичного разбиения пространства

Два примера с E1M1 (первой картой Doom) и BSP выглядят следующим образом:

на каком языке написан doom. Смотреть фото на каком языке написан doom. Смотреть картинку на каком языке написан doom. Картинка про на каком языке написан doom. Фото на каком языке написан doom

Обход BSP-дерева всегда начинается с корневого узла с сортировкой обоих подпространств. Рекурсия выполняется для обоих дочерних узлов.

Пример 1: Игрок (зелёная точка) смотрит сквозь окно из точки p=(2300,1900):

на каком языке написан doom. Смотреть фото на каком языке написан doom. Смотреть картинку на каком языке написан doom. Картинка про на каком языке написан doom. Фото на каком языке написан doomна каком языке написан doom. Смотреть фото на каком языке написан doom. Смотреть картинку на каком языке написан doom. Картинка про на каком языке написан doom. Фото на каком языке написан doom

Пример 2: Игрок (зелёная точка) смотрит с секретного балкона в точке p=(5040, 2400):

на каком языке написан doom. Смотреть фото на каком языке написан doom. Смотреть картинку на каком языке написан doom. Картинка про на каком языке написан doom. Фото на каком языке написан doomна каком языке написан doom. Смотреть фото на каком языке написан doom. Смотреть картинку на каком языке написан doom. Картинка про на каком языке написан doom. Фото на каком языке написан doom

BSP-деревья позволили сортировать SEGS из любой точки карты с постоянной скоростью, вне зависимости от позиции игрока. Ценой за это стали одна операция умножения и одна операция суммирования для каждой плоскости. Кроме того, благодаря тестированию ограничивающими прямоугольниками отбрасываются крупные части карты.

Примечание: Не сразу очевидно, но BSP сортирует все сегменты SEGS вокруг игрока, даже те, на которые он не смотрит. При использовании BSP необходимо применение отсечения по пирамиде видимости.

Стены

При сортировке BSP стен (SEGS) с ближней до дальней, рендерятся только ближайшие 256 стен. Две вершины каждого SEGS преобразуются в два угла (относительно позиции игрока).

на каком языке написан doom. Смотреть фото на каком языке написан doom. Смотреть картинку на каком языке написан doom. Картинка про на каком языке написан doom. Фото на каком языке написан doom

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

Интересный факт: Поскольку стены рендерились как вертикальные колонны, текстуры стен хранились в памяти повёрнутыми на 90 градусов влево. Этот трюк позволял полностью использовать функцию предварительного кэширования центрального процессора: процесс считывания тексела стены из ОЗУ также предварительно заполнял кэш ЦП восемью соседними текселами с каждой стороны. Поскольку последующие данные чтения уже находились в кэше ОЗУ, достигалось значительное снижение латентности считывания. Подробнее о предварительном кэшировании и выравнивании данных в памяти можно почитать в книге «The Art of Assembly Language programming» (раздел 3.2.4 Cache Memory).

Плоские поверхности (пол и потолок), или печально известные visplanes

При отрисовке столбцов стен верхние и нижние координаты экранного пространства использовались для генерирования «visplanes», областей в экранном пространстве (не обязательно непрерывных горизонтально). Вот как объявляется visplane_t в движке Doom.

Первая часть структуры хранит информацию о «материале», ( height, picnum, lightlevel ). Четыре последних члена определяют покрываемую зону экранного пространства.

Если два подсектора имеют одинаковый материал (высоту, текстуру и уровень освещённости), то движок Doom пытался слить их вместе, но из-за ограничений структуры visplante_t это было не всегда возможно.

Для всей ширины экрана visplane может хранить местоположение столбца пикселей (поскольку visplanes получаются проецированием стен на экран, они создаются как столбцы пикселей).

Вот три основные visplanes начального экрана:

на каком языке написан doom. Смотреть фото на каком языке написан doom. Смотреть картинку на каком языке написан doom. Картинка про на каком языке написан doom. Фото на каком языке написан doom

Зелёная плоскость особенно интересна: она демонстрирует, что visplane_t может хранить прерывистые (но только в горизонтальном направлении) области. Поскольку столбец непрерывен, visplane может хранить его. Это ограничение проявляется в движке: некоторые подсекторы можно слить и рендерить с помощью одной visplane, но если между ними есть что-нибудь по вертикали, то слияние невозможно.

Вот скриншот и соответствующее видео, показывающие фрагментацию visplane.

на каком языке написан doom. Смотреть фото на каком языке написан doom. Смотреть картинку на каком языке написан doom. Картинка про на каком языке написан doom. Фото на каком языке написан doom

Интересный факт: Жёстко заданный предел visplanes ( MAXVISPLANES 128) был большой головной болью для моддеров, потому что игра вываливалась и возвращалась в DOS. Могут возникнуть две проблемы:

Предметы и прозрачные стены

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

Примечание: В этом видео показан один из наихудших сценариев, когда некоторые пиксели приходится перерисовывать три раза.

Профайлинг

Загрузка Chocolate Doom в Instruments под Mac OS X позволила выполнить кое-какой профайлинг:

Похоже, что порт [на iPhone] довольно точно соответствует «ванильному» Doom: бóльшую часть времени выполняется отрисовка стен ( R_DrawColumn ), потолка/пола ( R_DrawSpan ) и предметов ( R_DrawMaskedColumn ). Кроме отрисовки я заметил высокие затраты ресурсов на интерполяцию стен ( R_RenderSegLoop ) и преобразование visplane из столбцов в строки пикселей ( R_MakeSpans ). Затем, наконец, дело доходит до AI ( R_MobjThinker ) и обход BSP-дерева ( R_RenderBSPNode ).

на каком языке написан doom. Смотреть фото на каком языке написан doom. Смотреть картинку на каком языке написан doom. Картинка про на каком языке написан doom. Фото на каком языке написан doom

С помощью инвертированного дерева вызовов можно увидеть, что бóльшая часть работы и в самом деле заключается в обходе BSP-дерева, рендеринге стен и генерировании visplanes: R_RenderBSPNode (второй столбец — процент потраченного времени).

на каком языке написан doom. Смотреть фото на каком языке написан doom. Смотреть картинку на каком языке написан doom. Картинка про на каком языке написан doom. Фото на каком языке написан doom

Всё вместе

И вот, наконец, видео генерирования легендарного первого экрана, в котором можно увидеть по порядку:

Интересные факты

Источник

Движок Doom

Содержание

Улучшения движка (по сравнению с Wolfenstein 3D )

Ограничения

Однако у движка были ограничения:

Технические особенности

Все вычисления проводятся в фиксированной запятой 16,16, с машинной единицей, равной одному текселю (рост игрока 56 текселей — значит, 1 тексель примерно равен 3 см). Для угловых величин применяется фиксированная запятая, в которой 65536 = 360°.

Запись демо-роликов и мультиплеер основаны том, что на цифровой ЭВМ один и тот же код с одними и теми же данными приводит к одному и тому же результату, а поведение целочисленной арифметики жёстко заспецифицировано и не зависит от модели процессора. Игра записывает в демо-ролик (и передаёт по сети) команды управления; если в игре нет ошибок, различные машины, интерпретируя одни и те же команды управления, получают один и тот же результат. Впрочем, ошибки, приводящие к рассинхронизации, в игре всё-таки есть: в частности, если в одиночной игре зайти в меню, игра останавливается, но ролик продолжает писаться. Недостаток такого подхода — невозможность перемотать ролик; его можно только прокрутить с начала.

В режиме записи демо-роликов точность поворотов снажалась до 256 на 360 градусов; внимательный игрок мог заметить, что в режиме демо-записи наводка становится грубее. Это служило исключительно для экономии памяти в демо-роликах и реально не требовалось.

Каждый такт игра проводит цикл управления каждым монстром. Для того, чтобы сэкономить процессорные такты, существует битовая матрица REJECT: для любых двух секторов, если ни из одной точки сектора А не видна никакая точка сектора Б, на этом месте в матрице ставится единица. Если на пересечении строки, соответствующей сектору игрока, и столбца, соответствующего сектору монстра, стоит 0, проводится проверка, видит ли монстр игрока; если 1 — монстр гарантированно не видит игрока. Матрица REJECT очень сложна для построения, и большинство пользовательских редакторов не строили её (существовало очень мало утилит, которые проводили эту работу; основные — RMB и ZENNODE).

Рендеринг

Принцип работы движка таков. Заводятся два массива по ширине экрана («верх» и «низ»). Рисуется выпуклый подсектор, в котором находится игрок — стены по столбцам, полы записываются в особый массив (visplanes). Для каждого столбца экрана модифицируем «верх» и «низ» таким образом, что «верх» — это экранная координата нижней точки потолка в соответствующем столбце экрана; «низ» — экранная координата верхней точки пола. После этого рисуем точно так же остальные подсекторы в порядке, который даст проход BSP-дерева. Как только во всех столбцах «верх» будет равен «низу», картинка нарисована.

После того, как отрисованы стены, по строкам рисуются полы, записанные в visplanes. Кстати, этот массив сделали довольно малым, и у самодеятельных конструкторов довольно часто случалось переполнение и выход с сообщением «No more visplanes!»

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

Текстуры стен и спрайты хранятся в WAD ‘е по столбцам, текстуры полов и потолков — простой массив 64×64.

Сетевой код

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

Игры на движке Doom’а

Мод Ghostbusters для Doom

Движок Doom’а продавался другим компаниям. На нём был сделан ряд игр. Среди них:

Открытие исходного кода

В декабре 1997 года исходный текст Doom для GNU/Linux был опубликован под несвободной бесплатной лицензией. Чи Хоан (Chi Hoang) был первым, кто сумел в январе 1998 года выпустить порт Doom для DOS — DosDoom.

Пионерами расширения Doom были Ли Киллоу (Boom — расширенная версия Doom, в которой были исправлены все ошибки оригинальной игры, а также серьёзно, почти в 1,5 раза, был ускорен движок), Денис Фабрис и Борис Перейра (Doom Legacy — версия Doom с качественным пользовательским интерфейсом и поддержкой высоких разрешений).

В Doom Legacy и ZDoom присутствует возможность игры с ботами.

Файлы данных Doom по сей день остаются платными. Для создания бесплатного WAD ’а был начат проект FreeDoom [3]. Если к расширенной версии Doom добавить WAD из FreeDoom, получаем полностью свободную игру. По состоянию на апрель 2007 года, проект не готов, страдает плохим качеством изображения и ориентирован на Boom (под версиями, которые не являются производными Boom, непроходим уже первый уровень).

DOOM Doom | Doom II: Hell on Earth | Final Doom | Doom 64 | Doom 3 ( Resurrection of Evil ) | Doom RPG | Doom 4Разработкаid Software | Джон Кармак | Джон РомероТехнологииДвижок Doom | id Tech 4 | id Tech 5Порты технологий Doom ( список )ZDoom | GZDoom | jDoom | WinDoomКниги и фильмыПо колено в крови | Ад на Земле | Адское небо | Конец игры | Doom (фильм)

Источник

Пишем клон движка Doom: чтение информации карт

на каком языке написан doom. Смотреть фото на каком языке написан doom. Смотреть картинку на каком языке написан doom. Картинка про на каком языке написан doom. Фото на каком языке написан doom

Введение

Цель этого проекта — создание клона движка DOOM, использующего ресурсы, выпущенные вместе с Ultimate DOOM (версия со Steam).

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

У меня нет опыта создания игр или игровых движков, и мало опыта в написании статей, поэтому можете предлагать свои изменения или даже полностью переписать код.

Вот список ресурсов и ссылок.

Книга Game Engine Black Book: DOOM Фабьена Санглара. Одна из лучших книг по внутреннему устройству DOOM.

Требования

Необязательное

Мысли

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

Моей целевой платформой будет Windows, но поскольку я использую SDL, будет просто заставить движок работать под любой другой платформой.

А пока установим Visual Studio!

Проект был переименован из Handmade DOOM в Do It Yourself Doom with SLD (DIY Doom), чтобы его не путали с другими проектами под названием «Handmade». В туториале есть несколько скриншотов, на которых он всё ещё называется Handmade DOOM.

Файлы WAD

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

Для начала давайте проверим, сможем ли мы считывать файлы ресурсов DOOM. Все ресурсы DOOM находятся в файле WAD.

Что такое файл WAD?

«Where is All my Data»? («Где все мои данные»?) Они в WAD! WAD — это архив всех ресурсов DOOM (и игр на основе DOOM), находящийся в одном файле.

Разработчики Doom придумали этот формат, чтобы упростить создание модификаций игры.

Анатомия файла WAD

Файл WAD состоит из трёх основных частей: заголовка (header), «кусков» (lumps), и каталогов (directories).

Формат заголовка

Размер поляТип данныхСодержимое
0x00-0x034 символа ASCIIСтрока ASCII (со значениями «IWAD» или «PWAD»).
0x04-0x07unsigned intНомер элемента каталогов.
0x08-0x0bunsigned intЗначение смещения на каталог в файле WAD.

Формат каталогов

Размер поляТип данныхСодержимое
0x00-0x03unsigned intЗначение смещения на начало lump-данных в файле WAD.
0x04-0x07unsigned intРазмер «куска» (lump) в байтах.
0x08-0x0f8 символов ASCIIASCII, содержащие название «куска».

Архитектура

Примечание: такая архитектура может быть и неоптимальной, и при необходимости мы будем её изменять.

Приступаем к коду

на каком языке написан doom. Смотреть фото на каком языке написан doom. Смотреть картинку на каком языке написан doom. Картинка про на каком языке написан doom. Фото на каком языке написан doom

Давайте добавим два новых класса: WADLoader и WADReader. Начнём с реализации WADLoader.

Реализовать конструктор будет просто: инициализируем указатель данных и храним копию передаваемого пути к файлу WAD.

Теперь давайте приступим к реализации вспомогательной функции загрузки OpenAndLoad : просто попробуем файл открыть как двоичный и в случае неудачи выведем ошибку.

Если всё будет хорошо, и мы сможем находить и открывать файл, то нам понадобится знать размер файла, чтобы выделить память для копирования в неё файла.

Теперь мы знаем, сколько места занимает полный WAD, и выделим нужное количество памяти.

Скопируем содержимое файла в эту память.

Давайте вкратце проверим код и убедимся, что всё работает. Но прежде нам нужно реализовать LoadWAD. Пока LoadWAD будет вызывать «OpenAndLoad»

И давайте добавим в функцию main код, создающий экземпляр класса и пытающийся загрузить WAD

Нужно будет ввести правильный путь к вашему файлу WAD. Давайте запустим!

Ой! Мы получили консольное окно, которое просто открывается на несколько секунд! Особо ничего полезного… работает ли программа? Идея! Давайте взглянем на память, и посмотрим, что в ней! Возможно, там мы найдём что-нибудь особенное! Для начала разместим точку останова, дважды щёлкнув слева от номера строки. Вы должны увидеть нечто подобное:

на каком языке написан doom. Смотреть фото на каком языке написан doom. Смотреть картинку на каком языке написан doom. Картинка про на каком языке написан doom. Фото на каком языке написан doom

Я поместил точку останова сразу после чтения всех данных из файла, чтобы посмотреть на массив памяти и увидеть, что в него загружено. Теперь снова запустим код! В автоматическом окне я вижу несколько первых байтов. В первых 4 байтах написано «IWAD»! Отлично, работает! Никогда не думал, что этот день настанет! Так, ладно, нужно успокоиться, впереди ещё много работы!

на каком языке написан doom. Смотреть фото на каком языке написан doom. Смотреть картинку на каком языке написан doom. Картинка про на каком языке написан doom. Фото на каком языке написан doom

Считывание заголовка

Общий размер заголовка составляет 12 байт (от 0x00 до 0x0b), эти 12 байт разделены на 3 группы. Первые 4 байта — это тип WAD, обычно «IWAD» или «PWAD». IWAD должен быть официальным WAD, выпущенным ID Software, «PWAD» должен использоваться для модов. Другими словами, это просто способ определить, является ли файл WAD официальным релизом, или выпущен моддерами. Заметьте, что строка не NULL terminated, поэтому внимательнее! Следующие 4 байта являются unsigned int, в котором содержится общее количество каталогов в конце файла. Следующие 4 байта обозначают смещение первого каталога.

Давайте добавим структуру, которая будет хранить информацию. Я добавлю новый файл заголовка и назову его «DataTypes.h». В нём мы будем описывать все нужные нам struct.

Теперь нам нужно реализовать класс WADReader, который будет считывать данные из загруженного массива байтов WAD. Ой! Тут есть хитрость — файлы WAD имеют формат big-endian, то есть нам нужно будет сдвинуть байты, чтобы сделать их little-endian (сегодня в большинстве систем используется little endian). Для этого мы добавим две функции, одну для обработки 2 байт (16 бит), другую — для обработки 4 байт (32 бит); если нам нужно считать только 1 байт, то делать ничего не надо.

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

Давайте соединим всё вместе, вызовем эти функции и выведем результаты

Запустим программу и посмотрим, всё ли работает!

на каком языке написан doom. Смотреть фото на каком языке написан doom. Смотреть картинку на каком языке написан doom. Картинка про на каком языке написан doom. Фото на каком языке написан doom

Отлично! Строку IWAD хорошо заметно, но правильны ли другие два числа? Попробуем считать каталоги при помощи этих смещений и посмотрим, получится ли!

Нам нужно добавить новую struct для обработки каталога, соответствующего представленным выше параметрам.

Теперь давайте дополним функцию ReadDirectories: считаем смещение и выведем их!

В каждой итерации мы умножаем i * 16, чтобы перейти к инкременту смещения следующего каталога.

Запустим код и посмотрим, что получится. Ого! Большой список каталогов.

на каком языке написан doom. Смотреть фото на каком языке написан doom. Смотреть картинку на каком языке написан doom. Картинка про на каком языке написан doom. Фото на каком языке написан doom

Судя по названию lump, можно предположить, что нам удалось правильно считать данные, но возможно есть способ получше, чтобы проверить это. Мы взглянем на записи WAD Directory при помощи Slade3.

на каком языке написан doom. Смотреть фото на каком языке написан doom. Смотреть картинку на каком языке написан doom. Картинка про на каком языке написан doom. Фото на каком языке написан doom

Похоже, что название и размер lump соответствуют данным, полученным при помощи нашего кода. Сегодня мы проделали отличную работу!

Другие примечания

DOOMReboot: полностью несогласен. 15 МБ ОЗУ в наши дни — совершенная мелочь, и считывание из памяти будет значительно быстрее, чем объёмные fseek, которые придётся использовать после загрузки всего, необходимого для уровня. Это увеличит время загрузки не меньше, чем на одну-две секунды (у меня всё время загрузки занимает меньше 20 мс). fseek задействуют ОС. У которой файл скорее всего находится в кэше ОЗУ, но может быть и нет. Но даже если он там, это большая трата ресурсов и эти операции запутают множество считываний WAD с точки зрения кэша ЦП. Самое лучшее, что можно создать гибридные методы загрузки и хранить данные WAD для уровня, которые помещаются в кэш L3 современных процессоров, где экономия окажется потрясающей.

Исходный код

Базовые данные карт

Научившись считывать файл WAD, давайте попробуем использовать прочитанные данные. Будет здорово научиться считывать данные миссии (мира/уровня) и применять их. «Куски» данных миссий (Mission Lumps) должны быть чем-то сложным и хитрым. Поэтому нам нужно будет двигаться и нарабатывать знания постепенно. В качестве первого небольшого шага давайте создадим нечто наподобие функции автокарты (Automap): двухмерного плана карты с видом сверху. Для начала посмотрим, что находится внутри Mission Lump.

Анатомия карты

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

Эти любопытные особенности стали причиной бесконечных холиваров по поводу того, является ли DOOM 2D- или 3D-движком. Постепенно был достигнут дипломатический компромисс, спасший множество жизней: стороны сошлись на приемлемом для обеих обозначении «2.5D».

Чтобы упростить задачу и вернуться к теме, давайте просто попробуем считать эти 2D-данные и посмотреть, можно ли их как-то использовать. Позже мы попробуем отрендерить их в 3D, а пока нам нужно разобраться в том, как работают совместно отдельные части движка.

Проведя исследования, я выяснил, что каждая миссия составлена из набора «кусков». Эти «куски» (Lumps) всегда представлены в файле WAD игры DOOM в одинаковом порядке.

на каком языке написан doom. Смотреть фото на каком языке написан doom. Смотреть картинку на каком языке написан doom. Картинка про на каком языке написан doom. Фото на каком языке написан doom

Показанная выше демо-карта имеет следующие характеристики:

Формат вершин

Как и можно ожидать, данные вершин очень просты — всего лишь x и y (точка) каких-то координат.

Размер поляТип данныхСодержимое
0x00-0x01Signed shortПозиция X
0x02-0x03Signed shortПозиция Y

Формат Linedef

В Linedef содержится больше информации, он описывает линию, соединяющую две вершины, и свойства этой линии (которая позже станет стеной).

Размер поляТип данныхСодержимое
0x00-0x01Unsigned shortНачальная вершина
0x02-0x03Unsigned shortКонечная вершина
0x04-0x05Unsigned shortФлаги (подробнее см. ниже)
0x06-0x07Unsigned shortТип линии/действие
0x08-0x09Unsigned shortМетка сектора
0x10-0x11Unsigned shortПередний sidedef (0xFFFF — стороны нет)
0x12-0x13Unsigned shortЗадний sidedef (0xFFFF — стороны нет)

Значения флагов Linedef

Не все линии (стены) отрисовываются. Некоторые из них имеют особое поведение.

БитОписание
0Преграждает путь игрокам и монстрам
1Преграждает путь монстрам
2Двусторонняя
3Верхняя текстура отключена (об этом мы поговорим позже)
4Нижняя текстура отключена (об этом мы поговорим позже)
5Секрет (на автокарте показывается как односторонняя стена)
6Препятствует звуку
7Никогда не показывается на автокарте
8Всегда показывается на автокарте

Архитектура

Для начала давайте создадим класс и назовём его map. В нём мы будем хранить все данные, связанные с картой.

Пока я планирую только хранить как вектор вершины и linedefs, чтобы применить их позже.

Также давайте дополним WADLoader и WADReader, чтобы мы могли считывать эти два новых элемента информации.

Кодинг

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

Теперь добавим структуры, чтобы считывать эти новые поля. Поскольку мы уже несколько раз это делали, просто добавим их все сразу.

Далее нам понадобится функция для считывания их из WADReader, она будет близка к тому, что мы делали ранее.

Думаю, для вас здесь нет ничего нового. А теперь нам нужно вызвать эти функции из класса WADLoader. Позвольте изложить факты: здесь важна последовательность lumps, мы найдём название карты в lump каталога, за которым в заданном порядке будут следовать все lumps, связанные с картами. Чтобы упростить себе задачу и не отслеживать индексы lumps по отдельности, мы добавим перечисление, позволяющее избавиться от магических чисел.

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

Ого, мы почти закончили! Теперь давайте просто считаем VERTEXES! Повторюсь, мы уже делали такое раньше, теперь вы должны разбираться в этом.

Хм, похоже, что мы постоянно копипастим один и тот же код; возможно, в дальнейшем его придётся оптимизировать, но пока вы реализуете ReadMapLinedef самостоятельно (или посмотрите на исходный код по ссылке).

Финальные штрихи — нам нужно вызвать эту функцию и передать ей объект карты.

Теперь давайте изменим функцию main и посмотрим, всё ли будет работать. Я хочу загрузить карту «E1M1», которую передам в объект карты.

Теперь давайте всё это запустим. Ого, куча интересных чисел, но верны ли они? Давайте проверим!

Посмотрим, сможет ли slade помочь нам и в этом.

Мы можем найти карту в меню slade и посмотреть на подробности lumps. Давайте сравним числа.

на каком языке написан doom. Смотреть фото на каком языке написан doom. Смотреть картинку на каком языке написан doom. Картинка про на каком языке написан doom. Фото на каком языке написан doom

А как насчёт Linedef?

на каком языке написан doom. Смотреть фото на каком языке написан doom. Смотреть картинку на каком языке написан doom. Картинка про на каком языке написан doom. Фото на каком языке написан doom

Также я добавил это перечисление, которое мы попробуем использовать при отрисовке карты.

Другие примечания

В процессе написания кода я ошибочно считывал больше байтов, чем нужно, и получал неверные значения. Для отладки я начал смотреть на смещение WAD в памяти, чтобы понять, нахожусь ли я на нужном смещении. Это можно сделать при помощи окна памяти Visual Studio, которые оказываются очень полезным инструментом при отслеживании байтов или памяти (также в этом окне можно устанавливать точки останова).

Если вы не видите окно памяти, то перейдите в Debug > Memory > Memory.

на каком языке написан doom. Смотреть фото на каком языке написан doom. Смотреть картинку на каком языке написан doom. Картинка про на каком языке написан doom. Фото на каком языке написан doom

Теперь мы видим значения в памяти в шестнадцатеричном виде. Эти значения можно сравнить с hex-отображением в slade, нажав правой клавишей на любой lump и отобразив его как hex.

на каком языке написан doom. Смотреть фото на каком языке написан doom. Смотреть картинку на каком языке написан doom. Картинка про на каком языке написан doom. Фото на каком языке написан doom

Сравниваем их с адресом загруженного в память WAD.

на каком языке написан doom. Смотреть фото на каком языке написан doom. Смотреть картинку на каком языке написан doom. Картинка про на каком языке написан doom. Фото на каком языке написан doom

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

Наверняка уже кто-то создал графопостроитель. Я загуглил «draw points on a graph» и первым результатом оказался веб-сайт Plot Points — Desmos. На нём можно вставить числа из буфера обмена, и он нарисует их. Они должны быть в формате «(x, y)». Чтобы получить его, достаточно немного изменить функцию вывода на экран.

Ого! Это уже похоже на E1M1! Мы чего-то добились!

на каком языке написан doom. Смотреть фото на каком языке написан doom. Смотреть картинку на каком языке написан doom. Картинка про на каком языке написан doom. Фото на каком языке написан doom

Если вам лениво это делать, то вот ссылка на заполненный точками график: Plot Vertex.

Но давайте сделаем ещё один шаг: немного потрудившись, мы можем соединить эти точки на основании linedefs.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *