Код игр для STEAD пишется на lua (5.1), поэтому, знание этого языка полезно, хотя и не необходимо. Код движка на lua занимает около ~3000 строк и лучшей документацией является изучение его кода.
Главное окно игры содержит информацию о статической и динамической части сцены, активные события и картинку сцены с возможными переходами в другие сцены (в графическом интерпретаторе).
Статическая часть сцены отображается только один раз, при показе сцены, или при повторении команды look
(в графическом интерпретаторе – клик на названии сцены).
Динамическая часть сцены составлена из описаний объектов сцены, она отображается всегда.
Игроку доступны объекты, доступные на любой сцене – инвентарь. Игрок может взаимодействовать с объектами инвентаря и действовать объектами инвентаря на другие объекты сцены или инвентаря.
Следует отметить, что понятие инвентаря является условным. Например, в «инвентаре» могут находиться такие объекты как «открыть», «осмотреть», «использовать» и т.д.
Действиями игрока могут быть:
Игра представляет из себя каталог, в котором должен находиться скрипт main.lua
. Другие ресурсы игры (скрипты на lua
, графика и музыка) должны находиться в рамках этого каталога. Все ссылки на ресурсы делаются относительно текущего каталога – каталога игры.
В начале файла main.lua
может быть определен заголовок, состоящий из тегов. Теги должны начинаться с символов –
: комментарий с точки зрения lua. На данный момент существует один тег: $Name:
, который должен содержать название игры в кодировке UTF-8. Пример использования тега:
-- $Name: Самая интересная игра!$
Сразу после заголовков вам необходимо указать версию STEAD API, которая требуется игре. На данный момент последняя версия 1.8.0.
instead_version "1.8.0"
Важно!
Если version отсутствует, то STEAD API будет работать в режиме совместимости (устаревшее API).
Инициализацию игры следует описывать в функции init
:
function init() me()._know_truth = false take(knife); take(paper); end
Графический интерпретатор ищет доступные игры в каталоге games
. Unix-версия интерпретатора кроме этого каталога просматривает также игры в каталоге ~/.instead/games
.
Windows-версия: Documents and Settings/USER/Local Settings/Application Data/instead/games
.
В Windows- и standalone-Unix-версии игры ищутся в каталоге ./appdata/games, если он существует
.
Сцена – это единица игры, в рамках которой игрок может изучать все объекты сцены и взаимодействовать с ними. В игре должна быть хотя бы одна сцена с именем main
.
main = room { nam = 'главная комната', dsc = 'Вы в большой комнате.', };
Запись означает создание объекта main
типа room
. У каждого объекта игры есть атрибуты и обработчики. Например, атрибут nam
(имя) является необходимым для любого объекта.
Атрибут nam
для сцены это то, что будет заголовком сцены при ее отображении. Имя сцены также используется для ее идентификации при переходах.
Атрибут dsc
это описание статической части сцены, которое выводится один раз при входе в сцену или явном выполнении команды look
.
Вы можете использовать символ ;
вместо ,
для разделения атрибутов. Например:
main = room { nam = 'главная комната'; dsc = 'Вы в большой комнате.'; };
Если для вашего творческого замысла необходимо, чтобы описание статической части сцены выводилось каждый раз, вы можете определить для своей игры параметр forcedsc
(в начале игры).
game.forcedsc = true;
Или, аналогично, задать атрибут forcedsc для конкретных сцен.
Для длинных описаний удобно использовать запись вида:
dsc = [[ Очень длинное описание... ]],
При этом переводы строк игнорируются. Если вы хотите, чтобы в выводе описания сцены присутствовали абзацы – используйте символ ^
.
dsc = [[ Первый абзац. ^^ Второй Абзац.^^ Третий абзац.^ На новой строке.]],
Объекты – это единицы сцены, с которыми взаимодействует игрок.
tabl = obj { nam = 'стол', dsc = 'В комнате стоит {стол}.', act = 'Гм... Просто стол...', };
Имя объекта nam
используется при попадании его в инвентарь а также в текстовом интерпретаторе для адресации объекта.
dsc
– описание объекта. Оно будет выведено в динамической части сцены. Фигурными скобками отображается фрагмент текста, который будет являться ссылкой в графическом интерпретаторе.
act
– это обработчик, который вызывается при действии пользователя (действие на объект сцены). Его задача – возвращение строки текста, которая станет частью событий сцены, или логического значения (см. раздел 5).
ВНИМАНИЕ: в пространстве имен lua уже существуют некоторые объекты (таблицы), например: table, io, string… Будьте внимательны при создании объекта. Например, в приведенном примере используется tabl
, а не table
. Лучше не использовать подобные названия, хотя в новых версиях INSTEAD эта проблема во многом решена.
Ссылкой на объект называется текстовая строка, содержащая имя объекта при его создании. Например: 'tabl' – ссылка на объект tabl
.
Для того, чтобы поместить в сцену объекты, нужно определить массив obj
, состоящий из ссылок на объекты:
main = room { nam = 'главная комната', dsc = 'Вы в большой комнате.', obj = { 'tabl' }, };
Теперь, при отображении сцены мы увидим объект «стол» в динамической части.
Вы можете использовать ссылки на объекты без кавычек в том случае, если объект был определен ранее, но использование кавычек всегда безопасней.
Объекты тоже могут содержать атрибут obj
. При этом, список будет последовательно разворачиваться. Например, поместим на стол яблоко.
apple = obj { nam = 'яблоко', dsc = 'На столе лежит {яблоко}.', act = 'Взять что-ли?', }; tabl = obj { nam = 'стол', dsc = 'В комнате стоит {стол}.', act = 'Гм... Просто стол...', obj = { 'apple' }, };
При этом, в описании сцены мы увидим описание объектов стол и яблоко, так как apple
– связанный с tabl
объект.
Большинство атрибутов и обработчиков могут быть функциями. Так, например:
nam = function() return 'яблоко'; end,
Это синоним записи: nam = 'яблоко';
Обработчик должен вернуть строку. Если вам удобнее, для возвращения текста вы можете использовать функции:
p («текст»)
– вывод текста и пробела;pn («текст»)
– вывод текста с переводом строки;pr («текст»)
– вывод текста как есть;
Если p
/pn
/pr
вызывается с одним текстовым параметром, то скобки можно опускать.
Используйте ..
или ,
для склейки строк. Например:
pn "Нет скобкам!"; pn ("Строка 1".." Строка 2"); pn ("Строка 1", "Строка 2");
Функции сильно расширяют возможности STEAD, например:
apple = obj { nam = 'яблоко', dsc = function(s) if not s._seen then p 'На столе {что-то} лежит.'; else p 'На столе лежит {яблоко}.'; end end, act = function(s) if s._seen then p 'Это яблоко!'; else s._seen = true; p 'Гм... Это же яблоко!'; end end, };
Если атрибут или обработчик оформлен как функция, то обычно первый аргумент функции (s
) – сам объект.
В данном примере при показе сцены в динамической части сцены будет выведен текст: 'На столе что-то лежит'. При взаимодействии с 'что-то', переменная _seen
объекта apple
будет установлена в true
и мы увидим, что это было яблоко.
Запись s._seen
означает, что переменная _seen
размещена в объекте s
(то-есть apple
). Подчеркивание означает, что эта переменная попадет в файл сохранения игры.
Начиная с версии 1.2.0 вы можете определять переменные следующим образом:
global { global_var = 1; } main = room { var { i = "a"; z = "b"; }; nam = 'Моя первая комната'; var { new_var = 3; }; dsc = function(s) p ("i == ", s.i); p ("new_var == ", s.new_var); p ("global_var == ", global_var); end;
Переменные записываются в файл сохранения, если они размещены в одном из перечисленных типов объектов: комната, объект, игра, игрок, глобальное пространство, при этом начинаются с символа _
или определены с помощью var
и global
.
Начиная с версии 1.2.0 вы можете определять функции следующим образом:
dsc = code [[ if not self._seen then p 'На столе {что-то} лежит.'; else p 'На столе лежит {яблоко}.'; end ]],
При этом в self
записан текущий объект, arg1 … arg9
и массив args[]
– параметры.
В файл сохранения могут быть записаны:
code
;Иногда может понадобиться обработчик, который совершал бы некоторое действие, но не выводил никакого описания. Например:
button = obj { nam = "кнопка", dsc = "На стене комнаты видна большая красная {кнопка}.", act = function (s) here()._dynamic_dsc = [[После того как я нажал на кнопку, комната преобразилась. Книжный шкаф куда-то исчез вместе со столом и комодом, а на его месте появился странного вида аппарат.]]; return true; end, } r12 = room { nam = 'комната', _dynamic_dsc = 'Я нахожусь в комнате.', dsc = function (s) return s._dynamic_dsc end, obj = {'button'} }
В данном случае обработчик act
нужен для того, чтобы поменять описание комнаты, и не нужно, чтобы чтобы он выводил результат действия. Для отключения результата можно вернуть из обработчика значение true
– это будет означать, что действие успешно выполнено, но не требует дополнительного описания.
Если необходимо показать, что действие невыполнимо, ничего не возвращайте. При этом будет отображено описание по умолчанию, заданное с помощью обработчика game.act
. Обычно описание по умолчанию содержит описание невыполнимых действий.
Обратите внимание, что для создания динамического описания сцены в рассмотренном выше примере использовалось новая переменная _dynamic_dsc
. Это сделано для того, чтобы изменённое описание попало в файл сохранения игры. Поскольку имя dsc
не начинается с подчёркивания или заглавной буквы, по умолчанию оно не попадёт в файл сохранения.
Данный пример мог бы выглядеть более разумно:
button = obj { nam = "кнопка"; dsc = "На стене комнаты видна большая красная {кнопка}."; act = function (s) here().dsc = [[Теперь комната выглядит совсем по-другому!!!]]; pn [[После того как я нажал на кнопку, комната преобразилась. Книжный шкаф куда-то исчез вместе со столом и комодом, а на его месте появился странного вида аппарат.]]; end, } r12 = room { nam = 'комната'; forcedsc = true; var { dsc = 'Я нахожусь в комнате.'; }; obj = {'button'} }
Простейший вариант сделать объект, который можно брать – определить обработчик tak
.
Например:
apple = obj { nam = 'яблоко', dsc = 'На столе лежит {яблоко}.', inv = function(s) inv():del(s); return 'Я съел яблоко.'; end, tak = 'Вы взяли яблоко.', };
При этом, при действии игрока на объект «яблоко» – яблоко будет убрано из сцены и добавлено в инвентарь. При действии игрока на инвентарь – вызывается обработчик inv
.
В нашем примере, при действии игроком на яблоко в инвентаре – яблоко будет съедено.
Для перехода между сценами используется атрибут сцены – список 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
.
Если вы используете функции p
/pn
/pr
, то просто возвращайте статус операции с помощью return
:
room2 = room { enter = function(s, f) if f == main then p 'Вы пришли из комнаты.'; end end, nam = 'зал', dsc = 'Вы в огромном зале.', way = { 'main' }, exit = function(s, t) if t == main then p 'Я не хочу назад!' return false end end, };
Следует отметить, что при вызове обработчика enter текущая сцена может быть еще не изменена!!! Начиная с версии 1.2.0 добавлены обработчики left и entered, которые вызываются после того, как переход произошел. Эти обработчики рекомендованы к использованию всегда, когда нет необходимости запрещать переход.
Игрок может действовать объектом инвентаря на другие объекты. При этом вызывается обработчик 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 then p 'Не хочу это резать.' return false else p 'Вы вырезаете на столе свои инициалы.'; end end };
В примере выше нож можно использовать только на стол.
Игрок в STEAD представлен объектом pl
. Тип объекта – player
. В движке объект создается следующим образом:
pl = player { nam = "Incognito", where = 'main', obj = { } };
Атрибут obj
представляет собой инвентарь игрока.
Игра также представлена объектом 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 = 'Не сработает...';
Атрибуты-списки (такие как way
или obj
) позволяют работать с собой, таким образом позволяя реализовать динамически определяемые переходы между сценами, живые объекты и т.д.
Методы списков: add
, del
, look
, srch
, purge
, replace
. Из них наиболее часто используемые: add
и del
.
add
- добавляет в список. del
– удаляет из него. purge
– удаляет даже выключенный объект. srch
– выполняет поиск объекта. replace
– замена объекта.
Следует отметить, что параметром add
, del
, purge
, replace
и 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(s); inv():add(s); end, };
Кроме добавления, удаления объектов из списков вы можете использовать выключение/включение объектов с помощью методов enable()
и disable()
. Например: knife:disable()
. При этом объект knife
пропадает из описания сцены, но в последствии может быть опять быть включен, с помощью knife:enable()
.
Начиная с версии 0.9.1 доступны методы zap()
и cat()
. zap()
– обнуляет список. cat(b, [pos])
– добавляет в список содержимое b
на позицию pos
.
Начиная с версии 1.8.0 поддерживаются методы списков enable
и disable
, работающие аналогично одноименным методам объектов.
Внимание!!! На данный момент для работы с инвентарем и объектами рекомендуется использовать более высокоуровневые функции: put
/get
/take
/drop
/remove
/seen
/have
и др.
В STEAD определены некоторые функции, которые возвращают наиболее часто используемые объекты. Например:
inv()
возвращает список инвентаря;objs()
возвращает список объектов текущей сцены; (начиная с 0.8.5 – необязательный параметр – сцена, для которой возвращается список);ways()
возвращает список возможных переходов из текущей сцены; (начиная с 0.8.5 – необязательный параметр – сцена, для которой возвращается список);me()
возвращает объект-игрок;here()
возвращает объект текущую сцену; (начиная с 0.8.5 – еще одна функция where(obj)
– возвращает сцену на которой находится объект, если он был помещен туда с помощью put/move/drop
)from()
возвращает объект прошлой сцены;seen(obj, [scene])
возвращает объект, если он присутствует и не отключен на сцене, есть второй необязательный параметр – сцена;have(obj, [scene])
возвращает объект, если он есть в инвентаре и не отключен, есть второй необязательный параметр – сцена;exist(obj, [scene])
возвращает объект, если он присутствует на сцене, есть второй необязательный параметр – сцена;live(obj)
возвращает объект, если он присутствует среди живых объектов;path(объект,[комната])
– найти элемент в way
, даже если он disabled
;
Комбинируя эти функции с методами add
, del
можно динамически менять сцену, например:
ways():add('nextroom'); -- добавить переход на новую сцену;
objs():add('chair'); -- добавить объект в текущую сцену;
Еще одна функция, которая получает объект по ссылке:
ref()
.
Например, мы можем добавить объект в локацию home
следующим образом:
ref('home').obj:add('chair');
Впрочем, следующая более простая запись тоже является корректной:
home.obj:add('chair');
Или, для версии >=0.8.5:
objs('home'):add('chair');
или, наконец:
put('chair', 'home');
или даже:
put(chair, home);
Начиная с 0.8.5 – deref(o)
, возвращает ссылку-строку для объекта;
В STEAD определены некоторые высокоуровневые функции, которые могут оказаться полезными при написании игры.
have()
– проверяет, есть ли объект в инвентаре. По объекту, его ссылке или по атрибуту nam
объекта. Например:... act = function(s) if have('knife') then return 'Но у меня же есть нож!'; end end ...
Следующие варианты тоже будут работать:
... if have 'knife' then ... if have (knife) then ...
В следующих примерах вы также можете использовать все варианты адресации объектов.
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
Начиная с 0.8.6 – необязательный второй параметр – сцена.
drop(o)
– положить объект из инвентаря на сцену:drop('knife');
Начиная с версии 0.8 присутствует также функция dropf
, аналогичная drop
, но добавляющая объект в начало списка. Начиная с версии 0.8.5 второй необязательный параметр – комната, куда помещается предмет. Кроме того, для версий >=0.8.5 доступны похожие функции put
/putf
, которые не удаляют предмет из инвентаря.
Начиная с 0.8.9 – присутствует функция remove(o, [from])
, удаляет объект из текущей сцены или сцены from
.
take(o)
– взять объект.take('knife');
Начиная с версии 0.8.5 второй необязательный параметр – комната, с которой берется предмет.
taken(o)
– если объект взят – вернет true
(взят с помощью tak
или take()
);rnd(m)
– случайное значение от 1
до m
.walk(w)
– перейти в сцену w
, при этом, если вы не используете p
/pn
/pr
обработчику нужно вернуть возвращаемое значение walk
. Например:act = code [[ pn "Я иду в следующую комнату..." walk (nextroom); ]] ... act = code [[ return cat('Я иду в следующую комнату', walk (nextroom)); ]]
Внимание!!!
После вызова walk
выполнение обработчика продолжится до его завершения.
change_pl(p)
– переключиться на другого игрока (со своим инвентарем и позицией). При этом функция переносит действие в сцену нового игрока без вызова exit/left/enter/entered. Для указания сцены, на которой находится игрок вы можете использовать атрибут where или явно вызывать функции walk()
после change_pl().mycar = obj { nam = 'моя машина', dsc = 'Перед хижиной стоит мой старенький {пикап} Toyota.', act = function(s) return walk('inmycar'); end };
walkback()
– возвращается из сцены в прошлую.back()
– возвращается из сцены в прошлую. Если это переход из диалога в комнату, то не вызываются: dsc
, enter
, entered
у комнаты. exit
/left
диалога вызываются. В других случаях аналогична goback
.walkin(комната)
– перейти в сцену, при этом exit
/left
текущей комнаты не вызывается;walkout()
– вернуться в прошлую сцену, при этом enter
/entered
этой сцены не вызовется;time()
– возвращает текущее время игры. Время игры считается в активных действиях.cat(…)
– возвращает строку – склейку строк-аргументов. Если первый аргумент nil
, то функция возвращает nil
.par(…)
– возвращает строку – склейку строк-аргументов, разбитых строкой-первым параметром.disable
/enable
/disable_all
/enable_all
– аналог одноименных методов у объекта;visited([комната])
– счетчик посещений комнаты или nil
;path(объект,[комната])
– найти элемент в way
, даже если он disabled
;nameof(объект)
– вернуть имя объекта (nam
атрибут);purge (объект, [откуда])
– см. remove
, удаляет даже выключенные объекты;replace(объект, на объект, [где])
– замена одного объекта другим;disabled(объект)
– возвращает true
, если объект отключен;Диалоги – это сцены, содержащие объекты – фразы. Например, простейший диалог может выглядеть следующим образом:
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; back(); end povardlg = dlg { nam = 'на кухне', dsc = 'Передо мной полное лицо женщины - повара в белом колпаке и усталым взглядом...', obj = { [1] = phr('Мне вот-этих зелененьких... Ага -- и бобов!', 'На здоровье!', [[pon(); gotfood(1);]]), [2] = phr('Картошку с салом, пожалуйста!', 'Приятного аппетита!', [[pon(); gotfood(2);]]), [3] = phr('Две порции чесночного супа!!!', 'Прекрасный выбор!', [[pon();gotfood(3);]]), [4] = phr('Мне что-нибудь легонькое, у меня язва...', 'Овсянка!', [[pon(); gotfood(4);]]), }, };
В данном примере, игрок выбирает еду. Получает ее (запомнив выбор в переменной food._num) и возвращается обратно (в ту сцену откуда попал в диалог).
В реакции может быть любой lua код, но в STEAD определены наиболее часто используемые функции:
pon(n..)
– включить фразы диалога с номерами n… (в нашем примере – чтобы игрок мог повторно взять еду того-же вида).poff(n…)
– выключить фразы диалога с номерами n…prem(n…)
– удалить (заблокировать) фразы диалога с номерами n… (удаление означает невозможность включения фраз. pon(n..)
не приведет к включению фраз).pseen(n..)
– вернет true
, если все заданные фразы диалога видимы.punseen(n..)
– вернет true
, если все заданные фразы диалога невидимы.
Если параметр n
не указан, действие относится к текущей фразе.
Переход в диалог осуществляется как переход на сцену:
povar = obj { nam = 'повар', dsc = 'Я вижу {повара}.', act = function() return walk('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
/prem
/pseen
/punseen
. Например: shopman:pon(5);
Начиная с версии INSTEAD 1.7.0 поддерживается новый более простой и мощный синтаксис диалогов. Фразы определяются в аттрибуте phr диалога, например:
povardlg = dlg { nam = 'на кухне', entered = 'Передо мной полное лицо женщины - повара в белом колпаке и усталым взглядом...', phr = { { always = true, 'Мне вот-этих зелененьких... Ага -- и бобов!', 'На здоровье!'}, { always = true, 'Картошку с салом, пожалуйста!', 'Приятного аппетита!'}, { always = true, 'Две порции чесночного супа!!!', 'Прекрасный выбор!' }, { always = true, 'Мне что-нибудь легонькое, у меня язва...', 'Овсянка!' }, }, };
Имейте в виду, что если в диалоге не определен атрибут dsc, то он формируется движком таким образом, чтобы отражать последнюю реакцию диалога, то-есть, если игрок нажмет на заголовок сцены он увидит последний ответ на свою реплику еще раз. Если вы рассчитываете на такое поведение диалога, то первоначальную реакцию диалога удобнее всего вписать в entered, как в примере выше.
Каждая фраза имеет вид:
{ [номер или tag=тэг,][false если выключена,][always = true], "Вопрос", "Ответ", [[ необязательный код ]] },
Вы можете использовать номер, вместо записи [1] = …, например:
{ 2, "Вопрос?", "Ответ!" }
Для сложных диалогов более удобными являются теги, например:
{ tag = 'exit', "Ну ладно, я пошел!", code [[ back() ]] }
Как видно, тег – это текстовая метка фразы. Вы можете делать pon/poff/pseen/punseen как с пронумерованными фразами, так и с фразами, имеющими тег. В случае, если один и тот-же тег стоит у нескольких фраз, то действие применяется на все фразы с одинаковым тегом. Для функции pseen, видимость тега означает наличие хотя бы одной фразы с таким тегом, для функции punseen – отсутствие включенных фраз с заданным тегом.
Вы можете присваивать тег и пронумерованной фразе, если это требуется.
Присутствие в фразе always = true означает, что фраза не будет автоматически выключена при ее срабатывании:
{ tag = 'exit', always = true, "Ну ладно, я пошел!", code [[ back() ]] }
Если необходимо опустить ответ фразы и всю реакцию описать в параметре «необязательный код», то следующие варианты записи являются допустимыми:
{ tag = 'exit', always = true, "Ну ладно, я пошел!", nil, [[ back() ]] }, { tag = 'exit', always = true, "Ну ладно, я пошел!", code = [[ back() ]] }
Вы также можете задавать вопрос и ответ в виде функций или code.
{ tag = 'exit', code [[ p "Ну ладно, я пошел!" ]], code [[ p "Может, останешься?"; pon 'really' ]] }, { tag = 'really', false, always = true, "Я точно пошел!", function() back() end } -- эта фраза выключена и включается предыдущей
Используя новый вариант диалогов, вы можете группировать фразы в ветви, тем самым организуя иерархические диалоги без необходимости использования pon/poff и перехода между несколькими dlg. Группа фраз это набор фраз, отделенная от другой группы фразой, у которой нет реакции (пример такой простейшей фразы это пара скобок {}). Например:
{ 'Расскажи что-нибудь о погоде?', 'Хорошо, что тебя интересует?', [[ psub 'погода' ]] }, { always=true, [[Пока!]], code = [[ back() ]] }, { tag = 'погода' }, { 'Какая температура?', '25 градусов!' }, { 'Какая влажность?', '80%' },
В диалоге показывается только текущая группа фраз. В примере выше мы видим две группы. При входе в диалог, игрок увидит выбор из двух фраз: «Расскажи что-нибудь…» и «Пока!». Выбрав первую фразу, он попадет в подветку с тегом 'погода', в которой увидит два вопроса (о температуре и влажности). Когда он задаст оба вопроса, то переместится снова на первую ветку, где останется активной только одна фраза: «Пока!».
В данном примере группы разделены фразой: { tag = 'погода' }, но также точно разделителем могла стать пустая фраза без тега:
{ 'Расскажи что-нибудь о погоде?', 'Хорошо, что тебя интересует?', [[ psub 'погода' ]] }, { always="true", [[Пока!]], code = [[ back() ]] }, { }, { tag = 'погода', 'Какая температура?', '25 градусов!' }, { 'Какая влажность?', '80%' },
Переход на ветку осуществляется с помощью команд:
В качестве аргумента psub/pstart/pjump может быть указан номер или тег. Вы можете использовать эти функции и извне диалога, аналогично pon/poff и т.д, с помощью звписи: диалог:метод(), например: shopdlg:pstart(1)
Для того, чтобы узнать текущую подветку, используйте методы диалога диалог:current() и диалог:curtag(). Первый всегда возвращает номер, а второй – тег.
Проверку состояния ветки можно осуществить с помощью функций:
Обе функции могут получать параметр – номер или тег фразы, с которой начинается анализ группы. :empty() возвращает true, в случае, если в группе нет активных фраз. :visible() возвращает число видимых фраз (0 – если группа пуста). В случае, если параметр не указан, анализируется текущая группа.
В случае перехода по psub/pstart/pjump, первая фраза, на который выполняется переход, может служить заголовком группы фраз.
Например:
{ 'Расскажи что-нибудь о погоде?', code = [[ psub 'погода' ]] }, { always=true, [[Пока!]], code = [[ back() ]] }, { }, { tag = 'погода', "Хорошо, что тебя интересует?" }, { 'Какая температура?', '25 градусов!' }, { 'Какая влажность?', '80%' },
фраза с тегом 'погода' не содержит в себе реакцию, и выполняет роль заголовка ветки. При переходе на ветку 'погода' с помощью psub будет выведен текст «Хорошо, что тебя интересует?».
Как вы знаете, вопрос может быть функцией, тем самым позволяя выполнять код при переходе между ветками:
{ 'Расскажи что-нибудь о погоде?', code = [[ psub 'погода' ]] }, { always=true, [[Пока!]], code = [[ back() ]] }, { }, { tag = 'погода', function() p "Хорошо, что тебя интересует?"; weather_asked = true; end }, { 'Какая температура?', '25 градусов!' }, { 'Какая влажность?', '80%' },
Кроме того, заголовок группы может содержать метод empty, который вызывается в ситуации, когда все вопросы данной группы исчерпаны:
{ 'Расскажи что-нибудь о погоде?', code = [[ psub 'погода' ]] }, { always=true, [[Пока!]], code = [[ back() ]] }, { }, { tag = 'погода', "Хорошо, что тебя интересует?", empty = code [[ p "Хватит о погоде..."; pret() ]] }, { 'Какая температура?', '25 градусов!' }, { 'Какая влажность?', '80%' },
empty вызывается в ситуации, когда в ветке не остается фраз. Если empty не определена, то действие по-умолчанию это возврат по pret(). Если вы переопределяете empty, вам придется вызвать pret() явно, если это требуется.
Законченный пример реализации диалога, вы можете посмотреть здесь: http://instead.googlecode.com/svn/trunk/doc/examples/dialog/main.lua
Иногда, сцену нужно наполнить декорациями, которые обладают ограниченной функциональностью, но делают игру разнообразней. Для этого можно использовать облегченный объект. Например:
sside = room { nam = 'южная сторона', dsc = [[Я нахожусь у южной стены здания института. ]], act = function(s, w) if w == "подъезд" then ways():add('stolcorridor'); p "Я подошел к подъезду. На двери подъезда надпись -- 'Столовая'. Хм -- зайти внутрь?"; elseif w == "люди" then p 'Те, кто выходят, выглядят более довольными...'; end end, obj = { vobj("подъезд", "У восточного угла находится небольшой {подъезд}."), vobj("люди", "Время от времени дверь подъезда хлопает впуская и выпуская {людей}.")}, };
Как видим, vobj
позволяет сделать легкую версию статического объекта, с которым тем не менее можно взаимодействовать (за счет определения обработчика act
в сцене и анализа имени объекта). vobj
также вызывает метод used
, при этом в качестве третьего параметра передается объект, воздействующий на виртуальный объект.
Синтаксис vobj
: vobj(имя, описатель)
;
Существует модификация объекта vobj
– vway
. vway
реализует ссылку.
Синтаксис vway
: vway(имя, описатель, сцена назначения);
например:
obj = { vway("дальше", "Нажмите {здесь}.", 'nextroom') }
Вы можете динамически заполнять сцену объектами vobj
или vway
с помощью методов add
и del
. Например:
objs(home):add(vway("next", "{Дальше}.", 'next_room')); -- здесь какой-нибудь код home.obj:del("next");
Определена также упрощенная сцена vroom
.
Синтаксис: vroom(имя перехода, сцена назначения)
. Например:
home.way:add(vroom("идти на запад", 'mountains');
Вы можете определять обработчики, которые выполняются каждый раз, когда время игры увеличивается на 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
, или любым другим способом.
life
метод может возвращать текст события, который печатается после описания сцены.
Начиная с версии 0.9.1 вы можете вернуть из обработчика life
второй код возврата, важность. (true
или false
). Например:
return 'В комнату вошел охранник.', true
Или:
p 'В комнату вошел охранник.' return true
При этом текст события будет выведен до описания объектов.
Графический интерпретатор анализирует атрибут сцены pic
, и воспринимает его как путь к картинке, например:
home = room { pic = 'gfx/home.png', nam = 'дома', dsc = 'Я у себя дома', };
Конечно, pic
может быть функцией, расширяя возможности разработчика.
Если в текущей сцене не определен атрибут pic
, то берется атрибут game.pic
. Если не определен и он, то картинка не отображается.
Начиная с версии 0.9.2 вы можете использовать в качестве картинок анимированные gif файлы.
Начиная с версии 0.9.2 вы можете встраивать графические изображения в текст или в инвентарь с помощью функции img
. Например:
knife = obj { nam = 'Нож'..img('img/knife.png'), }
А начиная с 1.3.0 поддерживается обтекание картинок текстом. Если картинка вставляется с помощью функции imgl
/imgr
, она будет расположена у левого/правого края. Такие картинки не могут быть ссылками.
Для задания отступов вокруг изображения используйте pad
, например:
imgl 'pad:16,picture.png' -- отступы по 16 от каждого края imgl 'pad:0 16 16 4,picture.png' -- отступы: вверху 0, справа 16, внизу 16, слева 4 imgl 'pad:0 16,picture.png' -- отступы: вверху 0, справа 16, внизу 0, слева 16
Вы можете использовать псевдо-файлы для изображений прямоугольников и пустых областей:
dsc = img 'blank:32x32'..[[Строка с пустым изображением.]]; dsc = img 'box:32x32,red,128'..[[Строка красным полупрозрачным квадратом.]];
В современной версии INSTEAD вы можете использовать атрибут disp
:
knife = obj { nam = 'Нож'; disp = 'Нож'..img('img/knife.png'), }
Начиная с версии 1.0.0 интерпретатор может обрабатывать составные картинки, например:
pic = 'gfx/mycat.png;gfx/milk.png@120,25;gfx/fish.png@32,32'
Интерпретатор проигрывает в цикле текущую музыку, которая задается с помощью функции:
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 – проигрывание текущего трека закончено.
Начиная с версии 0.9.2 set_sound()
позволяет проиграть звуковой файл. get_sound()
возвращает имя звукового файла, который будет проигран.
set_sound('snd', 'channel', 'loop')
, где:
Для того, чтобы отменить проигрывание, вы можете использовать stop_music()
(с версии 1.0.0). Так же для остановки проигрывания звука можно использовать stop_sound()
(с версии 1.4.0). Для остановки в определенном канале stop_sound('channel')
.
is_music()
позволяет узнать, проигрывается ли музыка. (с версии 1.0.0)
Для разбиения текста игры на файлы вы можете использовать dofile
. Вы должны использовать dofile
в глобальном контексте таким образом, чтобы во время загрузки main.lua
загрузились и все остальные фрагменты игры, например.
-- main.lua dofile "episode1.lua" dofile "npc.lau" dofile "start.lua"
Для динамической подгрузки частей игры (с возможностью переопределения существующих объектов), вы можете воспользоваться gamefile
:
... act = code [[ gamefile ("episode2.lua"); ]] ...
gamefile
позволяет загрузить новый файл и забыть стек предыдущих загрузок, запустив этот новый файл как самостоятельную игру.
... act = code [[ gamefile ("episode3.lua", true); ]] ...
Начиная с версии 1.2.0 появилась возможность использования модулей с помощью require
. На данный момент существуют следующие модули:
dbg
— модуль отладки (require «dbg»
– включить отладчик);walk
— улучшенный вариант реализации переходов;xact
— множественные ссылки;input
— клавиатурный ввод;click
— модуль перехвата кликов мыши по картинке сцены;vars
— модуль определения переменных;prefs
— модуль настроек;snapshots
— модуль поддержки снапшотов;format
— модуль оформления вывода;object
— модуль улучшенных объектов;theme
— управление темой;Использование модуля выглядит так:
--$Name: Моя игра!$ instead_version "1.2.0" require "para" require "dbg" ...
Следующие модули подключаются автоматически, если вы задали version >= 1.2.0: vars
, object
, walk
.
Объект prefs
(находится в модуле prefs
) служит для сохранения настроек игры, например, его можно использовать для реализации системы достижений или счетчика количества прохождений…
require "prefs" ... prefs.counter = 0 ... exit = function(s) prefs.counter = prefs.counter + 1 prefs:store() end ... enter = function(s) return 'Вы прошли игру '..prefs.counter..' раз(а)'; end ... act = function(s) prefs:purge() return "Настройки обнулены" end
Модуль xact
позволяет делать ссылки на объекты из других объектов, реакций и life
методов в форме {объект|строка} следующим образом:
... act = [[ Под столом я заметил {knife|нож}.]] ...
При этом, объект может быть объектом или именем объекта.
Примечание: до версии 1.2.2 использовался следующий формат ссылок: {объект:строка}.
В этом модуле определены также такие объекты как xact
и xdsc
.
xact
– объект - простейшая реакция. Например:
main = room { forcedsc = true; dsc = [[От автора. Эту игру я писал очень {note1|долго}.]]; obj = { xact('note1', [[Больше 10 лет.]]); } }
Реакция может содержать код:
xact('note1', code [[p "Больше 10 лет."]]);
xdsc
позволяет вставить в список объектов множественное описание:
main = room { forcedsc = true; dsc = [[Я в комнате.]]; xdsc = [[ Я вижу {apple|яблоко} и {knife|нож}. ]]; other = [[ Еще здесь лежат {chain|цепь} и {tool|пила}.]]; obj = { xdsc(), -- 'xdsc method by default' xdsc('other'), 'apple', 'knife', 'chain', 'tool', } }
Вы можете также использовать комнату xroom
:
main = xroom { forcedsc = true; dsc = [[Я в комнате.]]; xdsc = [[ Я вижу {apple|яблоко} и {knife|нож}. ]]; obj = { 'apple', 'knife', 'chain', 'tool', } }
Модуль input
позволяет реализовывать простые поля ввода, а click
отслеживает щелчки по картинке сцены.
Модуль format
выполняет форматирование вывода. По умолчанию все настройки выключены :
format.para = false -- отступы в начале абзаца; format.dash = false -- замена двойного - на тире; format.quotes = false -- замена " " на << >>; format.filter = nil -- пользовательская функция замены;
Вы можете пользоваться модулями para
, dash
, quotes
для включения отдельных настроек.
Вы можете делать простое форматирование текста с помощью функций:
txtc()
- разместить по центру;txtr()
- разместить справа;txtl()
- разместить слева;txttop()
- сверху строки;txtbottom()
- снизу строки;txtmiddle()
- середина строки (по умолчанию);Например:
main = room { nam = 'Intro', dsc = txtc('Добро пожаловать!'), }
Вы также можете менять оформление текста с помощью комбинаций функций:
txtb()
- жирный;txtem()
- курсив;txtu()
- подчеркнутый;txtst()
- перечеркнутый;Например:
main = room { nam = 'Intro', dsc = 'Вы находитесь в комнате '..txtb('main')..'.', }
Начиная с версии 1.1.0 вы также можете создавать неразрываемые строки с помощью: txtnb()
.
Вы можете делать меню в области инвентаря, определяя объекты с типом menu
. При этом, обработчик меню будет вызван после одного клика мыши. Если обработчик не возвращает текст, то состояние игры не изменяется. Например, реализация кармана:
pocket = menu { State = false, nam = function(s) if s.State then return txtu('карман'); end return 'карман'; end, gen = function(s) if s.State then s:enable_all(); else s:disable_all(); end end, menu = function(s) if s.State then s.State = false; else s.State = true; end s:gen(); end, }; knife = obj { nam = 'нож', inv = 'Это нож', }; function init() inv():add(pocket); put(knife, pocket); pocket:gen(); end main = room { nam = 'test', };
Ниже представлена реализация статуса игрока в виде текста, который появляется в инвентаре, но не может быть выбран.
global { life = 10; power = 10; } status = stat { nam = function(s) p ('Жизнь: ', life, 'Сила: ', power) end }; function init() inv():add('status'); end
Вы можете делать walk
из обработчиков enter
и exit
.
Динамически создаваемые ссылки могут быть реализованы разным способом. Ниже приводится пример, основанный на использовании объектов vway
. Для добавления ссылки можно использовать запись:
objs(home):add(vway('Дорога', 'Я заметил {дорогу}, ведущую в лес...', 'forest'));
Для удаления ссылки можно использовать метод del
.
objs(home):del('Дорога');
Для определения наличия ссылки в сцене – метод srch
.
if not objs(home):srch('Дорога') then objs(home):add(vway('Дорога', 'Я заметил {дорогу}, ведущую в лес...', 'forest')); end
Динамические ссылки удобно создавать в обработчике enter
, или по мере необходимости в любом месте кода игры. Если ссылки создаются в текущей сцене, то последний пример можно упростить:
if not seen('Дорога') then objs():add(vway('Дорога', 'Я заметил {дорогу}, ведущую в лес...', 'forest')); end
Кроме того, вы можете просто включать и выключать ссылки с помощью enable()
, disable()
, например:
seen('Дорога', home):disable(); exsist('Дорога', home):enable();
Вы можете создавать выключенные vobj
и vway
следующим образом:
obj = {vway('Дорога', 'Я заметил {дорогу}, ведущую в лес...', 'forest'):disable()},
И затем включать их по индексу в массиве obj
или иным способом (seen
/srch
/exist
):
objs()[1]:enable();
Если вы не хотите показывать исходный код своих игр, вы можете закодировать исходный код с помощью sdl-instead -encode <путь к файлу> [выходной путь]
и использовать его с помощью lua функции doencfile
. При этом главный файл main.lua
необходимо оставлять текстовым. Таким образом схема выглядит следующим образом (game
– закодированный game.lua
):
-- $Name: Моя закрытая игра!$ doencfile("game");
Не используйте компиляцию игр с помощью luac, так как luac создает платформозависимый код! Однако, компиляция игр может быть использована для поиска ошибок в коде.
Вы можете упаковать ресурсы игры (графику, музыку, темы) в файл .idf, для этого поместите все ресурсы в каталог data и запустите INSTEAD:
instead -idf <путь к data>
При этом в текущем каталоге должен будет создастся файл data.idf
. Поместите его в каталог с игрой. Теперь ресурсы игры в виде отдельных файлов можно удалить.
Вы можете запаковать в формат .idf
всю игру:
instead -idf <путь к игре>
Игры в формате idf
можно запускать как обычные игры instead
(как если бы это были каталоги) а также из командной строки:
instead game.idf
Вы можете создать игру с несколькими персонажами и время от времени переключаться между ними (см. change_pl
). Но вы можете также использовать этот трюк для того, чтобы иметь возможность переключаться между разными типами инвентаря.
Пример кода.
stone = obj { nam = 'камень', dsc = 'На краю лежит {камень}.', act = function() objs():del('stone'); return 'Я толкнул камень, он сорвался и улетел вниз...'; end
Обработчик act мог бы выглядеть проще:
act = function(s) objs():del(s); return 'Я толкнул камень, он сорвался и улетел вниз...'; end
Вы можете использовать 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, }; function init() lifeon('horse'); end
Начиная с версии 1.1.0 в instead появлилась возможность использовать таймер. (Только в графической версии интерпретатора.)
Таймер программируется с помощью объекта timer
.
timer:set(ms)
– задать интервал таймера в mstimer:stop()
– остановить таймерtimer.callback(s)
– функция-обработчик таймера, которая вызывается через заданный диапазон времениФункция таймера может возвратить команду интерфейса stead, которую нужно выполнить после того, как движок выполнит обработчик. Например:
timer.callback = function(s) main._time = main._time + 1; return "look"; end timer:set(100); main = room { _time = 1, forcedsc = true, nam = 'Таймер', dsc = function(s) return 'Демонстрация: '..tostring(s._time); end };
Начиная с версии 1.1.0 в instead появилась возможность анализировать ввод с клавиатуры (только в графической версии интерпретатора). Для этого используется объект input
.
input.key(s, pressed, key)
– обработчик клавиатуры; pressed
– нажатие или отжатие. key
– символьное имя клавиши;
Обработчик может вернуть команду интерфейса stead
, в этом случае клавиша не будет обработана интерпретатором.
Например:
input.key = function(s, pr, key) if not pr or key == "escape"then return elseif key == 'space' then key = ' ' elseif key == 'return' then key = '^'; end if key:len() > 1 then return end main._txt = main._txt:gsub('_$',''); main._txt = main._txt..key..'_'; return "look"; end main = room { _txt = '_', forcedsc = true, nam = 'Клавиатура', dsc = function(s) return 'Демонстрация: '..tostring(s._txt); end };
Начиная с версии 1.1.5 в instead появилась возможность анализировать события мыши. (Только в графической версии интерпретатора.) Для этого используется объект input
.
input.click(s, pressed, mb, x, y, px, py)
– обработчик клика мыши; pressed
– нажатие или отжатие. mb
– номер кнопки (1 - левая), x
и y
– координаты клика относительно левого верхнего угла. px
и py
присутствуют, если клик произошел в области картинки сцены и содержит координаты клика относительно левого верхнего угла картинки.
Обработчик может вернуть команду интерфейса stead, в этом случае клик не будет обработан интерпретатором.
Например:
input.click = function(s, press, mb, x, y, px, py) if press and px then click.x = px; click.y = py; click:enable(); return "look" end end click = obj { nam = 'клик', x = 0, y = 0, dsc = function(s) return "Вы кликнули по картинке в позиции: "..s.x..','..s.y..'.'; end }:disable(); main = room { nam = 'test', pic ='picture.png', dsc = 'Демонстрация.', obj = { 'click' }, };
Пример прослойки, которая реализует вызов метода click
в текущей комнате при клике на картинку:
input.click = function(s, press, mb, x, y, px, py) if press and px then return "click "..px..','..py; end end game.action = function(s, cmd, x, y) if cmd == 'click' then return call(here(), 'click', x, y); end end ---------------------------------------------------------------------- main = room { nam = 'test', pic ='picture.png', dsc = 'Демонстрация.', click = function(s, x, y) return "Вы кликнули по картинке в позиции: "..x..','..y..'.'; end };
Внимание!!! Начиная с 1.2.0 рекомендуется использовать модуль click.
Вы можете использовать функции new
и delete
для создания и удаления динамических объектов. Примеры:
new ("obj { nam = 'test', act = 'test' }") put(new [[obj {nam = 'test' } ]]); put(new('myconstructor()'); n = new('myconstructor()'); delete(n)
new
воспринимает строку-аргумент как конструктор объекта. Результатом выполнения конструктора должен быть объект. Таким образом в аргументе обычно задан вызов функции-конструктора. Например:
function myconstructor() local v = {} v.nam = 'тестовый объект', v.act = 'Тестовая реакция', return obj(v); end
Созданный объект будет попадать в файл сохранения. new()
возвращает реальный объект; чтобы получить его имя, если это нужно, используйте функцию deref
:
o_name = deref(new('myconstructor()')); delete(o_name);
Иногда вывод обработчика может формироваться сложным образом, в зависимости от условий. В таких случаях удобно пользоваться функциями p()
и pn()
. Эти функции добавляют текст в буфер, связанный с обработчиком, который будет возвращен из обработчика.
dsc = function(s) p "На полу стоит {бочка}." if s._opened then p "Крышка от бочки лежит рядом." end end
Функция pn()
выполняет вывод текста в буфер, дополняя его переводом строки. Функция p()
дополняет вывод пробелом.
Начиная с версии 1.1.6 существует функция pr()
, которая не выполняет дополнение вывода.
Для очистки буфера, используйте pclr()
. Если вам нужно вернуть статус действия, используйте pget()
, или просто используйте return
.
use = function(s, w) if w == apple then p 'Гм... Я почистил яблоко.'; apple._peeled = true return end p 'Это нельзя использовать так!' return false; -- или return pget(), false end
Для того, чтобы во время ошибки увидеть стек вызовов функций 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
…
Для включения простого отладчика, после version
вначале файла напишите:
require "dbg"
Отладчик вызывается по F7
.
Графический интерпретатор поддерживает механизм тем. Тема представляет из себя каталог, с файлом 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.cursor.x
= x координата центра курсора (число) (версия >= 0.8.9)scr.gfx.cursor.y
= y координата центра курсора (число) (версия >= 0.8.9)scr.gfx.cursor.normal
= путь к картинке-курсору (строка) (версия >= 0.8.9)scr.gfx.cursor.use
= путь к картинке-курсору режима использования (строка) (версия >= 0.8.9)scr.gfx.use
= путь к картинке-индикатору режима использования (строка) (версия < 0.8.9)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
– картинка является частью содержимого главного окна, параметры scr.gfx.x
, scr.gfx.y
, scr.gfx.w
игнорируются. float
– картинка расположена по указанным координатам (scr.gfx.x, scr.gfx.y) и масштабируется к размеру scr.gfx.w
x scr.gfx.h
если превышает его. fixed
– картинка является частью сцены как в режиме embedded, но не скроллируется вместе с текстом а расположена непосредственно над ним. win.x
, win.y
, win.w
, win.h
= координаты, ширина и высота главного окна. Области в которой располагается описание сцены (числа)win.fnt.name
= путь к файлу-шрифту (строка)win.fnt.size
= размер шрифта главного окна (размер)win.fnt.height
= междустрочный интервал как число с плавающей запятой (1.0 по умолчанию)win.gfx.up
, win.gfx.down
= пути к файлам-изображениям скорллеров вверх/вниз для главного окна (строка)win.up.x
, win.up.y
, win.down.x
, win.down.y
= координаты скроллеров (координата или -1)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.fnt.height
= междустрочный интервал как число с плавающей запятой (1.0 по умолчанию)inv.gfx.up
, inv.gfx.down
= пути к файлам-изображениям скорллеров вверх/вниз для инвентаря (строка)inv.up.x
, inv.up.y
, inv.down.x
, inv.down.y
= координаты скроллеров (координата или -1)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.fnt.height
= междустрочный интервал как число с плавающей запятой (1.0 по умолчанию)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/
Windows версия (>=0.8.7): Documents and Settings/USER/Local Settings/Application Data/instead/themes