Управляемое данными кодирование
Каждая вещь, которую вы упоминаете, может быть указана в данных. Почему ты грузишь aspecificmap
? Потому что конфигурация игры говорит, что это первый уровень, когда игрок начинает новую игру, или потому что это имя текущей точки сохранения в файле сохранения игрока, который он только что загрузил, и т. Д.
Как ты находишь aspecificmap
? Потому что он находится в файле данных, в котором перечислены идентификаторы карт и их ресурсы на диске.
Должен быть только очень небольшой набор «основных» ресурсов, которые по закону трудно или невозможно избежать жесткого кодирования. Немного поработав, это может быть ограничено одним жестко заданным именем актива по умолчанию, например main.wad
или тому подобным. Этот файл потенциально может быть изменен во время выполнения путем передачи аргумента командной строки в игру, иначе game.exe -wad mymain.wad
.
Написание кода, управляемого данными, основывается на нескольких других принципах. Например, можно избежать того, чтобы системы или модули запрашивали конкретный ресурс и вместо этого инвертировали эти зависимости. То есть не DebugDrawer
загружайте debug.font
его в код инициализации; вместо этого нужно DebugDrawer
взять дескриптор ресурса в своем коде инициализации. Этот дескриптор может быть загружен из основного файла конфигурации игры.
В качестве конкретных примеров из нашей кодовой базы у нас есть объект «глобальных данных», который загружается из базы данных ресурсов (которая сама по умолчанию является ./resources
папкой, но может быть перегружена аргументом командной строки). Идентификатор базы данных ресурсов этих глобальных данных является единственным необходимым жестко запрограммированным именем ресурса в кодовой базе (у нас есть другие, потому что иногда программисты становятся ленивыми, но мы обычно в конечном итоге исправляем / удаляем их). Этот глобальный объект данных полон компонентов, единственной целью которых является предоставление данных конфигурации. Одним из компонентов является компонент глобальных данных пользовательского интерфейса, который содержит дескрипторы ресурсов для всех основных ресурсов пользовательского интерфейса (шрифты, файлы Flash, значки, данные о локализации и т. Д.), А также ряд других элементов конфигурации. Когда разработчик UI решает переименовать главный актив UI от /ui/mainmenu.swf
до/ui/lobby.swf
они просто обновляют эту глобальную ссылку на данные; код двигателя не нужно менять вообще.
Мы используем эти глобальные данные для всего. Все игровые персонажи, все уровни, пользовательский интерфейс, аудио, основные ресурсы, конфигурация сети, все. (ну, не все , но эти другие ошибки исправляются.)
Этот подход имеет много других преимуществ. С одной стороны, это делает упаковку и объединение ресурсов неотъемлемой частью всего процесса. Твердо заданные пути в движке также означают, что эти же пути должны быть жестко закодированы в любых сценариях или инструментах, упаковывающих игровые ресурсы, и эти пути затем могут быть не синхронизированы. Вместо этого, опираясь на один основной актив и цепочки ссылок, мы можем создать пакет активов с помощью одной команды, например, bundle.exe -root config.data -out main.wad
и знать, что она будет включать все необходимые нам активы. Кроме того, поскольку упаковщик будет просто следовать ссылкам на ресурсы, мы знаем, что он будет включать только те ресурсы, которые нам требуются, и пропустит весь оставшийся пух, который неизбежно накапливается в течение жизни проекта (плюс мы можем автоматически создавать списки этих ресурсов). пух для обрезки).
Хитрый угловой случай всего этого в сценариях. Концептуально создать движок, управляемый данными, легко, но я видел очень много проектов (хобби для AAA), где сценарии считаются данными и, следовательно, «разрешено» просто использовать пути к ресурсам без разбора. Не делай этого. Если файлу Lua нужен ресурс, и он просто вызывает такую функцию, как textures.lua("/path/to/texture.png")
у конвейера ресурсов, будет много проблем, зная, что скрипт /path/to/texture.png
должен работать правильно, и он может счесть эту текстуру неиспользованной и ненужной. Сценарии должны обрабатываться как любой другой код: любые данные, которые им нужны, включая ресурсы или таблицы, должны быть указаны в записи конфигурации, которую механизм и конвейер ресурсов могут проверять на наличие зависимостей. Данные, которые говорят "загрузить скрипт foo.lua
", вместо этого должны сказать "foo.lua
и задайте ему эти параметры ", где параметры включают любые необходимые ресурсы. Если сценарий случайным образом порождает врагов, например, передайте список возможных врагов в сценарий из этого файла конфигурации. Затем механизм может предварительно загрузить врагов с уровнем ( так как он знает полный список возможных порождений) и конвейер ресурсов знает, как связать всех врагов с игрой (так как на них окончательно ссылаются данные конфигурации). Если сценарии генерируют строки с именами путей и просто вызывают load
функцию, то ни движок и конвейер ресурсов не могут каким-либо образом точно знать, какие ресурсы может попытаться загрузить скрипт.