У нас сегодня в меню... меню!

Страница создана Егор Елизаров
 
ПРОДОЛЖИТЬ ЧТЕНИЕ
У нас сегодня в меню... меню!

    1. Система меню в старых графических квестах

     Большое число старых (или как сейчас модно говорить, «олд-скульных»)
графических квестов используют так называемое «меню». То есть набор
глаголов, определяющих действия, которые можно совершать в локации с
людьми и предметами. Примерами являются практически все старые квесты от
Lucas Arts — «Indiana Jones and the Fate of Atlantis», «Sam & Max Hit The Road»,
«Day of the Tentacle» и пр. В этих квестах использовались глаголы «взять»,
«бросить», «отдать», «говорить», «открыть», «закрыть», «использовать»,
«тянуть», «толкать». Глаголы в квестах Lucas Arts оставались в основном одни
и те же, варьировались только их расположение (сравните примеры,
показанные на рисунке 1). Эта система стала почти «стандартом» для
большинства последующих квестов и надолго определила систему основных
производимых действий.

                 а)                                      б)
   а — меню из игры «Day of the Tentacle» (чёрный цвет заменён на белый)
          б — меню из игры «Indiana Jones and the Fate of Atlantis»
                                 Рисунок 1

    В русских играх система меню часто использовалась в текстовых квестах.
Примерами являются игры с ZX Spectrum — «Звёздное Наследие», «Зеркало»,
«Плутония» и другие. Причём, в игре «Звёздное Наследие» в одной из первых
было использовано контекстное меню, когда при выборе глагола выдавался
список не из всех предметов и людей на локации, а только объекты, действие с
которыми возможно производить с помощью данного глагола. То есть если мы
выбирали глагол «Идти», выдавались стороны света — «север», «запад», «юг»,
«восток», если выбирали меню «взять», то выдавался список предметов, и так
далее.
    Данная система меню была использована при реализации ремейков игр на
платформе INSTEAD.
    Необходимое дополнение, позволяющее реализовать данный функционал,
было написано автором платформы INSTEAD Петром Косых. Однако до сих
пор не было нигде описано. Попробуем устранить это.

    2. Подключение меню к своей INSTEAD-игре

    Для обеспечения возможности использования меню в Вашей игре
необходимы два файла — menu.lua и actions.lua. Причём достаточно включить в
main.lua вызов только файла actions.lua:
    dofile('actions.lua');
    поскольку в файле actions.lua первой строчкой идёт вызов menu.lua:
    dofile('menu.lua');

      Эти два файла можно взять из архива с исходным кодом instead
(http://instead.googlecode.com), в каталоге doc/examples/menu-demo. Файлы (с
некоторыми изменениями) были использованы в следующих играх: «Зеркало»,
«Кайлет» («Kayleth»), «Звёздное Наследие», которые можно найти на сайте
http://instead.syscall.ru. Вы можете взять их прямо из игр, но имейте в виду, что
игра «Зеркало» использует еще старое INSTEAD API, и лучше файлы этой игры
не использовать.
      Эти два файла копируете в папку со своей игрой, подключаете в main.lua и
у Вас появляется возможность использовать меню.

    3. Содержание файла actions.lua

    Файл actions.lua описывает действия и отзывы на них. В нём также
определяется порядок следования глаголов в меню (раздел с actmenu).
    Если внимательно изучить данный файл в различных играх, то можно
заметить особенности игр. Скажем, в игре «Зеркало» каждый предмет имел
свой вес, заданный в переменной weight. В функции Take, отвечающей за
поднимание предмета и помещение его в инвентарь игрока, есть функция,
проверяющая возможность поднимания предмета:

    if s.weight then
        if (s.weight > 100) then
          return 'Слишком тяжело!';
        end
    end

     Или другой пример, проверяющий возможность передвижения игрока
после того, как он возьмёт текущий предмет. Переменная status.Cargo — вес
всех переносимых игроком в данный момент предметов.

    if s.weight then
        if (status.Cargo + s.weight > 100) then
             return 'Слишком тяжело!';
        end
        status.Cargo = status.Cargo + s.weight;
    end
    take(s);

    Здесь же присутствуют обработчики нестандартных ситуаций. Скажем, в
игре «Зеркало» есть локации, когда герой перемещается на плоту. Если бросать
предметы в воду, а потом поднимать их как ни в чём не бывало, это будет
выглядеть, мягко говоря, странно. Поэтому в водных локациях присутствует
флаг признака воды — _water. И в функции бросания предмета Drop
выполняется соответствующая проверка:

   local q = here();
   if q._water then
         return 'Если я брошу его в воду, потом уже не
смогу достать.', false;
   end

    И так далее. Здесь же в функции отдыха rest есть проверки на наличие
охранников, при которых отдыхать нельзя, и так далее и тому подобное.
    Не старайтесь вникнуть во все детали этого файла. Используйте только то,
что Вам нужно. Если в Вашей игре у предметов будет вес — берите файл из
игры «Зеркало» и начинайте корректировать его. Если предметы не имеют веса,
берите файл из игры «Кайлет».

    4. Содержание файла menu.lua

    Никогда не вчитывался в содержание этого файла. И при этом написал
несколько игр с меню. А теперь подумайте. Нужно ли Вам тратить время на
изучение данного файла? Может быть, лучше заняться программированием
непосредственно кода игры? Я не призываю к тому, чтобы Вы полностью
игнорировали lua код. Я всего лишь пытаюсь объяснить как можно писать
квесты, обладая минимально необходимым уровнем знаний для этого.

    5. Организация файлов

     Сразу несколько слов об организации файлов в проекте. Размещайте
картинки в папке с именем images, музыку — в папке с именем music.
Привыкайте к фактически уже стандартной структуре каталогов игр INSTEAD.
     Не забудьте в корневой каталог игры поместить два файла — readme-
unix.txt и readme-windows.txt. Скопируйте их из любой игры из основного
репозитория. Там находится простейшая инструкция для тех, кто будет
вручную устанавливать Вашу игру.
     Не забывайте создавать файл .release_notes.txt. Ну просто хотя бы для себя.
Просто чтобы соблюдать правила хорошего тона. Записывайте туда даты
выхода новых версий и кратко описания исправленных ошибок. Но не
забывайте о спойлерах. Не надо писать туда информацию «исправлен баг с
применением зажигалки на картину». Человек, который ни разу не играл в
Вашу игру, может случайно прочитать этот файл и одним паззлом в игре для
него станет меньше. Просто пишите «исправлена ошибка с зажигалкой». Этого
более чем достаточно.

    6. Первая комната
Итак, создаём файл main.lua. Записываем в него следующее:
    -- $Name:Имя игры$
    -- $Version:0.1$
    instead_version '1.3.1'

    Имя, версия — это понятно из названия. instead_version — если хотите
использовать одно из нижеследующего:
    require 'para'
    require 'xact'
    require 'dash'
    require 'quotes'
    require 'dbg'

     Что означают эти модули?
     para — Ставит отступ в начале каждого параграфа.
     xact — Модуль позволяет делать ссылки на объекты из других объектов,
реакций и life методов в форме: {объект(параметры):текст}.
     dash — Заменяет последовательность символов -- на символ —. Хотя я
всегда стараюсь по тексту ставить сам длинное тире, где это нужно.
     quotes — типографские кавычки («») вместо стандартных ("");
     dbg — разрешает вызов из программы модуля отладки «на лету».

     Сразу добавьте в файл строчку
     game.use = 'Не получается...';
     Она нужна для обработки всех ситуаций использования предметов,
которые Вы не предусмотрели.
     Первая комната. Добавьте следующее:
     main = iroom {
           nam = '«Название игры»',
           pic = 'images/title.png',
           enter = function (s)
                set_music('music/music1.ogg');
           end,
           desc = function (s)
                return 'Описание';
           end,
           obj = { vway('1', '{НАЧАТЬ ИГРУ}', 'i101') },
     };
     Итак, что здесь у нас?
     nam — имя комнаты. Оно выводится сверху.
     pic — картинка. Старайтесь использовать файлы png для статичных
картинок и gif для анимированных.
     enter — функция, которая обрабатывается при входе в комнату. Здесь есть
мелкий нюанс. Когда герой должен выйти из текущей комнаты А и войти в
комнату Б, сначала выполняется функция exit в комнате А. После этого
выполняется функция enter в комнате Б. Если никаких запретов на переход нет,
то герой перемещается из комнаты А в Б. А если запрет есть? Герой остаётся в
комнате А, но код enter в комнате Б уже выполнился!
     Если Вы хотите чтобы в комнате Б выполнился какой-либо код когда герой
уже ТОЧНО перешёл в комнату Б, используйте функцию entered в комнате Б.
    Дружеский совет. Чтобы избежать подобных размышлений где какую
функцию использовать, старайтесь применять единый подход во всей игре. Я
никогда не использую запрет на переход в функции enter. Все запреты на
переход я пишу в функциях exit соответствующих комнат. Поэтому в моём
случае код enter выполнится ТОЛЬКО когда герой ТОЧНО переходит в
комнату.
    Продолжим рассматривать поля нашей комнаты.
    desc — описание комнаты.
    При реализации ремейка игры «Звёздное Наследие» я столкнулся с тем,
что в игре для каждой локации используется целых три описания. Первое,
самое длинное и пространное, выводится при первом посещении локации.
Второе и третье в зависимости от ситуации могут меняться в цикле или просто
выводится последовательно. В последнем случае третье описание выводится
при всех последующих посещениях локации.
    Данный подход значительно повышает удовольствие от игры. Куда
интереснее читать каждый раз новое сообщение, чем банальное «Вы
находитесь на берегу реки».
    В случае, если Вы решите использовать множественные описания для
локации, можно воспользоваться следующим кодом.
    location_A = iroom {
         nam = 'Комната',
         _visit = 0,
         enter = function (s, from)
              s._visit = s._visit + 1;
              if s._visit > 3 then
                    s._visit = 3;
              end
         end,
         desc = function (s)
              if s._visit == 1 then
                    return 'Текст описания локации 1';
              end
              if s._visit == 2 then
                    return 'Текст описания локации 2';
              end
              if s._visit == 3 then
                    return 'Текст описания локации 3';
              end
          end,
    В новых версиях INSTEAD API можно воспользоваться уже готовыми
счётчиками посещений. В этом случае код будет выглядеть следующим
образом:
    location_A = iroom {
         nam = 'Комната',
         desc = function (s)
              if visits() == 1 then
                    return 'Текст описания локации 1';
              elseif visits() == 2 then
return 'Текст описания локации 2';
              elseif visits() == 3 then
                    return 'Текст описания локации 3';
              end
          end,
     Да. Пока не забыл. Используйте нормальные английские слова для
обозначения переменных и локаций. Не используйте никаких транслитераций
для имён. У Вас не должно быть имён типа palka, komnata, ruka, chelovek и пр.
Не поленитесь, загрузите словарь. Заодно подтянете знания в английском.
     По возможности используйте разные имена для предметов, которых будет
более одного в игре. Скажем, в игре «Зеркало» я дважды столкнулся с этими
граблями. Кто же знал, что в игре два сундука и два камня? Поэтому не
ленитесь и давайте названия предметам подлиннее, скажем, forest_boulder если
это у Вас камень в лесу. Или castle_chest, если у Вас сундук находится в замке.
Только если Вы абсолютно уверены в том, что предмет в игре у Вас будет в
единственном экземпляре, давайте ему простое имя.
     Вернёмся к описанию комнаты. obj. Обычно здесь перечисляются все
объекты, которые присутствуют на локации.
          obj = { vway('1', '{НАЧАТЬ ИГРУ}', 'i101') },
     Но этой строкой реализуется ссылка на следующую комнату.
     Но где же меню? А его ещё нет. Оно не инициализировано.

    7. Инициализация меню

     Вызывать меню лучше всего в функции exit при выходе из комнаты с
предварительным описанием. Как правило, в любой игре есть небольшое
«интро», или «введение». После «введения» герой попадает в первую комнату.
В этот момент и надо вызывать инициализацию меню.
     Пример функции exit. Здесь же Вы можете, кстати, подарить предметы
главному герою.
          exit = function (s, to)
                actions_init();
                me():enable_all();
                put (sword, i101);
                Take (sword);
          end,
     Почему здесь не использовано inv():add(sword) или take (sword) для
добавления меча в инвентарь? Потому, что в этой игре все
поднимания/бросания предметов обрабатываются через функции Take и Drop
для учёта их веса.

    В дальнейшем, если Вам нужно отключить меню (скажем, при диалогах
или в комнатах, где делать ничего нельзя, а только прочитать текст и перейти
дальше), используйте команду
          me():disable_all();
    Включить меню обратно можно по аналогии:
          me():enable_all();
Еще есть модуль hideinv, который сам умеет прятать и показывать
инвентарь, но я его не использовал так как не люблю читать документацию.
     Пока не забыл. Используйте выравнивание кода. Добавляйте пробелы (ну
или табуляцию, хотя мне лично это нравится меньше). Это касается абсолютно
всех языков программирования. Не стоит писать код в виде:
     desc = function (s)
     if s._visit == 1 then
     return 'Текст описания локации 1';
     end
     end
     Отлаживать подобные конструкции весьма и весьма непросто.
Оформляйте код так, чтобы его было удобно читать с листа.

    8. Переходы между комнатами

    Существует несколько систем переходов. Стандартная для INSTEAD
система переходов отображает под картинкой названия локаций, куда можно
перейти. Достаточно наглядно и понятно.
    Другая система навигации — когда ссылки на переходы располагаются по
тексту. Очень неудобно и не всегда понятно, что нажав на ссылку с надписью
«Под лестницей я увидел сундук», Вы перейдёте в новую локацию с названием
«Под лестницей».
    Третий вариант. В системе меню используется компас для определения
направления движения. Такой принцип использован в «Звёздном Наследии».
Такой подход в совокупности с отсутствием ссылок в тексте позволяет
сосредоточить управление игрой только в области инвентаря. В этом есть свои
плюсы и свои минусы, но я постепенно склоняюсь к использованию именно
этого подхода.
    Итак, для обеспечения возможности стандартной для INSTEAD смены
локаций, в описании комнаты нужно иметь следующее:
    way = { vroom ('Окно', 'street'), vroom ('Вниз',
'floor_1'), },
    Здесь задаются ссылки на локации. Первый аргумент — название, которое
будет выводиться в области ссылок, второй аргумент — имя комнаты, в
которую нужно перейти.
    В данном примере под картинкой текущей локации будет выводиться:
          Окно | Вниз
    Если имена локаций и переходов совпадают, то можно написать просто:
    way = { 'street', 'floor_1' },
    Тогда под картинкой выведутся названия, взятые непосредственно из
локаций.
    Как уже упоминалось ранее, в функциях exit можно проверять
возможность перехода и запрещать её при необходимости.
    exit = function (s, to)
          if to == location_b then
               if not location_abc._access_granted then
                    p 'Меня не пустили.';
return false;
               else
                     p 'Я вошёл в новую комнату.';
                     return true;
               end
         end
    end,

    В примере проверяется флаг _access_granted в локации location_abc и в
зависимости от него переход либо разрешается, либо нет.

    Предпочтительнее использовать именно конструкции типа
          p 'текст';
          return false;
    нежели чем конструкции
          return 'текст', false;
    Это связано с тем, что если текст очень длинный, придётся листать до
конца длинной строки чтобы посмотреть разрешается ли переход или нет. В
случае, когда текст выделен в отдельную строку, признак разрешения перехода
виден сразу.

    9. Последовательность функций в описании комнаты

    В принципе, никаких особых правил в последовательности функций нет.
Однако, гораздо удобнее использовать один заранее принятый принцип. Я
использую следующую последовательность:
    1) имя локации;
    2) переменные и флаги локации;
    3) функция отображения картинки
    4) функция enter;
    5) функция описания локации;
    6) выходы из локации;
    7) объекты локации;
    8) функция выхода из локации.
    Иначе говоря:
    location_a = iroom {
          nam = '...',
          _visit = 0,
          pic = function (s)
               return 'images/image.png';
          end,
          enter = function (s)
               ....
          end,
          desc = function (s)
               ....
          end,
          way = {...},
obj = { ... },
         exit = function (s)
              ....
         end,
    };
    Данная последовательность как бы повторяет описание сцены. Вы можете
придумать свою последовательность.

    10. Предметы

     Наличие предметов в квесте отличает настоящий квест от книги-игры. Да,
в некоторых книгах-играх есть предметы, которые герой таскает с собой. Но в
настоящем квесте можно любой предмет в любой локации (за редкими
исключениями) попытаться отдать кому-нибудь или бросить им в кого-нибудь
и так далее. Варианты взаимодействия с объектами сцены могут быть самыми
разнообразными. Вообще, ключ к увлекательности квеста — в разнообразии.
Предоставьте герою свободу выбора, и он Вам скажет за это огромное спасибо.
     Итак, предметы. Типичное описание предмета выглядит следующим
образом:
     unmovable_object = iobj {
          nam = 'предмет',
          _examined = false,
          desc = nil,
          exam = function (s)
               if not s._examined then
                    s._examined = true;
                    return 'Осматриваем предмет впервые.';
               else
                    return 'Описание при повторном осмотре.';
               end
          end,
          take = function (s)
               p 'Это взять нельзя.',
               return false;
          end,
          drop = function (s)
               return 'Я бросил предмет.',
          end,
          useit = function (s)
               return 'Используем предмет сам на себя.';
          end,
          used = function (s, w)
               if w == object_1 then
                    return 'Текст 1';
               end
               if w == object_2 then
                    return 'Текст 2';
               end
end,
    };

     В типичных текстовых квестах выводится описание предметов,
присутствующих на сцене в виде:
     Я вижу стол. Я вижу стул. Я вижу яблоко. Оно лежит на
столе. Я вижу нож. Я вижу шкаф.
     При этом имена предметов являются ссылками на вызов функции
детального изучения объекта exam. После «Звёздного Наследия» я стараюсь
уходить от подобного перечисления предметов. Это выглядит коряво с точки
зрения русского языка, когда красивое описание локации, которое Вы
придумывали часами, завершается «я вижу то, я вижу это...»
     Наличие пункта меню «ОСМОТРЕТЬ» позволяет выводить список
предметов в локации и игрок может вызвать функцию exam для каждого из
предметов. Именно поэтому в приведённом выше примере desc предмета равно
пустоте, или nil. В принципе, его можно просто опустить. Но nil, на мой взгляд,
выглядит понятнее.
     Если у Вас текст при первом осмотре выводится длинный и подробный, а в
дальнейшем выводится просто краткое описание, удобно использовать флаг
_examined (или можете назвать как угодно), который выставляется при первом
осмотре предмета.
     take. Не все предметы в Вашей игре можно будет брать. Если данная
функция отсутствует в описании предмета, брать предмет нельзя. При этом
будет выводиться стандартная дежурная фраза из файла actions.lua. Лучше
вводить в свой код эту функцию всегда, и определять самому какой текст
выводить для каждого предмета.
     drop. Функция бросания предмета. Здесь кроется очень часто встречаемая
ошибка. Если описание этой функции отсутствует у предмета, то предмет не
только нельзя бросить, но его ещё и не получится передать никому. Поэтому
старайтесь всегда определять эту функцию в каждом предмете. Можете
возвращать return false, но всё равно старайтесь чтобы функция была. Во
избежание возможных проблем.
     Функция useit. Функция, вызываемая когда предмет используют сам на
себя. То есть два раза подряд в меню ИСПОЛЬЗОВАТЬ выбирают этот
предмет.
     Скажем, если предмет — перчатки (можете посмотреть игру «Кайлет»), по
функции useit герой либо надевает их, либо снимает. При этом надо не
забывать анализировать текущее состояние перчаток (одеты/сняты) и
выставлять соответствующий флаг.
     Ну, или если это, скажем, какой-то портал, герой может перемещаться в
другую локацию при использовании этого предмета.
     used. Функция использования предметов друг на друга. В примере, в
частности, описывается использование предмета object_1 на текущий предмет,
а также предмета object_2 на текущий предмет.
     Если в Вашей игре десятка два предметов и десяток людей (а их может
быть и гораздо больше), придумать внятное описание при использовании
предметов друг на друга становится достаточно проблематично. Но если Вы
предусмотрите все варианты и попытаетесь с лёгким юмором обыграть
ситуацию, когда игрок будет пытаться выполнять нелогичные действия,
удовольствие, получаемое при прохождении Вашего квеста возрастёт в разы.
Да, это потребует серьёзных усилий, большого объёма кода и фантазии на
разнообразные фразы-отклики на действия игрока. Но зато потом Вам скажут:
«В квесте понравилась адекватная реакция на неадекватные действия». Это
мелочь, но из мелочей складывается общее впечатление о Вашем проекте. Но
оцените это с точки зрения затрат на реализацию.
    Разумеется, можно и всего этого не делать. А запрограммировать только
действие нужных предметов на нужные объекты. Скорость разработки
подобного проекта быстрее в разы, однако игрок при попытке использовать
нелогичное сочетание предметов, будет получать одну и ту же дежурную
фразу. Какую? А мы её определили в файле main.lua:
    game.use = 'Не получается...';

    В любом случае, какой бы Вы подход при использовании предметов друг
на друга не выбрали (краткий, полный), фактически, на взаимодействии
предметов друг на друге основывается львиная доля всех квестов. Практически
все паззлы так или иначе связаны с использованием предметов. Очень редко
какие-то задачи можно решить с помощью диалогов или отдав кому-то нужный
предмет в нужное время и в нужном месте.
    На использовании предметов друг на друга кроется одна из самых
типичных ошибок. Я до сих пор делаю её с незавидной регулярностью.
    ПРАВИЛО: Всегда, если оба предмета остаются на сцене, необходимо
выставлять флаг того, что игрок уже произвёл запланированное действие.
    Скажем, нам нужно разрешить применить «топор» на «лес». В результате
должны остаться «лес» и «топор», но на сцене должен появиться новый
предмет — «бревно». Пишем код.
    forest = iobj {
          nam = 'лес',
          used = function (s, w)
               if w == axe then
                    objs():add(log);
                    return 'Вы срубили дерево.';
               end
          end,
    };
    Чем плох данный код? Тем, что игрок может бесконечно махать топором и
добавлять в сцену всё новые и новые брёвна. Что мы забыли? Проверить флаг
совершённого действия. Добавляем для этого переменную _tree_is_cutted и
проверяем её состояние в функции.
    forest = iobj {
          nam = 'лес',
          _tree_is_cutted = false,
          used = function (s, w)
               if w == axe then
                    if not s._tree_is_cutted then
                           objs():add(log);
                           return 'Вы срубили дерево.';
                    else
return 'Я уже срубил дерево.';
                    end
              end
         end,
    };
    Что мы забыли? А мы забыли установить флаг совершённого действия.
Это вторая типичнейшая ошибка. Если признаки совершённого действия рано
или поздно начинаешь вставлять уже на автомате, то вот выставлять их
забываешь постоянно.
    Правильный код выглядит так:
    forest = iobj {
         nam = 'лес',
         _tree_is_cutted = false,
         used = function (s, w)
              if w == axe then
                   if not s._tree_is_cutted then
                        s._tree_is_cutted = true;
                        objs():add(log);
                        return 'Вы срубили дерево.';
                   else
                        return 'Я уже срубил дерево.';
                   end
              end
         end,
    };

    Однако, это ещё не всё. Часто прежде чем позволять игроку выполнить
действие, необходимо проверять наличие предмета в инвентаре. Скажем, герой
бросил топор и пытается им срубить дерево. Или же вообще не выполнил паззл
по отниманию топора, который охраняет какой-нибудь персонаж. При этом
топор лежит на сцене и игрок пытается им срубить дерево. Одним словом,
топора в инвентаре нет. И необходимо выполнить проверку на это. В этом
случае код выглядит следующим образом:
    forest = iobj {
          nam = 'лес',
          _tree_is_cutted = false,
          used = function (s, w)
               if w == axe then
                    if not have (axe) then
                         return 'У Вас нет топора!';
                    else
                         if not s._tree_is_cutted then
                              s._tree_is_cutted = true;
                              objs():add(log);
                              return 'Вы срубили дерево.';
                         else
                              return 'Я уже срубил дерево.';
                         end
                    end
end
         end,
     };
     Иногда для удобства можно использовать глобальные переменные, тогда
не нужно сопровождать обращение к переменной s. Например:
     global { tree_is_cutted = false };
     ….
     if w == axe then
         if not tree_is_cutted then

    11. Передача предметов персонажам

    Передавать предметы можно двумя путями. Через функцию give самого
предмета, то есть:
    fish = iobj {
        nam = 'рыба',
        give = function (s, to)
                if to == eagle then
                    if eagle._hungry then
                          eagle._hungry = false;
                          Drop(s);
                          objs():del(s);
                          return 'Я отдал орлу рыбу.';
                    else
                          return 'Я уже накормил орла.';
                    end
                end
          end,
    };
    Подход с Drop и objs:del позволяет не проверять наличие предмета у героя.
Если предмета у героя нет, но он на сцене, функция Drop будет просто
проигнорирована. А objs:del удалит предмет со сцены.

    Вторым способом передачи предмета является функция accept у
персонажа, который предмет принимает.
    ВНИМАНИЕ!!! Поддержка в меню accept есть только в игре «Звёздное
Наследие». В играх «Зеркало» и «Кайлет» эта функция ещё не была
реализована.
    То есть
    seafood_hawker = iobj {
         nam = 'продавец',
         accept = function (s, w)
               if w == knife then
                    Drop (knife);
                    objs():del(knife);
                    p 'Я отдал нож продавцу.';
                    return true;
               end
               p 'Продавец сказал:^«Мне это не нужно.»';
return false;
         end,
    };
    Мне кажется более предпочтительным вариант с accept, когда для каждого
персонажа пишутся обработчики возможности принятия всех предметов и
возможные реакции на это. Данный подход позволяет группировать текст
ответов одного человека в одном куске кода.
    Но можете использовать тот подход, который Вам удобнее.

    12. Диалоги

    Ниже приведён код вызова диалога.
    seafood_hawker = iobj {
        nam = 'продавец',
        talk = function (s)
             return goto (seafood_hawker_dlg_1);
        end,
    };

     Теперь если вызвать меню ГОВОРИТЬ и в нём выбрать «продавец», будет
вызван диалог seafood_hawker_dlg_1.
     Не буду останавливаться подробно на описании диалогов. В документации
на INSTEAD они достаточно подробно описаны. Уточню только одну мелкую
деталь. Каждая строчка диалога включает в себя ответ персонажа на Вашу
реплику. Я для себя выработал правило, что в случае, когда при выборе
определённой реплики вызывается новый диалог, я всегда обнуляю ответ
персонажа. И реплику персонажа описываю в dsc нового диалога.
     То есть.
     seafood_hawker_dlg_1 = dlg {
          nam = 'Лоток мелкого торговца',
          pic = function (s)
               return 'images/near_seafood_hawker.png';
          end,
          enter = function (s)
               me():disable_all();
          end,
          dsc = function (s)
                    p 'Юркий китаец услужливо заглянул мне в
глаза.';
          end,
          obj = {
                    [1] = phr ('Как бизнес?', 'Китаец закивал:
«Хорошо, хорошо! Спасибо! А если купите у меня что-
нибудь, будет совсем замечательно»!', [[poff(1);]]),
                      [2] = phr ('Что есть на продажу?', nil,
[[pon(2); return goto (seafood_hawker_dlg_2);]]),
                        [3] = phr ('Закончить разговор.', nil,
[[pon(3); back();]]),
},
         exit = function (s)
              me():enable_all();
         end,
    };

     В данном примере вторая реплика вызывает новый диалог
seafood_hawker_dlg_2. Если ответ вставить здесь, в этой строчке, типа
                    [2] = phr ('Что есть на продажу?', 'Китаец
огласил          меню...',           [[pon(2);            return      goto
(seafood_hawker_dlg_2);]]),
     то данная фраза будет выведена только один раз. При любом изменении
экрана в диалоге seafood_hawker_dlg_2 будет выводиться только функция dsc
этого второго диалога.

    13. Темы для игры

     В начале разработки используйте одну из готовых тем оформления для
INSTEAD. Сейчас есть темы практически на все случаи жизни — для фэнтези и
фантастики, холодные, строгие, мягкие. В дальнейшем, если Вашему квесту
потребуется своя тема, Вы можете разработать её сами или попросить помочь
сделать это на форуме. Лучшим разработчиком тем является excelenter (он же
Terr в jabber-конференции).
     Но по началу не забивайте себе голову такой мелочью, как тема. Был бы
квест. Тема появится сама.

    14. Выпускайте релизы как можно чаще

     Старайтесь выпустить альфа-версию или бета-версию квеста как можно
раньше. Соберите несколько мнений людей по поводу Вашего творения. Из
числа тех, кому доверяете. Это позволит исправить какие-то вещи на ранних
этапах разработки. Это особенно важно если это Ваш первый проект. Скажем,
Вам могут сказать, что квест слишком линеен. И надо добавить несколько
паззлов и локаций, увеличить число диалогов. Или, что такой-то паззл слишком
прост или наоборот, слишком сложен.
     Насчёт сложности придуманных вами загадок. Попытайтесь по началу
расположить простые загадки, а самые сложные оставьте под конец игры. Но,
опять же, это не критично. Насчёт уменьшения сложности. Вы попробуйте
посмотреть старые квесты. Особенно на ZX. Очень сложные квесты с
множеством ситуаций, когда неправильная последовательность действий
приводит к невозможности дальнейшего прохождения игры в целом. При этом
никаких сообщений об этом не выводится. И ведь такие игры проходили! Я не
призываю Вас делать свои квесты подобным образом. Наоборот, лучше
пытаться уходить от ситуаций с невозможностью прохождения квеста после
неправильных действий. Но степень сложности загадок упрощать стоит не
всегда. Это же квест. Игрок должен подумать головой. В противном случае он
просто бегло прочитает текст, быстро прощёлкает мышкой предметы, пройдёт
за несколько минут то, что Вы писали несколько месяцев, зевнёт и скажет:
«Неплохо, но загадки все простенькие...»
    Насчёт чужого мнения по поводу игры. Также тут нужно учитывать два
аспекта.
    Первое. Поклонники квестов могут пожалеть Вас, и чтобы не убивать тягу
к творчеству, могут не указать на какие-либо недостатки. То есть
простимулировать заранее. Авансом. Авторы квестов рады выходу любого
квеста, пусть и самого примитивного и скучного. Поэтому могут оставить свою
истинную точку зрения при себе. Это плохо, но не так плохо как второй аспект.
    Второе. Вы рискуете нарваться на резкую критику людей, которые
считают себя «правдорубами» и «истинными приверженцами чистых квестов».
От подобных «критиков» Вы можете выслушать совсем незаслуженные слова.
    Поэтому доверяйте только проверенным людям. Это относится не только к
квестам. Используйте это правило в жизни.
    Удачных Вам квестов!

                                                           Вадим В. Балашов
                                                       vvb.backup@rambler.ru
                                                                  12.07.2011
Вы также можете почитать