Как мне реализовать ветвление диалога в JavaScript?

11

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

В настоящее время я держу сценарий для игры в строковой переменной и разбиваю каждую сцену с помощью тега, такого как «# ~», на меньшие массивы, чтобы сценарий игры выглядел следующим образом:

var script = "Hello World!#~How are you today?"
var gameText = script.split("#~");
//gameText[0]= Hello World!

Это прекрасно работает для линейных вещей, но как мне обработать ветку в дереве диалога? Этот метод кажется очень сложным, так как я должен был бы точно знать, какова длина каждого пути, а затем, если мне когда-нибудь понадобится что-то изменить, это будет головной болью.

Как я могу сделать это более простым способом? Я пытаюсь придерживаться ванильного JavaScript, поскольку я хотел бы, чтобы игра работала с Web Run Time.

Тихий картограф
источник
Это видео может дать вам несколько идей: youtube.com/watch?v=XM2t5H7kY6Y
JCM
Недавно мне пришлось разработать что-то для этого с помощью Node, и я выбрал очень простую структуру текстовых файлов. Вы можете увидеть полученный код и текстовый формат по адресу: github.com/scottbw/dialoguejs Код GPL, не стесняйтесь использовать его. Я уверен, что не составит труда адаптироваться к игре не-Node JS - замените часть "fs.load ()" на Ajax.
Скотт Уилсон
Проверьте Ink , язык сценариев ветвящихся историй, разработанный Inkle Studio . Существуют различные программные интеграции чернил (Java, Javascript, C #) и многие сторонние ресурсы . Чернила также использовались во многих коммерческих играх. Наконец, есть настольный редактор Inky, который может проверять синтаксис и «проигрывать» ваши ветвящиеся диалоги.
Большой Богатый

Ответы:

8

Ответ Филиппа уже показывает правильное направление. Я просто думаю, что структура данных излишне многословна. Более короткие тексты было бы легче писать и читать.

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

var story = [
  { m: "Hi!" },
  { m: "This is my new game." },
  { question: "Do you like it?", answers: [
    { m: "yes", next: "like_yes" },
    { m: "no", next: "like_no" },
  ] },
  { label: "like_yes", m: "I am happy you like my game!", next: "like_end" },
  { label: "like_no", m: "You made me sad!", next: "like_end" },
  { label: "like_end" },
  { m: "OK, let's change the topic" }
];

Некоторые объяснения этого дизайна:

Вся история написана в одном массиве. Вам не нужно указывать числа, они предоставляются автоматически с помощью синтаксиса массива: первый элемент имеет индекс 0, следующий - индекс 1 и т. Д.

В большинстве случаев нет необходимости записывать номер следующего шага. Я предполагаю, что большинство строк текста не являются ветвями. Давайте сделаем «следующий шаг - следующий элемент» предположением по умолчанию и будем делать пометки только тогда, когда это не так.

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

Найдите разумный компромисс между ясностью и краткостью. Например, я предлагаю написать «m» вместо «message», потому что это будет самая часто используемая команда из всех, поэтому ее сокращение сделает текст более разборчивым. Но нет необходимости сокращать оставшиеся ключевые слова. (Тем не менее, делайте, как хотите. Важно сделать его максимально разборчивым для вас . В качестве альтернативы вы могли бы поддерживать и «m», и «message» в качестве допустимых ключевых слов.)

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

function execute_game() {
  var current_line = 0;
  while (current_line < story.length) {
    var current_step = story[current_line];
    if (undefined !== current_step.m) {

      display_message(current_step.m);
      if (undefined !== current_step.next) {
        current_line = find_label(current_step.next);
      } else {
        current_line = current_line + 1;
      }

    } else if (undefined !== current_step.question) {

      // display the question: current_step.question
      // display the answers: current_step.answers
      // choose an answer
      // and change current_line accordingly

    }
  }
}

Кстати, эти идеи были вдохновлены Ren'Py , который не совсем то, что вы хотите (не JavaScript, не веб), но в любом случае может дать вам несколько интересных идей.

Вильям Бур
источник
Спасибо за подробное объяснение, я не знал, что массивы могут работать так, как вы с Филиппом, я думал, что они могут содержать только строки или числа.
Тихий картограф
1
Я пытался реализовать ваше решение, и оно работает довольно хорошо, хотя я думаю, что в некоторых местах ({ label: "like_yes"; m: "I am happy you like my game!"; next: "like_end" },)должно быть «,» а не «;». Кроме того, что именно называется в фигурных скобках? Это объект в массиве? если бы я хотел больше информации о том, как использовать это, что бы я искал?
Тихий картограф
Да, {...}это объект. В JavaScript объект - это ассоциативный массив ключ-значение (с некоторыми дополнительными функциями, не используемыми в этом примере), подобный массиву в PHP или Map в Java. Для получения дополнительной информации см. Статьи Википедии о JavaScript и ECMAScript, а также документацию, связанную с ними, особенно официальную документацию ECMAScript.
Вильям Бур
1
Кстати, обратите внимание, что структура данных, которую он рекомендует здесь, в основном JSON. Лично я бы рекомендовал пройти весь путь до JSON (в основном добавить фигурные скобки и «дерево»: вокруг всего объекта; например, {«дерево»: [и т. Д.}}), А затем вы можете сохранить свои диалоговые деревья во внешних файлах. Размещение ваших данных во внешних файлах, которые загружает ваша игра, гораздо более гибкое и модульное (поэтому такой подход является наилучшей практикой).
Джокинг
5

Я бы порекомендовал вам создать массив событий диалога. Каждое событие - это объект, содержащий текст, который говорит NPC, и массив возможных ответов игрока, которые, в свою очередь, являются объектами с текстом ответа и индексом события, которое следует за этим ответом.

var event = []; // create empty array

// create event objects and store them in the array
event[0] = { text: "Hello, how are you?",
             options: [    { response: "Bad", next: 1 },
                           { response: "Good", next: 2 }
                      ]
           };
event[1] = { text: "Why, what's wrong?",
             options: [    { response: "My dog ran away", next: 3},
                           { response: "I broke up with my girlfriend", next: 4}
                      ]
           };
event[2] = { text: "That's nice",

...
Philipp
источник
2

Вы должны использовать другой подход. JavaScript поддерживает массивы и объекты, так почему бы не использовать по одному для каждой записи, сэкономив все разделение, а также упростив редактирование / чтение реального текста?

Если хотите, можете взглянуть на прототип, который я сделал за несколько часов для игры №1 . Исходный код можно бесплатно использовать под GPLv3 (я прекрасно подхожу, если вы не придерживаетесь GPL, если вы просто используете его для вдохновения. Но дайте мне знать, как только ваша игра будет закончена.). Только не ожидайте, что вы будете писать что-нибудь классное ;)

Чтобы дать краткое объяснение того, как работает код, игнорируя CSS-анимацию и тому подобное:

  • var data по сути, содержит всю историю со всеми возможными вариантами и т. д.
  • Каждое «местоположение» (или страница / запись) идентифицируется идентификатором. Первый идентификатор в списке start, второй cwaitи т. Д.
  • Каждое место содержит два обязательных элемента: подпись, а также фактический текст. Ссылки для принятия решений написаны в простой разметке в форме [target location:display text].
  • Внутри происходит вся «магия» navigate(): эта функция делает ссылки на разметку кликабельными. Это немного дольше, потому что я также обрабатываю некоторый статический текст для мертвых концов там. Важной частью являются первые два звонка replace().
  • Необязательные последние записи определяют новые цвета фона, которые будут смешиваться, поддерживая общее настроение игры.
  • Вместо определения этих цветов вы также можете добавить ссылки, указывающие на другие местоположения (обратите внимание, что это не обрабатывается моим кодом; это просто идея продемонстрировать это):

    'start': ['Waking up', 'You wake...', 'cwait:yell for help', 'cwait: wait a bit', 'clook: look around']

Марио
источник