This repository has been archived on 2019-04-06. You can view files and clone it, but cannot push or open issues or pull requests.
adventin/doc/writing_games.txt
2009-08-26 05:25:53 +00:00

965 lines
56 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

== 0. Общие сведения ==
Код игр для STEAD пишется на lua (5.1), поэтому, знание этого языка полезно, хотя и не необходимо. Код движка на lua занимает всего ~900 строк и лучшей документацией, конечно, является изучение его кода.
Главное окно игры содержит информацию о статической и динамической части сцены, активные события и картинку сцены с возможными переходами в другие сцены (в графическом интерпретаторе).
Динамическая часть сцены составлена из описаний объектов сцены, она отображается всегда.
Статическая часть сцены отображается только один раз, при показе сцены, или при повторении команды look (в графическом интерпретаторе -- клик на названии сцены).
Игроку доступны объекты доступные на любой сцене -- инвентарь. Игрок может взаимодействовать с объектами инвентаря и действовать объектами инвентаря на другие объекты сцены или инвентаря.
Следует отметить, что понятие инвентаря является условным. Например, в "инвентаре" могут находиться такие объекты как "открыть", "осмотреть", "использовать" и т.д.
Действиями игрока могут быть:
* осмотр сцены;
* действие на объект сцены;
* действие на объект инвентаря;
* действие объектом инвентаря на объект сцены;
* действие объектом инвентаря на объект инвентаря;
* переход в другую сцену;
Игра представляет из себя каталог, в котором должен находиться скрипт main.lua. Другие ресурсы игры (скрипты на lua, графика и музыка) должны находиться в рамках этого каталога. Все ссылки на ресурсы делаются относительно текущего каталога -- каталога игры.
В начале файла main.lua может быть определен заголовок, состоящий из тегов. Теги должны начинаться с символов '--': комментарий с точки зрения lua. На данный момент существует один тег: $Name:, который должен содержать название игры в кодировке UTF8. Пример использования тега:
{{{
-- $Name: Самая интересная игра!$
}}}
Графический интерпретатор ищет доступные игры в каталоге games. Unix версия интерпретатора кроме этого каталога просматривает также игры в каталоге ~/.instead/games.
== 1. Сцена ==
Сцена -- это единица игры, в рамках которой игрок может изучать все объекты сцены и взаимодействовать с ними. В игре должна быть хотя бы одна сцена с именем main.
{{{
main = room {
nam = 'главная комната',
dsc = 'Вы в большой комнате.',
};
}}}
Запись означает создание объекта main типа room. У каждого объекта игры есть атрибуты и обработчики. Например, атрибут nam (имя) является необходимым для любого объекта.
Атрибут nam для сцены это то, что будет заголовком сцены при ее отображении. Имя сцены также используется для ее идентификации при переходах.
Атрибут dsc это описание статической части сцены, которое выводится один раз при входе в сцену или явном выполнении команды look.
Внимание!!! Если для вашего творческого замысла необходимо, чтобы описание статической части сцены выводилось каждый раз, вы можете определить для своей игры параметр forcedsc (в начале игры).
{{{
game.forcedsc = true;
}}}
Или, аналогично, задать атрибут forcedsc для конкретных сцен.
Для длинных описаний удобно использовать запись вида:
{{{dsc = [[ Очень длинное описание... ]],}}}
При этом переводы строк игнорируются. Если вы хотите, чтобы в выводе описания сцены присутствовали абзацы -- используйте символ ^.
{{{
dsc = [[ Первый абзац. ^^
Второй Абзац.^^
Третий абзац.^
На новой строке.]],
}}}
== 2. Объекты ==
Объекты -- это единицы сцены, с которыми взаимодействует игрок.
{{{
tabl = obj {
nam = 'стол',
dsc = 'В комнате стоит {стол}.',
act = 'Гм... Просто стол...',
};
}}}
Имя объекта nam используется при попадании его в инвентарь а также в текстовом интерпретаторе для адресации объекта.
dsc -- описатель объекта. Он будет выведен в динамической части сцены. Фигурными скобками отображается фрагмент текста, который будет являться ссылкой в графическом интерпретаторе.
act -- это обработчик, который вызывается при действии пользователя (действие на объект сцены). Его задача -- возвращение строки текста, которая станет частью событий сцены.
ВНИМАНИЕ: в пространстве имен lua уже существуют некоторые объекты (таблицы), например: table, io, string... Будьте внимательны, при создании объекта. Например, в приведенном примере используется tabl, а не table.
== 3. Добавляем объекты в сцену ==
Ссылкой на объект называется текстовая строка, содержащая имя объекта при его создании. Например: 'tabl' -- ссылка на объект tabl.
Для того, чтобы поместить в сцену объекты, нужно определить массив obj, состоящий из ссылок на объекты:
{{{
main = room {
nam = 'главная комната',
dsc = 'Вы в большой комнате.',
obj = { 'tabl' },
};
}}}
Теперь, при отображении сцены мы увидим объект стол в динамической части.
== 4. Объекты связанные с объектами ==
Объекты тоже могут содержать атрибут obj. При этом, список будет последовательно разворачиваться. Например, поместим на стол яблоко.
{{{
apple = obj {
nam = 'яблоко',
dsc = 'На столе лежит {яблоко}.',
act = 'Взять что-ли?',
};
tabl = obj {
nam = 'стол',
dsc = 'В комнате стоит {стол}.',
act = 'Гм... Просто стол...',
obj = { 'apple' },
};
}}}
При этом, в описании сцены мы увидим описание объектов стол и яблоко, так как apple -- связанный с tabl объект.
== 5. Атрибуты и обработчики как функции ==
Большинство атрибутов и обработчиков могут быть также функциями. Так, например:
{{{
nam = function()
return 'яблоко';
end,
}}}
Это синоним записи: nam = 'яблоко';
Функции сильно расширяют возможности STEAD, например:
{{{
apple = obj {
nam = 'яблоко',
dsc = function(s)
if not s._seen then
return 'На столе {что-то} лежит.';
else
return 'На столе лежит {яблоко}.';
end
end,
act = function(s)
if s._seen then
return 'Это яблоко!';
else
s._seen = true;
return 'Гм... Это же яблоко!';
end
end,
};
}}}
Если атрибут или обработчик оформлен как функция, то обычно первый аргумент функции (s) сам объект. В данном примере, при показе сцены будет в динамической части сцены будет текст: 'На столе что-то лежит'. При взаимодействии с 'что-то', переменная `_seen` объекта apple будет установлена в true и мы увидим, что это было яблоко.
Зная lua, можно упростить запись:
{{{
function sw(v, a, b)
if v then
return a;
end
return b
end
apple = obj {
nam = function(s)
return sw(not s._seen, 'нечто','яблоко');
end,
dsc = function(s)
return sw(not s._seen,'На столе {что-то} лежит.', 'На столе лежит {яблоко}.');
end,
act = function(s)
if s._seen then
return 'Это яблоко!';
else
s._seen = true;
return 'Гм... Это же яблоко!';
end
end,
};
}}}
И в дальнейшем всегда использовать функцию sw (или какую-либо другую, вспомогательную функцию).
Запись `s._seen` означает, что переменная `_seen` размещена в объекте s (то-есть apple). Подчеркивание означает, что эта переменная попадет в файл сохранения игры. Начиная с версии интерпретатора 0.7.7 в файл сохранения игры попадают также переменные, название которых начинается с большой буквы.
Внимание!!! Переменные в любом случае не записываются в файл сохранения, если они не размещены в одном из перечисленных типов объектов: комната, объект, игра, игрок.
== 6. Инвентарь ==
Простейший вариант сделать объект, который можно брать -- определить обработчик tak.
Например:
{{{
apple = obj {
nam = 'яблоко',
dsc = 'На столе лежит {яблоко}.',
inv = function()
inv():del('apple');
return 'Я съел яблоко.';
end,
tak = 'Вы взяли яблоко.',
};
}}}
При этом, при действии игрока на объект яблоко -- яблоко будет убрано из сцены и добавлено в инвентарь. При действии игрока на инвентарь -- вызывается обработчик inv.
В нашем примере -- при действии игроком на яблоко в инвентаре - яблоко будет съедено.
== 7. Переходы между сценами ==
Для перехода между сценами используется атрибут сцены -- список way.
{{{
room2 = room {
nam = 'зал',
dsc = 'Вы в огромном зале.',
way = { 'main' },
};
main = room {
nam = 'главная комната',
dsc = 'Вы в большой комнате.',
obj = { 'tabl' },
way = { 'room2' },
};
}}}
При этом, вы сможете переходить между сценами main и room2. Как вы помните, nam может быть функцией, и вы можете генерировать имена сцен на лету, например, если вы хотите, чтобы игрок не знал название сцены, пока не попал на нее.
При переходе между сценами движок вызывает обработчик exit из текущей сцены и enter в той сцены, куда идет игрок. Например:
{{{
room2 = room {
enter = 'Вы заходите в зал.',
nam = 'зал',
dsc = 'Вы в огромном зале.',
way = { 'main' },
exit = 'Вы выходите из зала.',
};
}}}
exit и enter могут быть функциями. Тогда первый параметр это (как всегда) сам объект, а второй это ссылка на комнату куда игрок хочет идти (для exit) или из которой уходит (для enter). Например:
{{{
room2 = room {
enter = function(s, f)
if f == 'main' then
return 'Вы пришли из комнаты.';
end
end,
nam = 'зал',
dsc = 'Вы в огромном зале.',
way = { 'main' },
exit = function(s, t)
if t == 'main' then
return 'Я не хочу назад!', false
end
end,
};
}}}
Как видим, обработчики могут возвращать два значения: строку и статус. В нашем примере функция exit вернет false, если игрок попытается уйти из зала в main комнату. false означает, что переход не будет выполнен. Такая же логика работает и для enter. Кроме того, она работает и для обработчика tak.
== 8. Действие объектов друг на друга ==
Игрок может действовать объектом инвентаря на другие объекты. При этом вызывается обработчик use у объекта которым действуют и used -- на которого действуют.
Например:
{{{
knife = obj {
nam = 'нож',
dsc = 'На столе лежит {нож}',
inv = 'Острый!',
tak = 'Я взял нож!',
use = 'Вы пытаетесь использовать нож.',
};
tabl = obj {
nam = 'стол',
dsc = 'В комнате стоит {стол}.',
act = 'Гм... Просто стол...',
obj = { 'apple', 'knife' },
used = 'Вы пытаетесь сделать что-то со столом...',
};
}}}
Если игрок возьмет нож и использует его на стол -- то увидит текст обработчиков use и used. use и used могут быть функциями. Тогда первый параметр это сам объект,
а второй -- ссылка на объект на который направлено действие в случае use и объект, которым действие осуществляется в случае used.
use может вернуть статус false, в этом случае обработчик used не вызовется (если он вообще был). Статус обработчика used -- игнорируется.
Пример:
{{{
knife = obj {
nam = 'нож',
dsc = 'На столе лежит {нож}',
inv = 'Острый!',
tak = 'Я взял нож!',
use = function(s, w)
if w ~= 'tabl'
return 'Не хочу это резать.', false
else
return 'Вы вырезаете на столе свои инициалы.';
end
};
}}}
Нож можно использовать только на стол.
== 9. Объект игрок ==
Игрок в STEAD представлен объектом pl. Тип объекта -- player. В движке объект создается следующим образом:
{{{
pl = player {
nam = "Incognito",
where = 'main',
obj = { }
};
}}}
Атрибут obj представляет собой инвентарь игрока.
== 10. Объект game ==
Игра также представлена объектом game с типом game. В движке он определяется следующим образом:
{{{
game = game {
nam = "INSTEAD -- Simple Text Adventure interpreter v"..version.." '2009 by Peter Kosyh",
dsc = [[
Commands:^
look(or just enter), act <on what> (or just what), use <what> [on what], go <where>,^
back, inv, way, obj, quit, save <fname>, load <fname>.]],
pl ='pl',
showlast = true,
};
}}}
Как видим, объект хранит в себе указатель на текущего игрока ('pl') и некоторые параметры. Например, вы можете указать в начале своей игры кодировку текста следующим образом:
{{{ game.codepage="UTF-8"; }}}
Поддержка произвольных кодировок изначально присутствует в UNIX версии интерпретатора, в windows версии -- начиная с 0.7.7.
Кроме того, объект game может содержать обработчики по умолчанию act, inv, use, которые будут вызваны, если в результате действий пользователя не будут найдены никакие другие обработчики. Например, вы можете написать в начале игры:
{{{
game.act = 'Не получается.';
game.inv = 'Гм.. Странная штука..';
game.use = 'Не сработает...';
}}}
== 11. Атрибуты - списки ==
Атрибуты списки (такие как way или obj) позволяют работать с собой, таким образом позволяя реализовать динамически определяемые переходы между сценами, живые объекты и т.д.
Методы списков: add, del, look, srch. Из них наиболее часто используемые: add и del.
add - добавляет в список. del -- удаляет из него. srch -- выполняет поиск объекта. Следует отметить, что параметром del и srch может быть не только сам объект или идентификатор объекта, но и имя объекта.
Начиная с версии 0.8 параметром add может быть сам объект. Кроме того, с этой версии добавляется необязательный второй параметр -- позиция в списке. Начиная с версии 0.8 вы можете также выполнять модификацию списка по индексу с помощью метода set. Например:
{{{
objs():set('knife',1);
}}}
Выше, вы уже видели пример со съеденным яблоком, там использовалась конструкция inv():del('apple');
inv() -- это функция, которая возвращает список инвентаря. del после ':'-- метод, удаляющий элемент инвентаря.
Аналогично, собственная реализация tak может быть такой:
{{{
knife = obj {
nam = 'нож',
dsc = 'На столе лежит {нож}',
inv = 'Острый!',
act = function(s)
objs():del('knife');
inv():add('knife');
end,
};
}}}
Кроме добавления, удаления объектов из списков вы можете использовать выключение/включение объектов с помощью методов enable() и disable(). Например: knife:disable(). При этом объект knife пропадает из описания сцены, но в последствии может быть опять быть включен, с помощью knife:enable().
== 12. Функции, которые возвращают объекты ==
В STEAD определены некоторые функции, которые возвращают наиболее часто используемые объекты. Например:
* inv() возвращает список инвентаря;
* objs() возвращает список объектов текущей сцены; (начиная с 0.8.5 -- необязательный параметр -- сцена, для которой возвращается список);
* ways() возвращает список возможных переходов из текущей сцены; (начиная с 0.8.5 -- необязательный параметр -- сцена, для которой возвращается список);
* me() возвращает объект-игрок;
* here() возвращает объект текущую сцену; (начиная с 0.8.5 -- еще одна функция where() -- возвращает текущую сцену как строку-имя объекта, а не сам объект)
* from() возвращает объект прошлой сцены;
Комбинируя эти функции с методами add, del можно динамически менять сцену, например:
{{{
ways:add('nexroom'); -- добавить переход на новую сцену;
}}}
{{{
objs():add('chair'); -- добавить объект в текущую сцену;
}}}
Еще одна функция, которая получает объект по ссылке:
ref().
Например, мы можем добавить объект в локацию 'home' следующим образом:
{{{
ref('home').obj:add('chair');
}}}
Впрочем, следующая более простая запись тоже является корректной:
{{{
home.obj:add('chair');
}}}
Или, для версии >=0.8.5:
{{{
objs('home'):add('chair');
}}}
или, наконец:
{{{
put('chair', 'home');
}}}
Начиная с 0.8.5 -- deref(o), возвращает ссылку-строку для объекта;
== 13. Некоторые вспомогательные функци. ==
В STEAD определены некоторые высокоуровневые функции, которые могут оказаться полезными при написании игры.
have() -- проверяет, есть ли объект в инвентаре. По имени объекта или по атрибуту nam объекта. Например:
{{{
...
act = function(s)
if have('knfie') then
return 'Но у меня же есть нож!';
end
end
...
}}}
move(o, w) -- переносит объект из текущей сцены в другую:
{{{ move('mycat','inmycar');}}}
Если вы хотите перенести объект из произвольной сцены, вам придется удалить его из старой сцены с помощью метода del. Для создания сложно перемещающихся объектов, вам придется написать свой метод, который будет сохранять текущую позицию объекта в самом объекте и делать удаление объекта из старой сцены. Вы можете указать исходную позицию (комнату) объекта в качестве третьего параметра move.
{{{ move('mycat','inmycar', 'forest'); }}}
Начиная с версии 0.8 присутствует также функция movef, аналогичная move, но добавляющая объект в начало списка.
seen(o) -- если объект присутствует в текущей сцене:
{{{
if seen('mycat') then
move('mycat','inmycar');
end
}}}
drop(o) -- положить объект из инвентаря на сцену:
drop('knife');
Начиная с версии 0.8 присутствует также функция dropf, аналогичная drop, но добавляющая объект в начало списка. Начиная с версии 0.8.5 второй необязательный параметр -- комната, куда помещается предмет. Кроме того, для версий >=0.8.5 доступна функция put, которая не удаляет предмет из инвентаря.
take(o) -- взять объект.
take('knife');
Начиная с версии 0.8.5 второй необязательный параметр -- комната, с которой берется предмет.
taken(o) -- если объект взят -- вернет true (взят с помощью tak или take());
rnd(m) -- случайное значение от 1 до m.
goto(w) -- перейти в сцену w, при этом обработчику нужно вернуть возвращаемое значение goto, так как goto вернет описание новой сцены или сообщенмие о том, что переход невозможен, например:
change_pl(p) -- переключиться на другого игрока (со своим инвентарем и позицией). При этом функция возвращает описание сцены нового игрока и это возвращаемое значение должно быть передано из обработчика (см. goto()).
{{{
mycar = obj {
nam = 'моя машина',
dsc = 'Перед хижиной стоит мой старенький {пикап} Toyota.',
act = function(s)
return goto('inmycar');
end
};
}}}
back() -- goto в предыдущую сцену.
time() -- возвращает текущее время игры. Время игры считается в активных действиях.
cat(...) -- возвращает строку -- склейку строк-аргументов. Если первый аргумент nil, то функция возвращает nil.
par(...) -- возвращает строку -- склейку строк-аргументов, разбитых строкой-первым параметром.
== 14. Диалоги ==
Диалоги это сцены, содержащие объекты -- фразы. Например, простейший диалог может выглядеть следующим образом.
{{{
povardlg = dlg {
nam = 'на кухне',
dsc = 'Передо мной полное лицо женщины - повара в белом колпаке и усталым взглядом...',
obj = {
[1] = phr('Мне вот-этих зелененьких... Ага -- и бобов!', 'На здоровье!'),
[2] = phr('Картошку с салом, пожалуйста!', 'Приятного аппетита!'),
[3] = phr('Две порции чесночного супа!!!', 'Прекрасный выбор!'),
[4] = phr('Мне что-нибудь легонькое, у меня язва...', 'Овсянка!'),
},
};
}}}
phr -- создание фразы. Фраза содержит вопрос, ответ и реакцию (реакция в данном примере отсутствует). Когда игрок выбирает одну из фраз, фраза отключается. Когда все фразы отключатся диалог заканчивается. Реакция -- это строка кода на lua который выполнится после отключения фразы. Например:
{{{
food = obj {
nam = 'еда',
inv = function (s)
inv():del('food');
return 'Я ем.';
end
};
gotfood = function(w)
inv():add('food');
food._num = w;
return back();
end
povardlg = dlg {
nam = 'на кухне',
dsc = 'Передо мной полное лицо женщины - повара в белом колпаке и усталым взглядом...',
obj = {
[1] = phr('Мне вот-этих зелененьких... Ага -- и бобов!', 'На здоровье!', [[pon(1); return gotfood(1);]]),
[2] = phr('Картошку с салом, пожалуйста!', 'Приятного аппетита!', [[pon(2); return gotfood(2);]]),
[3] = phr('Две порции чесночного супа!!!', 'Прекрасный выбор!', [[pon(3);return gotfood(3);]]),
[4] = phr('Мне что-нибудь легонькое, у меня язва...', 'Овсянка!', [[pon(4); return gotfood(4);]]),
},
};
}}}
В данном примере, игрок выбирает еду. Получае ее (запомнив выбор в переменной food._num) и возвращается обратно (в ту сцену откуда попал в диалог).
В реакции может быть любой lua код, но в STEAD определены наиболее часто используемые функции:
pon(n..) -- включить фразы диалога с номерами n... (в нашем примере -- чтобы игрок мог повторно взять еду того-же вида).
poff(n...) -- выключить фразы диалога с номерами n...
prem(n...) -- удалить (заблокировать) фразы диалога с номерами n... (удаление означает невозможность включения фраз. pon(n..) не приведет к включению фраз).
Переход в диалог осуществляется как переход на сцену:
{{{
povar = obj {
nam = 'повар',
dsc = 'Я вижу {повара}.',
act = function()
return goto('povardlg');
end,
};
}}}
Вы можете переходить из одного диалога в другой диалог -- организовывая иерархические диалоги.
Также, вы можете прятать некоторые фразы при инициализации диалога и показывать их при некоторых условиях.
{{{
facectrl = dlg {
nam = 'фэйсконтроль',
dsc = 'Я вижу перед собой неприятное лицо полного охранника.',
obj = {
[1] = phr('Я пришел послушать лекцию Белина...',
'-- Я не знаю кто вы -- ухмыляется охранник -- но мне велели пускать сюда только приличных людей.',
[[pon(2);]]),
[2] = _phr('У меня есть приглашение!',
'-- А мне плевать! Посмотри на себя в зеркало!!! Ты пришел слушать самого Белина -- правую руку самого... -- охранник почтительно помолчал -- Так что пошел вон..', [[pon(3,4)]]),
[3] = _phr('Сейчас я дам тебе по роже!', '-- Ну все... Мощные руки выталкивают меня в коридор...',
[[poff(4)]]),
[4] = _phr('Ты, кабан! Я же тебе сказал -- у меня есть приглашение!',
'-- Чтоооооо? Глаза охранника наливаются кровью... Мощный пинок отправляет меня в коридор...',
[[poff(3)]]),
},
exit = function(s,w)
s:pon(1);
end,
};
}}}
`_phr` -- создает выключенную фразу, которую можно включить. Данный пример показывает также возможность использования методов pon, poff, prem для диалога (см. exit).
Вы можете включать/выключать фразы не только текущего, но и произвольного диалога, с помощью методов объекта диалог pon/poff. Например: shopman:pon(5);
== 15. Облегченные объекты ==
Иногда, сцену нудно наполнить декорациями, которые обладают ограниченной функциональностью, но делают игру разнообразней. Для этого можно использовать облегченный объект. Например:
{{{
sside = room {
nam = 'южная сторона',
dsc = [[Я нахожусь у южной стены здания института. ]],
act = function(s, w)
if w == 1 then
ways():add('stolcorridor');
return "Я подошел к подъезду. На двери подъезда надпись -- 'Столовая'. Хм -- зайти внутрь?";
end
if w == 2 then
return 'Те, кто выходят, выглядят более довольными...';
end
end,
obj = { vobj(1, "подъезд", "У восточного угла находится небольшой {подъезд}."),
vobj(2, "люди", "Время от времени дверь подъезда хлопает впуская и выпуская {людей}.")},
};
}}}
Как видим, vobj позволяет сделать легкую версию статического объекта, с которым тем не менее можно взаимодействовать (за счет определения обработчика act в сцене и анализа ключа объекта). vobj также вызывает метод used, при этом в качестве третьего параметра передается объект, воздействующий на виртуальный объект.
Синтаксис vobj: vobj(ключ, имя, описатель); где ключ -- это цифра, которая будет передана обработчикам act/used сцены как второй параметр.
Существует модификация объекта vobj -- vway. vway реализует ссылку.
Синтаксис vway: vway(имя, описатель, сцена назначения); например:
{{{
obj = { vway("дальше", "Нажмите {здесь}.", 'nextroom') }
}}}
Вы можете динамически заполнять сцену объектами vobj или vway с помощью методов add и del. Например:
{{{
home.objs:add(vway("next", "{Дальше}.", 'next_room');
-- some code here
home.objs:del("next");
}}}
Определена также упрощенная сцена vroom.
Синтаксис: vroom(имя перехода, сцена назначения). Например:
{{{
home.objs:add(vroom("идти на запад", 'mountains');
}}}
== 16. Динамические события ==
Вы можете определять обработчики, которые выполняются каждый раз, когда время игры увеличивается на 1. Например:
{{{
mycat = obj {
nam = 'Барсик',
lf = {
[1] = 'Барсик шевелится у меня за пазухой.',
[2] = 'Барсик выглядывает из за пазухи.',
[3] = 'Барсик мурлычит у меня за пазухой.',
[4] = 'Барсик дрожит у меня за пазухой.',
[5] = 'Я чувствую тепло Барсика у себя за пазухой.',
[6] = 'Барсик высовывает голову из за пазухи и осматривает местность.',
},
life = function(s)
local r = rnd(6);
if r > 2 then
return;
end
r = rnd(6);
return s.lf[r];
end,
....
profdlg2 = dlg {
nam = 'Белин',
dsc = 'Белин бледен. Он смотрит на дробовик рассеянным взглядом.',
obj = {
[1] = phr('Я пришел за своим котом.',
'Я выхватываю Барсика из руки Белина и засовываю себе за пазуху.',
[[inv():add('mycat'); lifeon('mycat')]]),
....
}}}
Любой объект или сцена могут иметь свой обработчик life, который вызывается каждый раз при смене текущего времени игры, если объект или сцена были добавлены в список живых объектов с помощью lifeon. Не забывайте удалять живые объекты из списка с помощью lifeoff, когда они больше не нужны. Это можно сделать, например, в обработчике exit, или любым другим способом.
== 17. Графика и музыка ==
Графический интерпретатор анализирует атрибут сцены pic, и воспринимает его как путь к картинке, например:
{{{
home = room {
pic = 'gfx/home.png',
nam = 'дома',
dsc = 'Я у себя дома',
};
}}}
Конечно, pic может быть функцией, расширяя возможности разработчика.
Если в текущей сцене не определен атрибут pic, то берется атрибут game.pic. Если не определен и он, то картинка не отображается.
Интерпретатор проигрывает в цикле текущую музыку, которая задается с помощью функции:
set_music(имя музыкального файла).
Например:
{{{
street = room {
pic = 'gfx/street.png',
enter = function()
set_music('mus/rain.ogg');
end,
nam = 'на улице',
dsc = 'На улице идет дождь.',
};
}}}
get_music() возвращает текущее имя трека.
Начиная с версии 0.7.7 в функцию set_music() можно передавать второй параметр -- количество проигрываний. Получить текущий счетчик можно с помощью get_music_loop. 0 - означает вечный цикл. 1..n -- количество проигрываний. -1 -- проигрывание текущего трека закончено.
== 18. Полезные советы ==
=== Статус игрока ===
Код движка расположен в файле stead.lua. В своей игре вы можете переопределять любую функцию или объект lua, добиваясь того, что нужно для вашего творческого замысла. Кроме того, полезно знать особенности работы движка. Например, ниже представлена реализация статуса игрока в виде текста, который появляется в инвентаре, но не может быть выбран.
{{{
pl.Life = 10;
pl.Power = 10;
status = obj {
nam = 'Жизнь: '..pl.Life..',Сила: '..pl.Power,
};
inv():add('status');
status.object_type = nil
}}}
=== goto из обработчика exit ==
Если вы выполните goto из обработчика exit, то получите переполнение стека, так как goto снова и снова будет вызывать метод exit. Вы можете избавиться от этого, если вставите проверку, разрушающую рекурсию. Например:
{{{
exit = function(s, t)
if t == 'dialog' then return; end
return goto('dialog');
}}}
Вы можете также делать goto из обработчиков enter.
=== Динамически создаваемые ссылки. ==
Динамически создаваемые ссылки могут быть реализованы разным способом. Ниже приводится пример, основанный на использовании объектов vway. Для добавления ссылки можно использовать запись:
{{{
home.obj:add(vway('Дорога', 'Я заметил {дорогу}, ведущую в лес...', 'forest'));
}}}
Для удаления ссылки можно использовать метод del.
{{{
home.obj:del('Дорога');
}}}
Для определения наличия ссылки в сцене -- метод srch.
{{{
if not home.obj:srch('Дорога') then
home.obj:add(vway('Дорога', 'Я заметил {дорогу}, ведущую в лес...', 'forest'));
end
}}}
Динамические ссылки удобно создавать в обработчике enter, или по мере необходимости в любом месте кода игры. Если ссылки создаются в текущей сцене, то последний пример можно упростить:
{{{
if not seen('Дорога') then
objs():add(vway('Дорога', 'Я заметил {дорогу}, ведущую в лес...', 'forest'));
end
}}}
Кроме того, вы можете просто включать и выключать ссылки с помощью enable(), disable(), например:
{{{
objs()[1]:disable();
}}}
Вы можете создавать выключенные vobj и vway следующим образом:
{{{
obj = {vway('Дорога', 'Я заметил {дорогу}, ведущую в лес...', 'forest'):disable()},
}}}
И затем включать их по индексу в массиве obj:
{{{
objs()[1]:enable();
}}}
=== Компиляция игр ===
Если вы не хотите показывать исходный код своих игр, вы можете откомпилировать исходный код компилятором luac. Правда, при этом главный файл main.lua желательно оставлять текстовым, так его заголовок сканируется интерпретатором при выборе игры. Таким образом схема выглядит следующим образом (game.lua -- скомпилированный lua код):
main.lua
{{{
-- $Name: Моя закрытая игра!$
dofile("game.lua");
}}}
Кроме того, компиляция игр может быть использована для поиска ошибок в коде.
=== Переключение между игроками ===
Вы можете создать игру с несколькими персонажами и время от времени переключаться между ними (см. switch_pl). Но вы можете также использовать этот трюк для того, что бы иметь возможность переключаться между разными типами инвентаря.
=== Использование первого параметра обработчика ===
Пример кода.
{{{
knife = obj {
nam = 'камень',
dsc = 'На краю лежит {камень}.',
act = function()
objs():del('knife');
return 'Я толкнул камень, он сорвался и улетел вниз...';
end
}}}
Обработчик act мог бы выглядеть проще:
{{{
act = function(s)
objs():del(s);
return 'Я толкнул камень, он сорвался и улетел вниз...';
end
}}}
=== Использование set_music ===
Вы можете использовать set_music для проигрывания звуков, задавая второй параметр -- счетчик циклов проигрывания звукового файла.
Вы можете написать для игры свой проигрыватель музыки, создав его на основе живого объекта, например:
{{{
-- играет треки в случайном порядке, начиная со 2-го
tracks = {"mus/astro2.mod", "mus/aws_chas.xm", "mus/dmageofd.xm", "mus/doomsday.s3m"}
mplayer = obj {
nam = 'плеер',
life = function(s)
local n = get_music();
local v = get_music_loop();
if not n or not v then
set_music(tracks[2], 1);
elseif v == -1 then
local n = get_music();
while get_music() == n do
n = tracks[rnd(4)]
end
set_music(n, 1);
end
end,
};
lifeon('mplayer');
}}}
Вы можете использовать функции get_music_loop и get_music, для того, чтобы запоминать прошлую мелодию, и потом восстанавливать ее, например:
{{{
function save_music(s)
s.OldMusic = get_music();
s.OldMusicLoop = get_music_loop();
end
function restore_music(s)
set_music(s.OldMusic, s.OldMusicLoop);
end
-- ....
enter = function(s)
save_music(s);
end,
exit = function(s)
restore_music(s);
end,
-- ....
}}}
Начиная с версии 0.8.5 функции save_music и restore_music уже присутствуют в библиотеке.
=== Живые объекты ===
Если вашему герою нужен друг, одним из способов может стать метод life этого персонажа, который всегда переносит объект в локацию игрока:
{{{
horse = obj {
nam = 'лощадь',
dsc = 'Рядом со мной стоит {лошадь}.',
life = function(s)
if not seen('horse') then
move('horse', here(), s.__where);
s.__where = pl.where;
end
end,
};
lifeon('horse');
}}}
=== Отладка ===
Для того, чтобы во время ошибки увидеть стек вызовов функций lua, вы можете запустить sdl-instead с параметром -debug. При этом в windows версии интерпретатора будет создана консоль отладки.
Вы можете отлаживать свою игру вообще без instead. Например, вы можете создать следующий файл game.lua:
{{{
dofile("/usr/share/games/stead/stead.lua"); -- путь к stead.lua
dofile("main.lua"); -- ваша игра
game:ini();
iface:shell();
}}}
И запустите игру в lua: lua game.lua.
При этом игра будет работать в примитивном shell окружении. Полезные команды: ls, go, act, use....
== 19. Темы для sdl-instead ==
Графический интерпретатор поддерживает механизм тем. Тема представляет из себя каталог, с файлом theme.ini внутри.
Тема, которая является минимально необходимой -- это тема default. Эта тема всегда загружается первой. Все остальные темы наследуются от нее и могут частично или полностью заменять ее параметры. Выбор темы осуществляется пользователем через меню настроек, однако конкретная игра может содержать собственную тему и таким образом влиять на свой внешний вид. В этом случае в каталоге с игрой должен находиться свой файл theme.ini. Тем не-менее пользователь свободен отключить данный механизм, при этом интерпретатор будет предупреждать о нарушении творческого замысла автора игры.
Синтаксис theme.ini очень прост.
<параметр> = <значение>
или
; комментарий
Значения могут быть следующих типов: строка, цвет, число.
Цвет задается в форме #rgb, где r g и b компоненты цвета в шестнадцатеричном виде. Кроме того некоторые основные цвета распознаются по своим именам. Например: yellowgreen, или violet.
Параметры могут принимать значения:
scr.w = ширина игрового пространства в пикселях (число)
scr.h = высота игрового пространства в пикселях (число)
scr.col.bg = цвет фона
scr.gfx.bg = путь к картинке фонового изображения (строка)
scr.gfx.use = путь к картинке-индикатору режима использования (строка)
scr.gfx.pad = размер отступов к скролл-барам и краям меню (число)
scr.gfx.x, scr.gfx.y, scr.gfx.w, scr.gfx.h = координаты, ширина и высота окна изображений. Области в которой располагается картинка сцены. Интерпретация зависит от режима расположения (числа)
win.gfx.h - синоним scr.gfx.h (для совместимости)
scr.gfx.mode = режим расположения (строка fixed, embedded или float). Задает режим изображения. embedded -- картинка является частью содержимого главного окна, параметры win.x, win.y, win.w игнорируются. float -- картинка расположена по указанным координатам (win.x, win.y) и масштабируется к размеру win.w x win.h если превышает его. fixed -- картинка является частью сцены как в режиме embedded, но не скроллируется вместе с текстом а расположена непосредственно над ним.
win.x, win.y, win.w, win.h = координаты, ширина и высота главного окна. Области в которой располагается описание сцены (числа)
win.fnt.name = путь к файлу-шрифту (строка)
win.fnt.size = размер шрифта главного окна (размер)
win.gfx.up, win.gfx.down = пути к файлам-изображениям скорллеров вверх/вниз для главного окна (строка)
win.col.fg = цвет текста главного окна (цвет)
win.col.link = цвет ссылок главного окна (цвет)
win.col.alink = цвет активных ссылок главного окна (цвет)
inv.x, inv.y, inv.w, inv.h = координаты, высота и ширина области инвентаря. (числа)
inv.mode = строка режима инвентаря (horizontal или vertical). В горизонтальном режиме инвентаря в одной строке могут быть несколько предметов. В вертикальном режиме, в каждой строке инвентаря содержится только один предмет. (число)
inv.col.fg = цвет текста инвентаря (цвет)
inv.col.link = цвет ссылок инвентаря (цвет)
inv.col.alink = цвет активных ссылок инвентаря (цвет)
inv.fnt.name = путь к файлу-шрифту инвентаря (строка)
inv.fnt.size = размер шрифта инвентаря (размер)
inv.gfx.up, inv.gfx.down = пути к файлам-изображениям скорллеров вверх/вниз для инвентаря (строка)
menu.col.bg = фон меню (цвет)
menu.col.fg = цвет текста меню (цвет)
menu.col.link = цвет ссылок меню (цвет)
menu.col.alink = цвет активных ссылок меню (цвет)
menu.col.alpha = прозрачность меню 0-255 (число)
menu.col.border = цвет бордюра меню (цвет)
menu.bw = толщина бордюра меню (число)
menu.fnt.name = путь к файлу-шрифту меню (строка)
menu.fnt.size = размер шрифта меню (размер)
menu.gfx.button = путь к файлу изображению значка меню (строка)
menu.button.x, menu.button.y = координаты кнопки меню (числа)
snd.click = путь к звуковому файлу щелчка (строка)
include = имя темы (последний компонент в пути каталога) (строка)
Кроме того, заголовок темы может включать в себя комментарии с тегами. На данный момент существует только один тег: $Name:, содержащий UTF-8 строку с именем темы. Например:
{{{
; $Name:Новая тема$
; модификация темы book
include = book
scr.gfx.h = 500
}}}
Интерпретатор выполняет поиск тем в каталоге themes. Unix версия кроме этого каталога, просматривает также каталог ~/.instead/themes/
TODO
Полный список объектов и методов.