Файлы информации

Материал из Клуб любителей рогаликов
Перейти к: навигация, поиск

Один из основных факторов, выделяющих Angband среди Roguelike-игр - абсолютное количество его вариантов. Захотелось поиграть в мире Zelazny, а не Tolkien'а? Играйте в Zangband. Захотелось Lovecraft'а? Играйте в CthAngband. А делает эти варианты возможными то, что почти всех своих монстров и информацию о предметах Angband загружает из легко редактируемых инфо-файлов (я определяю здесь "инфо"- файл как текстовый файл, используемый для конфигурирования фактических аспектов гэймплея игры, а не интерфейс пользователя). Это означает, что я могу сделать свой собственный вариант Angband'а не зная ни единой строки кода! Только не поймите меня неправильно, эти другие варианты также почти всегда имеют свои собственные модификации кода, но игра с инфо-файлами это начало, которое может увлечь человека возможностью сделать свой вариант.

Вы должны решительно определиться: загружать-ли вашу информацию из текстовых файлов вместо ее хранения в самом коде. Для одного значительно легче разработать простой текстовый парсер и затем вводить данные как текст, чтобы потом выполнять загрузку массивов. Для другого, это позволяет сделать тонкую настройку вашей игры без постоянных перекомпиляций. Теперь Вас могут обеспокоить игроки способные прочитать все уязвимые места монстров и свойства предметов, но это очень легко спрятать - просто инвертировать ваш текстовый файл (обратить 0 в 1 и наоборот) перед выдачей вашей откомпилированной версии. И если Вы в раздумьях поскольку хотите, чтобы вся игра была в одном простом файле приложения, даже не беспокойтесь: почти каждая ОС имеет способ упаковки ваших текстовых данных в "ресурс" resource в вашем файле приложения (Windows и Mac разумеется это позволяют). Например, я делал аркадную игру называвшуюся Witches (Ведьмы) (http://www.strangegames.com), все карты которой были сделаны как текстовые файлы, которые для простоты редактирования загружались отдельно от программы. Когда приходило время компилировать и распространять игру, я перемещал файлы в ресурс приложения и изменял единственную директиву (#define) в коде - совсем никаких проблем.

Теперь важная часть: формат вашего файла. У Вас есть много вариантов доступных форматов, но рассмотрите сначала Ваши приоритеты:

1. Файл должен редактироваться человеком. Это означает это должен быть текстовый файл, с легко понятными ключевыми словами и структурой, с интервалами и комментариями пользователя.

2. Он должен легко сканироваться парсером (я использую слово "парсер" в свободном значении: "программный код, для чтения и интерпретации текста"). Это значит, что он имеет довольно плотную структуру. Это также означает, что нет никакого фактического программирования в инфо-файле - мы просто храним данные, а не код! Итак, иметь в файле флаг ATTACKS_ELVES - разумно, допускать создание конструкций наподобие "ЕСЛИ ИГРОК=ЭЛЬФ АТАКУЕТ ИГРОКА" ("IF PLAYER=ELF ATTACK PLAYER") - не разумно, поскольку писать парсер для этого - головная боль. Парсер должен быть способен прочитать весь файл за одно быстрое сканирование.

3. Он должен быть относительно компактным. Отмечу, что этот приоритет находится в противоречии с первыми двумя (например, он будет значительно компактнее, если отказаться от комментариев, справочных конструкций с наследованием данных, чтобы избежать излишних данных, и т.п., но первое предложение нарушает правило 1, а второе нарушает правило 2). При сомнениях первые два важнее. Это, в конце концов, текст и он не займет слишком много пространства независимо от того, насколько габаритно-неэффективен ваш формат: например, стандартный файл монстров Angband'а (который компактен в разумных пределах, но не слишком), имеет свыше 400 созданий в этом и тем не менее занимает менее 40k!

Вот простой пример формата. В нашей игре есть, скажем, предметы и монстры. В действительности Вы можете конфигурировать гораздо больше вещей, например:

  • артефакты (такие как уникальные предметы)
  • "особенности" (flavors) предметов (такие как точность/острота (sharpness), сопротивление огню - часто называемые "предметы личности" (ego items) из-за AD & D)
  • характеристики местности (леса, стены, и т.п.)
  • сокровища
  • комнаты & хранилища (vaults)
  • магазины & лавочники (shops & shopkeepers)
  • какие заклинания существуют в игре
  • случайное присвоение имен созданиям & предметам
  • каких рас & классов может быть игрок

Мы скажем, что основные элементы нашего формата - Интервал (пробелы, табуляции, и возвраты), Комментарии и Данные (Entries). Комментарий мы определим как любую строку, которая начинается с символа # (Вы можете, если нравится, сделать комментарии в Си-стиле /* */, но # парсится легче). Парсер будет читать данные по одному, в любом порядке. Я нахожу, что это легче, чем построение по порядку важности, по двум причинам: а) поскольку потом пользователь может просто добавить данные как он/она обдумает их, и б) Вы можете выделить часть парсера, для отделения интервалов и комментариев от части, которая действительно создаёт данные.

Затем мы скажем, что все данные имеют следующий формат:

ENTRY_TYPE &ltEntry name> (
INFORMATION_LINE
INFORMATION_LINE
INFORMATION_LINE
...
)

ENTRY_TYPE - специфический тип входа. Поскольку в нашем примере только предметы и монстры, это будет или ПРЕДМЕТ (ITEM) или МОНСТР (MONSTER). У Вас могуть быть ПРЕДМЕТ, МОНСТР, ЗАКЛИНАНИЕ (SPELL), МАГАЗИН (SHOP), ХРАНИЛИЩЕ (VAULT), и т.п. Имя входа очевидно: базовое имя входа. Так

MONSTER &ltGrue> (
...
)

- злой монстр Груэ (Grue). Вас может удивить, почему я использую <угловые скобки> вместо "кавычек" (quotes). Это потому, что я могу использовать кавычки в имени (я мог бы определить эскейп-последовательности (escape sequence), но я сохраняю парсер простым, правда?), так что у меня могут быть монстры наподобие <Моргот (Morgoth) "Sex-Kitten" Бауглир (Bauglir)> не путающие парсер.

Теперь, когда с информационными строками на входе всё ясно, нужно сообщить нам, что нам нужно знать о монстре. Я хочу сказать, что каждая информационная строка начинается с имени определенного атрибута. У Вас их будет много, с различными атрибутами для разных созданий, как например, ВЕС (WEIGHT) и ЦЕННОСТЬ (VALUE) для предмета или ХИТПОЙНТЫ (HITPOINTS) и АТАКА (ATTACK) для монстра. За этим атрибутом может следовать разделенный запятыми список из целых чисел и заключённых в угловые скобки строк, которые могут быть любой (включая нулевую) длины. Что собой представляет каждый из этих элементов зависит от типа атрибута. Атрибут ВЕС может представлять просто число, означающее вес. Атрибут АТАКА может представлять тип атаки (строку), за которой следуют показатели точности и наносимого ущерба монстра. Отмечу, что у нас может быть на одном входе много атрибутов одного и того же самого типа, хотя в некоторых случаях (таких как ВЕС) ничего подобного делать нельзя. Вот два примера:

# Это легко читается, не правда ли? Пищевой рацион представляет 200 единиц
# пищи, весит 2 фунта или
# килограмми или чего-нибудь ещё, и стоит 5 золотых монет, или долларов или чего-нибудь ещё.
#
ITEM &ltRation of Food> (
NUTRITION 200
WEIGHT 2
VALUE 5
)

# Я даже сомневаюсь, что Вам нужны комментарии, чтобы понять это
#
MONSTER &ltMauve Dragon> (
PLURAL &ltMauve Dragons>
DESCR &ltA bluish-purplish dragon, an affront to both goodness >
DESRC &ltand standards of taste.>
DUNGEONLEVEL 30
EXPVALUE 15000
ATTACK &ltclaws>, 30, 15
ATTACK &ltclaws>, 30, 15
ATTACK &ltbite>, 15, 25
BREATH &ltirritation>, 400
)

Здесь нет необходимости приводить сканер для этого файла, но у Вас должна быть возможность увидеть как это легко. Сканер просто прогоняется пока не достигнет входа, после чего получает первое слово. Поскольку у него есть тип, он формирует или монстра или предмет с данным названием и затем заполняет его информацией из строк. Отметим, что это особенно легко в объектно-ориентированном языке вроде C++ или Java - как только Вы узнаете, какой тип монстра Вы будете делать, Вы просто создаете новый объект типа MonsterType(), затем по мере того как Вы поочередно сканируете информационные строки для данного объекта многократно вызывается метод addInfo(), позволяющий объекту формировать из информации самого себя. Поскольку как у парсера, так и у записывателя файла (file writer) есть для этого дешёвое время, и даже хотя это вообще не сжатый формат, вышеупомянутый Лиловый Дракон (Mauve Dragon) занял всего лишь 369 байтов! Для получения большего сжатия, есть легкий способ заключающийся в том, чтобы уменьшать размер ключевых слов - это во любом случае легче, поскольку так удобнее и меньше вероятность сделать ошибку, записывая DL вместо DUNGEONLEVEL и XP вместо EXPVALUE. С другой стороны, если Вы дойдёте до всего одной буквы как в Angband'е, то файлы станут не настолько легко читаемыми.

Помимо прочего я должен упомянуть две предлагаемых разработки для информационных файлов, которые иногда всплывают в newsgroups. Сначала XML. Если Вы ещё не знаете, XML - воистину великолепный язык разметки (при поверхностном ознакомлении он выглядит как HTML со всеми <угловыми скобками>), который позиционируется как новый, не зависящий от платформы способ пересылки данных в любой форме. Вы определенно можете делать ваши инфо-файлы в виде XML. Я *не* рекомендую этого, поскольку это будет, возможно, чуть более интенсивнее чем Вам хочется или необходимо. Он а) достаточно мудрёный для записи вручную, что нарушает наш первый приоритет; б) довольно интенсивный, чтобы написать для него правильный парсер (все-же _значительно_ легче, чем парсирование языка программирования), что нарушает второй приоритет; и в) не слишком компактен, что нарушает третий приоритет.

Другая разработка - использование скриптовых языков. В идеале Вы пишете "движок" игры - графику, интерфейс и так далее в C или C++, но вместо реализации особого поведения в коде, Вы делаете его в скриптовом языке. Так, например, если бы мне захотелось сделать с использованием приведённой выше схемы данных специальный "поющий меч" который бы сочинял небольшое стихотворение каждый раз, когда убивал оппонента, я мог бы пойти двумя путями. Первый (плохой путь "NetHack'а"), проверял бы наличие предмета в коде; как в "IF weapon = singing sword THEN DoSing()". Это плохо, потому что ваш код быстро станет загромождённым и нечитаемым. Второй (путь Angband'а), делал бы новый атрибут или флаг, например, ПЕНИЕ (SINGING), и помещал его в ваше описание объекта.

Скриптовый язык будет наилучшей альтернативой: везде, где что-то случается, должна быть "ловушка" (hook) на вашем объекте, который выполняет код. Эти ловушки инициируются после определенных событий; например, оружие может иметь ловушку ON_SLAY, которая совершает определенное действие если использующий это оружие убивает какое-нибудь создание (люди знакомые с JavaScript увидят сходство с событиями вроде "onClick" в HTML, которое перехватывается в JS - это та же самая идея). Рекомендуемый мною способ - иметь отдельный файл со всеми вашими скриптовыми функциями. В качестве простого примера: Вы можете иметь файл под названием "scripts.js" с JavaScript-программами для всех ваших ловушек. Так, Вы можете иметь функцию наподобие:

function Sing() {
  var r = game.RandomInt(0,2);
  if(r = 0) game.GiveMessage("Your sword sings of joy!");
  if(r = 1) game.GiveMessage("Your sword sings of triumph!");
  if(r = 2) game.GiveMessage("Your sword sings of victory!");
  PlaySound("annoying_song.wav");
}

И на входе вашего предмета, Вам потом будет нужна только информационная строка наподобие этой:

ON_SLAY < Sing(); >

Мечта относительно скриптов в Roguelike-играх - то, что варианты должны затем быть способны полностью или почти полностью модифицироваться по заказу *без* необходимости перекомпилировать код! Обычно Python предлагается как идеальный язык для таких задач, поскольку в этом случае он очень гибок. Никто не предлагает делать для Roguelike-игр специализированный скриптовый язык, поскольку это не стоит усилий - уже есть много хороших скриптовых языков, которые имеют хорошую базу людей, способных программировать в них и достаточно гибких, чтобы легко приспособить их к Roguelike-играм.

Но вернёмся к действительности, если Вы начинаете разрабатывать Roguelike-игру, у вас будет много поводов для беспокойства и без добавления процессора скриптового языка. Это огромная задача, даже если бы кто-нибудь создал для Вас процессор (обдумайте добавление всех ловушек, имеющих скромную объектную модель кода, чтобы действительно делать такие вещи, и т.п.). Для приложения тех же усилий, Вы могли бы пойти длинным путём чтобы сделать ваши info-файлы более перестраиваемыми и общими. Почему бы не иметь info-файл, который будет преобразовывать каждое текстовое сообщение в игре, чтобы оно могло быть легко портировано на другие языки? (Или использовать язык, вышедший из употребления несколько столетий назад, чтобы почувствовать эпоху Шекспира?) Который предоставит вашему игроку конфигурируемые квесты? Если каждый важный аспект вашей игры может быть определён извне, тогда он значительно легче завоюет друзей и фанатов, которые вместе с Вами создают вашу игру - это достаточно круто, не так ли?



Автор: James Burton.
Источник: неизвестен.
Перевел: Дмитрий О. Бужинский, 01.03.2006.