Вход
Приветствую Вас Гость
 

easy.dialog v.3.0.0

Создание диалогов

Для написания диалогов, как уже было сказано выше, используется синтаксис edsynt. В этом разделе вы научитесь писать диалоги, используя данный синтаксис.

Разделы расположены в порядке усложнения правил написания, а не в порядке рассмотрения структуры диалога. Поэтому вкратце опишу, как сгенерированный диалог будет представлен в QSP.

Диалоги, написанные для модуля "easy.dialog", в QSP выглядят как записи в большой таблице данных, где каждая строчка таблицы является отдельной сущностью (объектом):

  • Диалогом;
  • Ролью;
  • Репликой.

Если абстрагироваться от структуры таблицы данных, можно представить, что в ней лежат только диалоги, а уже в диалогах лежат наборы ролей и реплики. Одни реплики вкладываются в другие реплики, и так далее.

Если вам это не понятно, то нет смысла вникать, пока не возникнет такая необходимость. Для написания простых диалогов, понимание этой структуры не требуется. Просто пишите каждый диалог в отдельном файле, конвертируйте их в формат QSP и вызывайте в игре.

edsynt

"edsynt" (дальше это слово будет без кавычек) — это самописный синтаксис, который должен облегчить написание диалогов, даже больших и сложных. Этот синтаксис используется только для работы модуля easy.dialog, так что скорее всего вы не услышите о нём за пределами данного руководства.

Подсветка edsynt для sublime-text так же поставляется вместе с релизной версией модуля, вы можете найти её в папке tools в виде готового пакета edg.sublime-package. Просто скопируйте пакет в папку Packages в место установки Sublime Text, и вам станет доступна подсветка edsynt для текстовых файлов с расширениями .edg и .edsynt.

Простые диалоги

Чтобы использовать модуль, нужно знать, как написать диалог, чтобы модуль вас понял.

Диалоги пишутся предельно просто и не требуют специальных знаний, кроме тех, которые вы получите сейчас.

Диалог - это разговор, как правило, двух персонажей. В игре один персонаж - это герой, которым управляет игрок, другой персонаж - неиграбельный, т.е. игрок условно не может им управлять. Персонаж игрока мы будем называть героем, а персонаж, с которым герой вступает в диалог, просто персонажем, актёром, или неписью (от англ. NPC - non-playable character).

Фразы, доступные игроку (и соответственно герою), должны выводиться в виде списка действий, доступных для выбора и выполнения. А фразы актёра должны выводиться непосредственно на экран автоматически. Собственно это все различия между одним типом фраз и другим.

Итак, у нас есть фразы NPC, которые выводятся автоматически, и фразы героя, которые появляются в виде действий и не используются в диалоге, пока игрок не выберет одну из них. Оба типа фраз помещаются между специальными метками. Я называю их тегами.

{: Фраза, которую будет произносить неиграбельный персонаж :} — фигурная скобка, двоеточие, потом идёт фраза персонажа, потом двоеточие и снова фигурная скобка. "{:" - открывающий тег. ":}" - закрывающий тег. Всё, что произносит актёр, помещаем между такими тегами. Каждая отдельная фраза должна помещаться между тегами. Например:

 {: Сегодня прекрасная погода. :}
{: Не думал я, что будет дождь! :}
{: На город набегают тучи, осенний сплин кого-то мучит... :}

[: Фраза, которая превратится в действие, а когда игрок выберет действие, выведется на экран. :] — квадратная скобка, двоеточие, потом идёт фраза героя, снова двоеточие, квадратная скобка. "[:" - открывающий тег, ":]" - закрывающий тег. Всё, что может сказать герой, помещаем между такими тегами. Опять же, каждая отдельная фраза - в отдельных тегах. И каждая такая фраза будет выведена одним действием.

Фразы, заключённые в те или другие теги, мы будем называть репликами.

Реплики героя и актёра можно располагать одна в другой, или несколько в одной. Мало того, можно располагать реплики одного типа друг в друге.

Зачем это нужно? А вот зачем: диалог собирается и распознаётся по принципу вопрос-ответ. Допустим, реплика актёра - это вопрос, а реплика героя должна быть ответом на вопрос. Но игроку можно предложить несколько вариантов ответов на вопрос. Чтобы написать такой простой диалог: вопрос и несколько ответов, - делаем так:

Первым делом пишем вопрос. Например, непись должна спросить героя "Как тебя зовут?". Пишем:

 {: Как тебя зовут? :}

А теперь ответы на вопрос "вкладываем" в вопрос:

 {: Как тебя зовут? 
   [:Вася:]
   [:Петя:]
   [:Не твоё дело!:]
   [:Я забыл...:]
:}

На экран выведется фраза актёра "Как тебя зовут?" и четыре действия. При щелчке на любом из действий, герой будет произносить фразу, написанную в этих репликах. Например, если игрок выберет действие "Я забыл...", на экран выведется фраза героя "Я забыл...".

Как сделать так, чтобы по щелчку на действии произносил фразу не только герой, но и актёр? Всё тем же способом. "Вложить" реплику актёра в реплику героя. Разовьём предыдущий пример:

 {: Как тебя зовут?
   [:Вася:]
   [:Петя:]
   [:Не твоё дело!:]
   [:А тебя как?
      {:Меня Васькой Пупкиным кличут:}
   :]
:}

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

 {:
   Как вас зовут?
   [:
      Меня зовут Вася.
      {:Вас зовут Вася?
         [:Да, меня зовут Вася.
            {:да ну нафиг:}
            [:
            нет. чистая правда
            :]
         :]
         [:Нет, никто меня так не зовёт:]
      :}
      {:
         Это правда, что вас зовут Васей?
         [:Правда
         :]
         [:Неправда:]
      :}
   :]
   [:
      Меня зовут Петя.
      {:
         Вас зовут Петя?
         [:Да{:Как это удобно!:}:]
         [:
            Нет
            {:Вас зовут не Петя? Как неудобно!:}
         :]
      :}
      {:
         Это правда, что вас зовут Петей?
         [:Правда:]
         [:Неправда:]
      :}
      {:Неужели в самом деле Петей Вас зовут?
         [:В самом деле:]
         [:Нет, не в самом:]
      :}
   :]
:}

Обратите внимание на то, как записаны фразы, которые должны выводиться на экран в диалоге. Перед ними есть пустые строки, символы пробелов и табуляций. Модуль сам очистит фразы от мусора и уберёт пустые строки перед фразой и после неё, а так же пробельные символы перед каждой строкой и после неё. То есть даже если вы вставите фразу между тегами вот так:


 {:







                  Как вас зовут?






:}

То на экран всё равно будет выведено: "Как вас зовут?" без пробелов перед репликой и пустых строк. Хотя лучше, конечно, избегать избыточного преформатирования.

Назначение ролей. Форматирование реплик

Итак, мы научились составлять простейшие диалоги, используя два типа Реплик. Реплики героя воспроизводятся сначала в виде действий, и только после нажатия (активации) выводятся на экран в лог диалога, и потому вдальнейшем мы будем называть их активируемыми репликами. Реплики актёра воспроизводятся в логе автоматически, и поэтому мы будем называть их пассивными репликами.

Чтобы реплики относящиеся к разным актёрам и герою не сливались в одно монотонное месиво, нам нужно каким-либо образом обозначить, кто произносит ту или иную фразу. И с этим делом нам помогут Роли.

Роли — это специальные объекты в диалоге, которые необходимы, чтобы настроить внешний вид отображения Реплик.

Если вы не добавите Роли в свой диалог, будут использованы Роли по умолчанию.

Вы можете создать сколь угодно большое число Ролей на каждый диалог, однако, как правило, требуется не более трёх: Роль для героя, Роль для неписи и Роль для ремарок (технически Роль для ремарок это тоже Роль для неписи, поскольку она используется для пассивных реплик).

Описывать Роли следует в самом начале файла с диалогом.

Первым делом перечисляем идентификаторы Ролей в специальной переменной actors.

actors="hero; npc; remarka; other; etc"

Идентификаторы Ролей перечисляются через точку с запятой, можно дополнительно отделять их пробелами.

К идентификаторам ролей предъявляются те же требования, что и к именам переменных в QSP, c дополнительным условием: в них не должны использоваться никакие спецсимволы, включая $.

Далее необходимо сделать описание каждой роли. Для этого нужно создать специальные описательные блоки, границы которых обозначаются невалидными html-тегами. Название каждого такого тега состоит из слова "actor" и идентификатора роли, поставленного через точку. Примеры:

actors="hero; npc; remark;"
   
   <actor.hero> </actor.hero>

   <actor.npc> </actor.npc>

   <actor.remark> </actor.remark>

Внутри этих описательных блоков необходимо разместить настройки Ролей, а также блоки обёрток.

  • Для активируемых реплик, это блок, ограниченный html-тегами <wrap.btn></wrap.btn>. Он отвечает за внешний вид действий (кнопок).
  • Для пассивных и активируемых реплик, это блок, ограниченный html-тегами <wrap.frase></wrap.frase>. Он отвечает за внешний вид выводимых на экран (в лог диалога) фраз.

Блоки обёрток должны содержать валидный код QSP, который и будет формировать обёртку реплик для указанной Роли. Роль может не содержать ни одного из этих блоков, тогда будет использоваться обёртка для реплик, вшитая в интерпретатор.

Как написать собственную обёртку для реплик, описано в разделе Обёртки.

Помимо обёрток вы можете использовать для описания каждой Роли наборы собственных параметров (переменных). Пример:

   actors="hero; npc;"
   
      <actor.hero>
         name="Балур Светосильный"
         age="17"
      </actor.hero>

      <actor.npc>
         name="Темнейший Злеус"
         age="1069"
      </actor.npc>

Подобные переменные полностью переносятся в объект Роль, и их можно легко извлекать, что опять же демонстрируется в разделе Обёртки.

Одна из описанных Ролей должна быть помечена, как роль по умолчанию для активируемых реплик (это делается с помощью одиночного тега <default_active>), и одна роль должна быть помечена, как роль по умолчанию для пассивных реплик (это делается с помошью одиночного тега <default_passive>).

Подключение ролей

При описании Роли можно подключить к ней стороннюю Роль. При этом в исходную Роль копируются все настройки подключаемой Роли, кроме тегов, устанавливающих роль по умолчанию.

   actors="hero; npc;"
   
      <actor.hero>
         name="%$property['heroname']%"

         include_role:DIALOG.ROLE
      </actor.hero>

Здесь DIALOGуникальное название диалога, а ROLE — уникальная метка роли в указанном диалоге.

Общие настройки диалога

Настройки диалога можно прописывать как до описания ролей, так и после, это не имеет значения.

dialog_usrid. Пользовательский идентификатор диалога

Параметр dialog_usrid является обязательным для любого диалога.

С его помощью указывается уникальное название диалога (пользовательский идентификатор). К этому названию применяются те же требования, что и к именам переменных в QSP, и дополнительно такое название не должно содержать никаких спецсимволов и знаков, в том числе $ и ..

Если вы не укажете уникальное название, или названия будут совпадать в нескольких диалогах, генератор предупредит вас об этом.

Пример:

dialog_usrid="barmen"

По данному уникальному имени вы сможете вызывать диалог у себя в игре:

@dialog.int('barmen')

strings. Число фраз в логе

Настройка strings позволяет указать число фраз из лога диалога, которые игрок будет видеть на экране. По умолчанию — 10.

actors="hero; npc;"
   
   <actor.hero>name="%$property['heroname']%"</actor.hero>
   <actor.npc>name="%$DIALOG_VALUE['current_npc_name']%"</actor.npc>

strings:10

Вместо 10 можно указать своё число. Если в логе будет меньше фраз, выведутся все фразы из лога.

Обёртки

По сути обёртки представляют собой html-разметку, в которую заворачиваются фразы героя или NPC. Однако, реализованы обёртки, как обычный код QSP, что позволяет нам довольно гибко извлекать данные из Роли, оборачивать фразы в любую разметку, форматируя диалог так, как нам удобно.

Как уже было сказано в разделе Назначение ролей. Форматирование реплик, обёртки прописываются в роль с помощью тегов <wrap.btn></wrap.btn> и <wrap.frase></wrap.frase>, и представляют собой валидный код QSP. При написании собственной обёртки, её следует рассматривать, как функцию, которой передаются три аргумента:

  • $args[0] - идентификатор текущей роли
  • $args[1] - идентификатор родительской роли
  • $args[2] - фраза, которую необходимо вывести в лог диалога, или в виде действия (кнопки).

Результатом выполнения кода обёртки должна быть фраза реплики, завёрнутая в HTML-код. Поэтому нужно использовать переменную $result для возвращения результата.

Вот как может выглядеть пример обёртки для выводимых в лог диалога фраз:

local $name_ = @dialog.role.get_set($args[0], 'name')
$result = '<span style="color:#880000;"><<$name_>></span>: — <<$args[2]>>'

А вот так может выглядеть обёртка для кнопок:

$result = '<div class="avs-act-button"><<$args[2]>></div>'

Для примера, вот так могут выглядеть обёртки в описании Роли:

<actor.hero>
   name="Вы"

   <default_active>
   <wrap.btn>
      !@ внешний вид кнопок для ответов.
      $result += '<div class="avs-act-button"><<$args[2]>></div>'
   </wrap.btn>
   <wrap.frase>
      !@ внешний вид реплик в логе диалога Извлекаем имя из роли:
      local $name_ = @dialog.role.get_set($args[0], 'name')
      $result += '<div class="avs-hero-replic">'
         $result += '<span style="color:#000000;"><<$name_>>:</span>'
         $result += '<span style="color:#000000;"> — <<$args[2]>></span>'
      $result += '</div>'
   </wrap.frase>
</actor.hero>

Настройки отображения реплик

Настройки отображения влияют на то, как реплики будут выглядет на экране, когда они представлены в виде действий (кнопок), или фраз. Есть настройки, которые влияют на отображение реплик сразу в целой ветке, а есть которые влияют на отображение отдельной реплики (см. раздел Наследование).

Настройки отображения отдельных реплик

btn_name

btn_name: :btn_name — для активируемой реплики устанавливается указанное название кнопки. Данная настройка имеет приоритет перед act_lenght. Пример:

[:
   Никогда не думай, что ты иная, чем могла бы быть иначе, чем будучи иной в тех случаях, когда иначе нельзя не быть.

   btn_name:Никогда не думай, что ты иная...:btn_name
:]
actor_this

actor_this — ссылка на роль, применяему только к этой реплике, и не наследуемую вложенными репликами. Можно указать в следующем формате: DIALOG.ROLE, — где DIALOG — это уникальное название диалога, а ROLE — идентификатор роли в этом диалоге. Пример:

{:
   Как Вас зовут?
   {:   
      actor_this:remarka <!-- здесь указан только идентификатор роли -->
      Что Вы на это ответите?
   :}
   [:Петя:]
   [:Вася <!-- здесь указано уникальное название диалога,
               а через точку идентификатор роли -->
      actor_this:barmen.npc
   :]
:}

Настройки отображения реплик для веток

actor_act

actor_act — ссылка на роль, которая будет применяться для всех активируемых реплик в этой ветке. Иными словами, это наследуемая настройка для реплик, вложенные реплики унаследуют эту настройку от старших. Можно указать в следующем формате: DIALOG.ROLE, — где DIALOG — это уникальное название диалога, а ROLE — идентификатор роли в этом диалоге. Пример:

{:
   actor_act:barmen.hero <!-- Все активируемые реплики в данной ветке диалога
                            будут выводиться с форматированием для роли hero
                            из диалога с уникальным названием barmen -->
   Как Вас зовут?
   {:   
      Что Вы на это ответите?
   :}
   [:Петя:]
   [:Вася:]
:}
actor_pass

actor_pass — ссылка на роль, которая будет применяться для всех пассивных реплик в этой ветке. Иными словами, это наследуемая настройка для реплик; вложенные реплики унаследуют эту настройку от старших. Можно указать в следующем формате: DIALOG.ROLE, — где DIALOG — это уникальное название диалога, а ROLE — идентификатор роли в этом диалоге. Пример:

{:
   Спрашивай, не стесняйся, у меня самый продвинутый диалог!
   {:
      actor_pass:aragorn.npc <!-- Все пассивные реплики в данной ветке диалога
                            будут выводиться с форматированием для роли npc
                            из диалога с уникальным названием aragorn -->
      [:
         Как реализован твой диалог?
         {:
            Ну, это довольно просто...
         :}
      :]
      [:
         А чем бармен с тем алкоголиком заведуют?
         {:
            Бармен показывает пример самого простого линейного диалога...
         :}
      :]
   :}
:}
btn_length

btn_length — устанавливает максимальное число символов от длины фразы активируемой реплики для названия кнопки. По умолчанию 64 символа. Иными словами, если фраза активируемой реплики превышает 128 символов в длину, движок автоматически подрежет эту фразу до 64 символов, когда будет генерировать кнопку.

[:
   Никогда не думай, что ты иная, чем могла бы быть иначе, чем будучи иной в тех случаях, когда иначе нельзя не быть.

   btn_length:50 <!-- в данном случае на кнопке будет написано:
   Никогда не думай, что ты иная, чем могла бы быт...
   -->
:]

Настройки порядка вывода реплик

Настройки порядка вывода реплик всегда прописываются для целых веток. Иными словами они являются наследуемыми; вложенные реплики наследуют настройки от старших реплик.

repeat

repeat — данная настройка регулирует количество повторов вывода пассивных реплик. Можно указать три значения:

  • one — выводится одна реплика. Например, выбранная случайно.
  • once — все реплики выводятся по одному разу, после чего выводится только последняя выведенная реплика.
  • cicle — реплики выводятся по кругу, т.е. имеют свойство повторяться (режим по умолчанию).

Пример:

[:
   Вы не знаете, как попасть в город?

   repeat:one

   {:Отвали!:}
   {:Я занят!:}
   {:Позвоните попозже...:}
:]
shuffle

shuffle — настройка последовательности вывода пассивных реплик. Можно указать два значения:

  • random — реплики выводятся в случайном порядке.
  • straight — реплики выводятся от первой к последней, как они прописывались в файлах edsynt

Управление репликами

Простых ветвистых диалогов иногда бывает недостаточно. Иногда необходимо задать дополнительное поведение отдельным репликам, или даже воздействовать на диалог целиком. Для таких случаев в синтаксисе edsynt предусмотрены специальные команды и параметры.

selrepl.del

selrepl.del — на время интерпретации диалога "удаляет" реплику из сеанса интерпретации. То есть, если реплика помечена этой меткой, и была один раз воспроизведена, она помечается, как скрытая, и больше не воспроизводится, только если на неё не произведут целенаправленный переход. При повторном посещении диалога реплика снова доступна до первой интерпретации.

selbtn.del

selbtn.del — удаляет кнопку из списка, но лишь пока не произойдёт повторная интерпретация ветки с репликой, помеченной этой меткой (грубо говоря удаляет действие из списка, не удаляя прочие). Используется в примере "Диалог Арагорна":

[:
   И сколько всего этих "блоков"? И какие они?
   <!-- selbtn.del -->
   {:
      Ну, первый блок, это блок приветствия. В него ты попадаешь в самом начале. Оттуда тебя "перекидывает" в основной блок вопросов, второй по счёту, а за ним идут блоки вопросов, для каждого вопроса — свой блок.
   :}
:]
btn.fix
selrepl.kill

selrepl.kill — реплика, помеченная этой меткой, полностью удаляется из таблицы.

frase_block

<frase_block> </frase_block> — между этими тегами размещается блок фраз. Каждая строчка такого блока считается отдельной фразой. Если нужно разделить строки внутри фразы, можно использовать html-теги вроде <br> и <p></p>.

Чтобы к строке применилась необходимая роль, в начале строки нужно использовать тег <actor:HERO>, где вместо HERO нужно указать идентификатор роли в текущем диалоге, или DIALOG.ROLE. Здесь DIALOG — уникальное название диалога, а ROLE — идентификатор роли в указанном диалоге.

Пример из "диалога Чешира":

<frase_block>
   <actor:npc>У тебя одна минута на то, чтобы объяснить мне, как добраться до хранилища.
   <actor:hero>Что?
   <actor:remarka>Бац! В глазах сверкнуло и боль такая сильная, что кажется, будто она — единственное, что я сейчас чувствую. Даже сильнее страха.
   <actor:npc>Одна минута, — <em style="color:#888888;">говорит он и прижимает холодный кружок дула к моему лбу.</em>
</frase_block>
if

<if> </if> — если нам нужно, чтобы реплика воспроизводилась только при определённом условии, между этими тегами записывается валидное условие для QSP.

Например, если бы мы использовали обычный код QSP, наше условие могло бы выглядеть так:

if obj('Синий крокодил'):
   ! выводим реплику в лог
   *pl '— Я гляжу, ты везде таскаешь с собой это чудовище! Продай его мне.'
end

Ну а в edsynt этом может выглядеть так:

{:
   <if>obj('Синий крокодил')</if>
   Я гляжу, ты везде таскаешь с собой это чудовище! Продай его мне.
:}
dynamic_code

<dynamic_code> </dynamic_code> — между этими тегами размещается валидный код QSP, который будет выполнен при интерпретации реплики, перед выводом её в лог диалога.

С помощью данных тегов можно фиксировать состояние диалога во внешних переменных, изменять состояния квестов, предметов и т.п. Например, в "диалоге Арагорна" так реализована очистка окна от всех кнопок перед закрытием диалога, а так же задержка перед закрытием диалога.

[:
   Ладно, пойду я, пожалуй.
   <!-- очистка списка действий <dynamic_code>killvar '$DIALOG_BUTTONS'</dynamic_code>-->
   {:
      <!-- в оригинале перед этой репликой стоит задержка, потом вывод реплики, снова задержка, потом закрытие -->
      <!-- динамический код в этой реплике осуществит задержку, после чего она появится на экране -->
      Ну, прощай тогда!
      <!-- <dynamic_code>wait 500</dynamic_code> -->
         {:
            <!-- Эта реплика не появится на экране, она выполнит задержку и closeup -->
            <!-- <dynamic_code>wait 2000</dynamic_code> -->
         :}
   :}
:]

В выполняемый код нулевым аргументом передаётся айди (первичный ключ) реплики, в которой этот код лежит. Таким образом можно использовать в этом коде данные о реплике, и различными способами управлять ей.

marker

marker — с помщью данного параметра устаналвиваем на реплику уникальную для всего диалога метку. В пределах одного диалога метки не должны повторяться, но между диалогами реплики могут повторяться. Если вы ошиблись и указали две одинаковые метки в диалоге, генератор сообщит вам об этом.

Пример:

{:
   marker:dialog_struckture
:}
levelup

levelup — переход по узлам наверх (назад). Например, от текущей реплики вам нужно вернуться на две реплики вверх (назад), тогда указываете:

levelup:2
leveljump

leveljump — переход на указанную метку. Можно указать метку в текущем диалоге, или DIALOG.MARKER, где DIALOG — уникальное название диалога, а MARKER название метки в этом диалоге. В последнем случае вас фактически перебросит в другой диалог, поэтому будьте внимательны при указании меток вне текущего диалога.

Пример:

[:
   А как организован твой диалог?
   {:<!-- leveljump:block_stick -->Ну, это довольно просто. Диалог состоит из нескольких блоков, после того как ты выберешь один из вариантов, он удаляется из списка действий. Иногда удаляются все действия, а вместо них добавляются новые. Как сейчас, например.:}
:]
closeup

closeup — закрывает текущий диалог, восстанавливая окно основного описания к состоянию до диалога.

replic_app

replic_app — подключение реплики из другого диалога. Указывается DIALOG.MARKER, где DIALOG — уникальное название диалога, реплику из которого мы хотим подключить, а MARKER — метка реплики в этом диалоге.

Наверх