Начало
Пошагово рассмотрим написание простенькой игры Хейди, сюжет которой таков:
«Хейди живёт в маленьком домике в лесу. Одним солнечным днём она слышит писк птички — её гнездо упало с ветки на поляну. Хейди кладёт птичку в гнездо, а гнездо обратно на ветку».
Шаблон для игры
Для начала создадим исходный файл-шаблон. Создайте папку heidi
в папке Информа (например, c:\rinform\heidi\
), а в ней, при помощи текстового редактора, файл heidi.inf
со следующим содержимым:
!% -SD
!=============================================================================
Constant Story "Хейди";
Constant Headline
"^Пример простой игры на Inform.
^Авторы: Роджер Фирт (Roger Firth) и Соня Кессерих (Sonja Kesserich).
^Перевод Юрия Салтыкова a.k.a. G.A. Garinson^";
Include "Parser";
Include "VerbLib";
!============================================================================
! Описание игровых объектов
!============================================================================
! Функции инициализации
[ Initialise; ];
!============================================================================
! Стандартные и расширенные грамматики
Include "RussiaG";
!============================================================================
Пока можно просто скопировать и вставить этот текст; каждая строчка будет рассмотрена позднее. И убедитесь, что файл называется именно heidi.inf
, а не, например, heidi.txt
(регистр значения не имеет).
В папке игры с помощью текстового редактора создайте следующий файл heidi.bat
:
..\inform.exe +..\library +language_name=Russian -G -Cu $DICT_CHAR_SIZE=4 heidi.inf
pause
Запустите этот bat
-файл, в консоли появится примерно следующее:
c:\rinform\heidi>..\inform.exe +..\library +language_name=Russian -G -Cu $DICT_CHAR_SIZE=4 heidi.inf
Inform 6.34 (16th August 2017)
c:\rinform\heidi>pause
Для продолжения нажмите любую клавишу . . .
В папке heidi
появится файл heidi.ulx
, который можно запустить и поиграть:
Хейди
Пример простой игры на Inform.
Авторы: Роджер Фирт (Roger Firth) и Соня Кессерих (Sonja Kesserich).
Перевод Юрия Салтыкова a.k.a. G.A. Garinson
Release 1 / Serial number 180808 / Inform v6.34 Library 6/11 SD
В темноте
Кромешная тьма – не видно ни зги!
>
Теперь разберём каждую строчку исходного файла.
Разбор исходного файла
Для исходных файлов Информа существует несколько правил:
-
Если самая первая (или первые) строчки исходного файла начинаются с
!%
, то компилятор воспринимает их как параметры, а не как часть игры. В данном случае мы включаем два режима для будущей игры, Strict (-S
) и Debug (-D
). Strict режим в случае проблем выполнения в игре будет выдавать сообщение об ошибке, а режим Debug добавляет дополнительные команды в игру для облегчения отладки.На самом деле режим Strict включён по умолчанию и, например, чтобы его выключить (что не рекомендуется), нужно указать параметр
-~S
. -
Если строка начинается с восклицательного знака, то это комментарий, и он не обрабатывается компилятором. Комментарий может начинаться и посередине строки — тогда игнорируется всё, что идёт за знаком комментария. Для многострочного комментария нужно начать каждую строку с восклицательного знака.
-
Компилятор пропускает пустые строки, а также объединяет все пробелы, знаки табуляции и знаки переноса строки в один пробел (кроме пробелов внутри строк).
Например, можно было записать наш исходник таким образом, и результат остался бы тем же:
Constant Story "Хейди"; Constant Headline "^Пример простой игры на Inform.^Авторы: Роджер Фирт (Roger Firth) и Соня Кессерих (Sonja Kesserich).^Перевод Юрия Салтыкова a.k.a. G.A. Garinson^"; Include "Parser";Include "VerbLib"; [ Initialise; ]; Include "RussiaG";
Но при таком форматировании его гораздо сложнее читать.
-
В каждой игре должны быть описаны строковые константы
Story
(название игры) иHeadline
(короткое описание, имя автора). Вместе с датой и номером релиза они выводятся при запуске игры в заголовке. -
В каждой игре должны быть строки
Include
для включения стандартной библиотеки (эти файлы подставляются вместо этих строк при компиляции), и именно в этом порядке:Include "Parser"; Include "VerbLib"; ... Include "RussiaG";
-
В каждой игре должна быть функция
Initialise
:[ Initialise; ];
В нашем примере она ничего не выполняет, но тем не менее она должна присутствовать.
-
Все команды разделяются символом «точка с запятой» (аналогично C/C++).
Любая новая игра начинается с подобного шаблона, так что можно сохранить его отдельно, чтобы воспользоваться им позднее.
Задание локаций игры
При написании игры неизбежно задумываются о тех локациях, которые в ней встретятся. В нашей игре они будут следующими:
В Информе локации называются комнатами, пусть у них может и не быть стен. Для начала опишем наши комнаты таким образом:
Object "Перед домом"
with description
"Ты стоишь около избушки, на восток от которой раскинулся лес.",
has light;
Object "В лесной чаще"
with description
"На западе, сквозь густую листву, можно разглядеть небольшое строение.
Тропинка ведет на северо-восток.",
has light;
Object "Полянка"
with description
"Посреди полянки стоит высокий платан.
Тропинка вьется меж деревьев, уводя на юго-запад.",
has light;
Object "На верхушке дерева"
with description "На этой высоте цепляться за ствол уже не так удобно.",
has light;
Рассмотрим общие принципы:
-
Задание комнаты начинается со слова
Object
и заканчивается точкой с запятой. Вообще практически всё в игре является объектами — комнаты, предметы, люди, звуки и прочее. -
Строка после слова
Object
— это название, под которым объект появится в игре для игрока. -
Ключевое слово
with
обозначает компилятору, что дальше идёт перечисление свойств. -
description
содержит в себе подробное описание объекта. В случае комнаты этот текст выводится, когда игрок попадает в эту комнату. -
Ключевое слово
has
обозначает компилятору, что дальше идёт перечисление атрибутов. -
light
обозначает, что объект является источником света, и что игрок сможет увидеть, что происходит. В каждой комнате должен быть хотя бы один источник света, и обычно это сама комната. Иначе игрок ничего не увидит: «Кромешная тьма – не видно ни зги!»
У свойств есть название и значение (например, description
и «Ты стоишь около избушки, на восток от которой раскинулся лес»), а у атрибутов есть только название.
Позже, когда игра будет готова, в ней можно будет увидеть следующее:
Перед домом
Ты стоишь около избушки, на восток от которой раскинулся лес.
Можно увидеть, как здесь используются название комнаты и её описание (description
).
Соединение комнат
Комнаты заданы, и в тексте описано, как локации находится относительно друг друга — например, что из Лесной чащи можно пойти на запад к домику, либо по тропинке на северо-восток. Однако, в коде нужно явно указать, как соединены наши комнаты:
Object before_cottage "Перед домом"
with description
"Ты стоишь около избушки, на восток от которой раскинулся лес.",
e_to forest,
has light;
Object forest "В лесной чаще"
with description
"На западе, сквозь густую листву, можно разглядеть небольшое строение.
Тропинка ведет на северо-восток.",
w_to before_cottage,
ne_to clearing,
has light;
Object clearing "Полянка"
with description
"Посреди полянки стоит высокий платан.
Тропинка вьется меж деревьев, уводя на юго-запад.",
sw_to forest,
u_to top_of_tree,
has light;
Object top_of_tree "На верхушке дерева"
with description "На этой высоте цепляться за ствол уже не так удобно.",
d_to clearing,
has light;
Здесь мы сделали два изменения:
-
Между ключевым словом
Object
и именем объекта мы ввели внутреннее название, идентификатор этого объекта, которое используется внутри программы. Например, для домика этоbefore_cottage
, а для лесной чащи —forest
.Идентификатор не может включать в себя пробелы.
-
После описания объектов мы ввели строки, которые показывают, как соединены наши комнаты. Например для
before_cottage
:e_to forest,
Так игрок, который находится в первой комнате, сможет ввести в игре ИДТИ НА ВОСТОК (или просто ВОСТОК, или В), и игра перенесёт его в комнату с идентификатором
forest
. Если игрок попробует пойти в другом направлении, то получит в ответ «Этот путь недоступен».Таким образом мы добавили односторонний переход на восток, из
before_cottage
вforest
. В объектеforest
есть две строки:w_to before_cottage, ne_to clearing,
Первая строка вводит путь обратно на запад к объекту
before_cottage
(к домику), а вторая — на северо-восток на Полянку.В Информе есть 8 «горизонтальных» направлений:
n_to
на север,ne_to
на северо-восток,e_to
на восток,se_to
на юго-восток,s_to
на юг,sw_to
на юго-запад,w_to
на запад,nw_to
на северо-запад,а также два «вертикальных» направления
u_to
вверх,d_to
вниз и два дополнительных —in_to
внутрь иout_to
наружу.
Последнее что нужно добавить — это начальную локацию. В начале игры Хейди стоит перед своим домом, поэтому укажем, что игра начинается в before_cottage
. Делается это в функции Initialise
:
[ Initialise; location = before_cottage; ];
location
— это библиотечная переменная, в которой хранится текущее положение игрока. Здесь мы указываем, что в начале игры игрок должен находиться в комнате before_cottage
.
Теперь можно внести все эти изменения в наш изначальный шаблон и скомпилировать игру:
!=============================================================================
Constant Story "Хейди";
Constant Headline
"^Пример простой игры на Inform.
^Авторы: Роджер Фирт (Roger Firth) и Соня Кессерих (Sonja Kesserich).
^Перевод Юрия Салтыкова a.k.a. G.A. Garinson^";
Include "Parser";
Include "VerbLib";
!============================================================================
! Описание игровых объектов
Object before_cottage "Перед домом"
with description
"Ты стоишь около избушки, на восток от которой раскинулся лес.",
e_to forest,
has light;
Object forest "В лесной чаще"
with description
"На западе, сквозь густую листву, можно разглядеть небольшое строение.
Тропинка ведет на северо-восток.",
w_to before_cottage,
ne_to clearing,
has light;
Object clearing "Полянка"
with description
"Посреди полянки стоит высокий платан.
Тропинка вьется меж деревьев, уводя на юго-запад.",
sw_to forest,
u_to top_of_tree,
has light;
Object top_of_tree "На верхушке дерева"
with description "На этой высоте цепляться за ствол уже не так удобно.",
d_to clearing,
has light;
!============================================================================
! Функции инициализации
[ Initialise; location = before_cottage;];
!============================================================================
! Стандартные и расширенные грамматики
Include "RussiaG";
!============================================================================
В получившуюся игру можно поиграть. Конечно тут практически ничего нет, но можно побродить по локациям.
Добавление птицы и гнезда
Конечно же, птица и её гнездо тоже будут объектами в Информе. Опишем их:
Object bird "птенчик/"
with description "Слишком мал, чтобы летать, птенец беспомощно попискивает.",
has ;
Object nest "птичь/е гнезд/о"
with description "Гнездо сплетено из прутиков и аккуратно устлано мхом.",
has ;
Эти объекты записываются так же, как и комнаты ранее — у них есть идентификатор, название и описание. Описание комнаты выводится при входе в неё игрока, или если игрок пишет ОСМОТРЕТЬСЯ (или ОСМ, или просто О). Описание прочих объектов выводится, когда игрок вводит в игре ОСМОТРЕТЬ ПРЕДМЕТ (или ОСМ, или просто О). У этих объектов нет соединений, например e_to
или w_to
(они есть только у комнат) или свойства light
(оно не нужно, так как освещение предоставляют комнаты).
Слэши в названии объектов необходимы для того, чтобы при упоминании названий объектов в русскоязычной игре они верно склонялись. Отделять стоит окончание, так как оно изменяется при склонении.
Во время игры игрок будет обращаться к этим предметам, например, вводя ОСМОТРЕТЬ ПТЕНЧИКА или ВЗЯТЬ ГНЕЗДО. Чтобы это корректно работало, нужно перечислить слова, которые относятся к данному объекту. Важно указать различные синонимы, чтобы игроку было проще в игре.
Object bird "птенчик/"
with description "Слишком мал, чтобы летать, птенец беспомощно попискивает.",
name 'детеныш' 'птиц' 'птичк' 'птенчик' 'птенц' 'маленьк',
has ;
Object nest "птичь/е гнезд/о"
with description "Гнездо сплетено из прутиков и аккуратно устлано мхом.",
name 'птичь' 'гнезд' 'гнездышк' 'пруть' 'прутик' 'мох',
has ;
В секции name
идёт перечисление так называемых словарных (dictionary) слов в одинарных кавычках. В словарных словах нельзя использовать пробелы, запятые или точки, но сам список разделяется пробелами. Интерпретатор проверяет введённые игроком слова и сверяет их со списками словарных слов. Если игрок упоминает ПТИЧКУ, МАЛЕНЬКОГО ПТЕНЦА или ДЕТЕНЫША, значит он или она имеет в виду птенца (bird
), если ПТИЧЬЕ ГНЕЗДО или МОХ, то речь идёт про объект nest
. Если игрок введёт ГНЕЗДО ПТЕНЕЦ, то интерпретатор выведет сообщение о том, что не понимает, о чём идёт речь.
В списке нужно указывать слова, обрезая окончания, и стараться учесть все возможные уменьшительно-ласкательные формы и прилагательные, которые может ввести игрок.
Для комнат список name
не нужен, так как взаимодействие с ними происходит по-другому. Например, не нужно вводить ОСМОТРЕТЬ ЛЕС, достаточно ввести ОСМ.
Для гнезда нужно ввести дополнительную особенность — чтобы в неё можно было положить птенца. Для этого мы помечаем его как container
(контейнер), чтобы игрок мог ввести ПОЛОЖИТЬ ПТИЦУ В ГНЕЗДО. Также мы помечаем его как открытое — open
, так как по умолчанию контейнеры закрыты.
Кроме того, для верного склонения названий объектов необходимо указать их род. Гнездо — среднего рода, поэтому помечаем его как neuter
. Птенчик — мужского рода, помечаем как male
. Объекты с описанием женского рода помечаются как female
, а объекты множественного числа — plural
.
Object nest "птичь/е гнезд/о"
with description "Гнездо сплетено из прутиков и аккуратно устлано мхом.",
name 'птичь' 'гнезд' 'гнездышк' 'пруть' 'прутик' 'мох',
has container open neuter;
Теперь оба объекта готовы, и осталось ввести их в игру. Пусть птенец будет в лесу, а гнездо на поляне:
Object bird "птенчик/" forest
with description "Слишком мал, чтобы летать, птенец беспомощно попискивает.",
name 'детеныш' 'птиц' 'птичк' 'птенчик' 'птенц' 'маленьк',
has male;
Object nest "птичь/е гнезд/о" clearing
with description "Гнездо сплетено из прутиков и аккуратно устлано мхом.",
name 'птичь' 'гнезд' 'гнездышк' 'пруть' 'прутик' 'мох',
has container open neuter;
Первую строчку можно прочитать так: «описание объекта bird
с названием "птенчик", который изначально будет находиться в объекте forest
».
Размещать объекты в исходном файле игры можно где угодно, но удобнее всего это делать рядом с соответствующими комнатами.
Часть кода с объектами будет выглядеть так:
!============================================================================
! Описание игровых объектов
Object before_cottage "Перед домом"
with description
"Ты стоишь около избушки, на восток от которой раскинулся лес.",
e_to forest,
has light;
Object forest "В лесной чаще"
with description
"На западе, сквозь густую листву, можно разглядеть небольшое строение.
Тропинка ведет на северо-восток.",
w_to before_cottage,
ne_to clearing,
has light;
Object bird "птенчик/" forest
with description "Слишком мал, чтобы летать, птенец беспомощно попискивает.",
name 'детеныш' 'птиц' 'птичк' 'птенчик' 'птенц' 'маленьк',
has male;
Object clearing "Полянка"
with description
"Посреди полянки стоит высокий платан.
Тропинка вьется меж деревьев, уводя на юго-запад.",
sw_to forest,
u_to top_of_tree,
has light;
Object nest "птичь/е гнезд/о" clearing
with description "Гнездо сплетено из прутиков и аккуратно устлано мхом.",
name 'птичь' 'гнезд' 'гнездышк' 'пруть' 'прутик' 'мох',
has container open neuter;
Object top_of_tree "На верхушке дерева"
with description "На этой высоте цепляться за ствол уже не так удобно.",
d_to clearing,
has light;
Внесите эти изменения, скомпилируйте и запустите. В игре можно будет увидеть следующее:
В лесной чаще
На западе, сквозь густую листву, можно разглядеть небольшое
строение. Тропинка ведет на северо-восток.
Здесь имеется птенчик.
>
Дерево и ветка
В описании полянки есть высокий платан, на который игрок сможет залезть. Опишем его:
Object tree "высок/ий платан/" clearing
with description
"Величавое дерево стоит посреди поляны.
Кажется, по его стволу будет несложно влезть наверх.",
name 'высок' 'платан' 'дерев' 'ствол' 'величав',
has scenery male;
В этом описании нам уже всё знакомо, за исключением scenery
. Поскольку мы уже написали о дереве в описании локации, нам не нужно, чтобы в игре выводилось «Здесь имеется высокий платан». Для этого мы помечаем его как scenery
. Кроме того, scenery
запрещает игроку возможность подобрать объект.
И наконец ветка наверху дерева:
Object branch "надежн/ый толст/ый сук/" top_of_tree
with description "Сук достаточно ровный и крепкий, чтобы на нем надежно
держалось что-то не очень большое.",
name 'надежн' 'ровн' 'толст' 'крепк' 'сук' 'ветк',
has static supporter male;
Здесь встречаются два новых атрибута. static
аналогичен scenery
, то есть запрещает подбирать объект, но в отличие от scenery
всё равно выводит объект отдельно. supporter
аналогичен container
, но позволяет класть объекты не в, а на сук. (Позднее будет объяснено, что объект не может быть одновременно и container
, и supporter
).
Получаем следующее:
!============================================================================
! Описание игровых объектов
Object before_cottage "Перед домом"
with description
"Ты стоишь около избушки, на восток от которой раскинулся лес.",
e_to forest,
has light;
Object forest "В лесной чаще"
with description
"На западе, сквозь густую листву, можно разглядеть небольшое строение.
Тропинка ведет на северо-восток.",
w_to before_cottage,
ne_to clearing,
has light;
Object bird "птенчик/" forest
with description "Слишком мал, чтобы летать, птенец беспомощно попискивает.",
name 'детеныш' 'птиц' 'птичк' 'птенчик' 'птенц' 'маленьк',
has male;
Object clearing "Полянка"
with description
"Посреди полянки стоит высокий платан.
Тропинка вьется меж деревьев, уводя на юго-запад.",
sw_to forest,
u_to top_of_tree,
has light;
Object tree "высок/ий платан/" clearing
with description
"Величавое дерево стоит посреди поляны.
Кажется, по его стволу будет несложно влезть наверх.",
name 'высок' 'платан' 'дерев' 'ствол' 'величав',
has scenery male;
Object nest "птичь/е гнезд/о" clearing
with description "Гнездо сплетено из прутиков и аккуратно устлано мхом.",
name 'птичь' 'гнезд' 'гнездышк' 'пруть' 'прутик' 'мох',
has container open neuter;
Object top_of_tree "На верхушке дерева"
with description "На этой высоте цепляться за ствол уже не так удобно.",
d_to clearing,
has light;
Object branch "надежн/ый толст/ый сук/" top_of_tree
with description "Сук достаточно ровный и крепкий, чтобы на нем надежно
держалось что-то не очень большое.",
name 'надежн' 'ровн' 'толст' 'крепк' 'сук' 'ветк',
has static supporter male;
Вновь скомпилируйте игру, запустите её и проверьте, что можно сделать с объектами.
Завершение
Первый вариант игры почти готов, осталось внести два изменения. Во-первых, нельзя чтобы Хейди могла забраться на дерево держа в руках и птенца, и гнездо — нужно чтобы игрок сначала положил птенца в гнездо. Есть простой способ ввести это ограничение:
Constant Story "Хейди";
Constant Headline
"^Пример простой игры на Inform.
^Авторы: Роджер Фирт (Roger Firth) и Соня Кессерих (Sonja Kesserich).
^Перевод Юрия Салтыкова a.k.a. G.A. Garinson^";
Constant MAX_CARRIED 1;
Константа MAX_CARRIED
ограничивает количество предметов, которые могут быть одновременно в руках у игрока. Установив её равной 1, мы указываем что игрок может держать либо гнездо, либо птенца, но не оба одновременно. Однако это ограничение не учитывает содержимое container
'ов или supporter
'ов, поэтому птенец в гнезде считается за один объект.
Второе изменение чуть более сложное и более важное — сейчас нет способа «выиграть» игру, то есть пройти её. Цель игры — положить птенца в гнездо, подняться на верхушку дерева и положить гнездо на сук. Если это условие выполнено, то игра должна закончиться. Вот как можно это сделать:
Object branch "надежн/ый толст/ый сук/" top_of_tree
with description "Сук достаточно ровный и крепкий, чтобы на нем надежно
держалось что-то не очень большое.",
name 'надежн' 'ровн' 'толст' 'крепк' 'сук' 'ветк',
each_turn [; if (nest in branch) deadflag = 2; ],
has static supporter male;
Все нововведения будут рассмотрены подробнее в следующих главах. Рассмотрим их кратко.
Библиотечная переменная deadflag
обычно равна 0. Если присвоить ей значение 2, то интерпретатор заметит это и выведет сообщение «Вы выиграли». Строку
if (nest in branch) deadflag = 2;
можно прочитать так: «Проверить, находится ли объект nest
в объекте branch
(если branch
является container
) или на этом объекте (если branch
является supporter
) — если да, то присвоить deadflag
значение 2». Далее,
each_turn [; ... ],
стоит понимать как «В конце каждого хода (если игрок находится в той же комнате, где сук), выполнить то, что записано в квадратных скобках». В итоге получаем:
-
В конце каждого хода (когда игрок ввёл команду, нажал Enter, и интерпретатор выполнил эту команду), интерпретатор проверяет, находится ли игрок в той же комнате, где находится
branch
. Если нет, то ничего не выполняется. Если да, то интерпретатор проверяет, где находитсяnest
. Изначально гнездо на полянке, поэтому ничего не происходит. -
Также, в конце каждого хода интерпретатор проверят значение
deadflag
. Обычно оно равно 0, поэтому ничего не происходит. -
Затем игрок кладёт гнездо на сук. Интерпретатор видит это и устанавливает
deadflag
равным 2. -
Сразу же после этого интерпретатор видит, что
deadflag
равен 2, то есть, что игра закончена, и выводит на экран «Вы выиграли».
На этом глава закончена, перекомпилируйте игру и проверьте, как работают внесённые изменения. Многое можно сделать получше, и это будет рассмотрено позднее. В следующей главе будет обобщение уже изученных особенностей Информа.