Библиотека сайта rus-linux.net
Битва за Веснот
Глава 25 из 1 тома книги "Архитектура приложений с открытым исходным кодом".
Оригинал: Battle for Wesnoth, глава из книги "The Architecture of Open Source Applications" том 1.
Авторы: Richard Shimooka и David White
Перевод: Н.Ромоданов
25.2. Wesnoth Markup Language — язык разметки Wesnoth
Поскольку Wesnoth является расширяемым игровым движком, в нем используется простой язык описания данных, позволяющий сохранять и загружать все игровые данные. Хотя сначала рассматривалось использование языка XML, мы решили, что нам нужно что-нибудь более дружественное для пользователей, которые технически подготовлены слабее, и менее строгое для описания визуальных данных. Поэтому мы разработали свой собственный язык описания данных, называемый Wesnoth Markup Language (WML). Он был разработан в расчете на пользователей, имеющих самую слабую техническую подготовку: мы надеялись, что даже те пользователи, для которых языки Python и HTML кажутся страшными, смогут разобраться в файле WML. Все игровые данные Wesnoth хранятся на языке WML, в том числе определения юнитов, описания кампаний, сценарии, определения пользовательского интерфейса и другие настройки игровой логики.
В языке WML используются такие же самые базовые элементы, что и в языке XML: элементы и атрибуты, хотя в нем не поддерживается использование текста внутри элементов. Атрибуты WML представлены просто как словарь, осуществляющий отображение строк в строки, а за интерпретацию атрибутов отвечает логика программы. Ниже показан простой пример на языке WML, с помощью которого в игре приводится сокращенное определение юнита Elvish Fighter (Эльфийский Воин):
[unit_type] id=Elvish Fighter name= _ "Elvish Fighter" race=elf image="units/elves-wood/fighter.png" profile="portraits/elves/fighter.png" hitpoints=33 movement_type=woodland movement=5 experience=40 level=1 alignment=neutral advances_to=Elvish Captain,Elvish Hero cost=14 usage=fighter {LESS_NIMBLE_ELF} [attack] name=sword description=_"sword" icon=attacks/sword-elven.png type=blade range=melee damage=5 number=4 [/attack] [/unit_type]
Поскольку в Wesnoth очень важна интернационализация, в языке WML есть ее непосредственная поддержка: значения атрибутов, начинающиеся со знака подчеркивания, являются переводимыми. Когда происходит синтаксический разбор языка WML, все переводимые строки с помощью GNU gettext
преобразовываются в переводенные версии строк.
Вместо того, чтобы использовать много различных документов WML, в Wesnoth выбран подход, в котором все основные игровые данные передаются в игровой движок просто в виде единого документа. Это позволяет для работы с документом использовать только одну глобальную переменную, а когда в игре происходит загрузка, например, определения юнитов, выполнять поиск элементов с именем unit_type}
, находящихся внутри элемента units
.
Не смотря на то, что все данные хранятся в виде одного концептуально единого документа WML, было бы громоздко хранить весь этот документ в одном файле. Поэтому в Wesnoth используется препроцессор, которыйперед тем, как начнется синтаксический разбор, проходит по всем файлам WML. Этот препроцессор позволяет в файл добавлять содержимое другого файла или целого каталога. Например:
{gui/default/window/}
будет добавлять содержимое всех файлов .cfg
, находящихся в каталоге gui/default/window/
.
Поскольку описание в WML может быть очень подробным, в препроцессоре также можно использовать макросы, которые нужны для более компактных определений. Например, обращение к LESS_NIMBLE_ELF
в определении Эльфийского Воина (Elvish Fighter) является вызовом макроса, который делает некоторых эльфийских юнитов менее ловкими при определенных условиях, например, когда они размещены в лесу:
#define LESS_NIMBLE_ELF [defense] forest=40 [/defense] #enddef
Данная конструкция обладает тем преимуществом, что движок не считает нужным разбираться с тем, как документ WML разбит на отдельные файлы. Это ответственность лежит на авторах документа WML, которые решают, как структурировать документ и как разделить его на отдельные файлы и каталоги.
Когда игровой движок загружает документ WML, он, в соответствии с различными настройками игры, также определяет некоторые значения настроек для препроцессора. Например, для кампании в Wesnoth можно определять различные уровни сложности, причем различные настройки каждого из них приведут к тому, что для препроцессора будут определены различные значения настроек. Например, обычным способом изменения сложности игры является изменение количества ресурсов, предоставляемых оппоненту (определяемых количеством золота). Чтобы это упростить, используется макрос, который определен следующим образом:
#define GOLD EASY_AMOUNT NORMAL_AMOUNT HARD_AMOUNT #ifdef EASY gold={EASY_AMOUNT} #endif #ifdef NORMAL gold={NORMAL_AMOUNT} #endif #ifdef HARD gold={HARD_AMOUNT} #endif #enddef
Этот макрос может быть вызван, например, внутри определения оппонента как {GOLD 50 100 200}
с тем, чтобы указать, сколько в зависимости от уровня сложности он получит золота.
Поскольку XML обрабатывается в зависимости от настроек, то если какое-либо значение, прилагаемое к документу WML, изменится во время его исполнения движком Wesnoth, весь документ WML должен быть повторно загружен и обработан. Например, когда пользователь запускает игру, то загружается документ WML и среди всего прочего загружается и список доступных кампаний. Но, если затем пользователь выбирает кампанию и выбирает определенный уровень сложности, например, легкий уровень, то весь документ будет перезагружен с установленным параметром EASY.
Такая конструкция удобна тем, что в одном документе содержатся все игровые данные и что с помощью значений настроек можно легко сконфигурировать документ WML. Но в Wesnoth, как у успешного проекта, появляется всё больше и больше контента, в том числе контента, доступного для загрузки, который, в конце концов, весь оказывается в дереве основного документа, что означает, что размер окончательно получившегося документа WML будет составлять много мегабайтов. Из-за этого возникают проблемы с производительностью Wesnoth: на некоторых компьютерах загрузка документа может доходить до минуты, приводя в игре к задержкам каждый раз, когда документ необходимо перезагрузить. Кроме того, при этом используется значительный объем памяти. Против этого предпринимаются некоторые меры: когда кампания загружается, то для этой капании есть уникальное значение, определяемое в препроцессоре. Это означает, что когда нужна эта кампания, то будет использован весь тот контент, принадлежность которого к кампании определяется с помощью #ifdef
.
Кроме того, в Wesnoth используется система кеширования, которая кеширует препроцессорную версию документа WML для заданного набора ключевых определений. Естественно, что такая кеширующая система должна проверять даты изменения файлов WML для того, чтобы в случае, если какой-нибудь из них будет изменен, перегенерировать закешированный документ.
Продолжение статьи: Юниты в Wesnoth