Как работать с несколькими сюжетными нитями в RPG-игре?

26

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

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

Я видел много игр со сложными механизмами принятия решений, и мне интересно, как они сделаны? Есть ли специальный дизайн, который делает вещи действительно легкими? Кто-нибудь делал что-то подобное и может подсказать, как с этим бороться?

ОБНОВЛЕНИЕ 1:

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

Кроме того, меня интересует дизайнерское решение, хороший способ сделать это, как это делают другие.

Валентин Раду
источник
1
Когда важные решения приняты, просто следите за ними в глобально доступной переменной (массив этих переменных будет легче управлять). На эти переменные могут ссылаться более поздние части вашей игровой программы, чтобы они действовали или отображали вещи соответствующим образом. Например, игрок решает посадить маленькое дерево, и позже это дерево кажется очень большим - если они не посадили дерево, то этого дерева вообще не было бы в тот же более поздний момент в игре.
Рэндольф Ричардсон
Да, это то, что я изначально хотел, однако, мне нужно, чтобы это было независимым от кода. Это означает, что история может быть полностью изменена из внешнего файла. Итак, я должен найти способ обобщить то, что вы только что сказали, и сделать это таким образом, чтобы я не потерял контроль над проектом (есть довольно много решений). Буду обновлять вопрос. Благодарность!
Валентин Раду
Поэтому, чтобы быть более конкретным, я не могу проверить if (isTree)или сохранить isTreeглобальную переменную, потому что история может иметь или не иметь такой выбор. Знаешь что я имею ввиду? Это больше похоже на двигатель выбора, который будет обслуживать несколько историй.
Валентин Раду
Также у этого есть другая проблема, скажем, если пользователь решает посадить дерево, которое мы установили, isTree=trueоднако позже он делает что-то еще, например, борется со школьным товарищем, который в свою очередь идет и рубит свое дерево, пока дерево еще молодо потому что он получил свою задницу. Теперь у нас есть 2 переменные, которые влияют на существование дерева isTree==true' and didFightBrat == false`. Знаешь что я имею ввиду? И цепочка может продолжаться вечно, на существование дерева может влиять неизвестное количество факторов. Знаешь что я имею ввиду?
Валентин Раду
Затем сохраните эти данные в файле на диске. Вам нужно будет создать две подпрограммы для загрузки и сохранения информации, а затем использовать эти подпрограммы из каждой части кода по мере необходимости.
Рэндольф Ричардсон

Ответы:

18

Вы также можете обобщить очередь в ориентированный ациклический граф (DAG). Вы можете прочитать об этом в Википедии. По сути, каждый узел может иметь один или несколько родительских узлов, от которых он «зависит». Циклы не допускаются, т. Е. Если A зависит от B, B не может зависеть от A (напрямую или через любую косвенную цепочку других узлов).

Каждый узел находится в «активном» или «неактивном» состоянии, и ему разрешается становиться активным, только если все его родители уже активны. Структура графика (какие есть узлы и как они связаны) является частью данных игры, но активное / неактивное состояние является частью данных сохранения игрока.

Таким образом, вы можете моделировать такие вещи, как: когда вы сажаете дерево, вы помечаете задачу «plantedTree» как активную; затем, позже в игре, другая задача «treeGrown» называет «PlantedTree» и некоторый другой узел (часть истории) своими родителями. Затем «treeGrown» становится активным, только когда игрок достигает этой точки в истории, а также «PlantedTree» активен.

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

Натан Рид
источник
Очень хороший ответ, спасибо. Это на самом деле решает и другие проблемы, которые у меня есть, такие как сохранение прогресса пользователя. Это то, что мне нужно.
Валентин Раду
@NathanReed Почему это не может быть цикличным? Быть ациклическим, как правило, не является функцией, а побочным продуктом графического дизайна. Я бы не стал создавать это с таким намерением. Например, представьте, хотите ли вы, чтобы ваше дерево распознавало времена года. Они по своей природе цикличны, и ваша сюжетная линия может быть идентичной в зависимости от выбора, доступного в течение одного сезона.
Это должно быть ациклично, потому что если есть цикл, вы попадаете в бесконечный цикл, пытаясь выяснить, может ли узел в цикле быть активным, потому что вы рекурсивно проверяете всех его предков, включая сам узел. Если бы вы хотели смоделировать что-то вроде времен года, я бы не стал делать это в контексте этого графика.
Натан Рид
@NathanReed Ах, прости, я пропустил рекурсивную часть.
3

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

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

Еще немного в Википедии .

Из их подзаголовка « Когда использовать правила» (акцент мой):

  • Проблема слишком сложна для традиционного кода.
  • Проблема может быть не сложной, но вы не можете найти надежный способ ее создания.
  • Эта проблема выходит за рамки любого очевидного алгоритмического решения.
  • Это сложная проблема для решения. Нет очевидных традиционных решений или проблема не до конца понята.
  • Логика часто меняется
  • Сама логика может быть простой, но правила меняются довольно часто.Во многих организациях выпуски программного обеспечения редки, и правила могут помочь обеспечить «гибкость», которая необходима и ожидается достаточно безопасным способом.
  • Специалисты по предметной области и бизнес-аналитики легко доступны, но не являются техническими.
  • Специалисты по предметной области часто обладают обширными знаниями о бизнес-правилах и процессах. Они, как правило, нетехнические, но могут быть очень логичными. Правила могут позволить им выражать логику в их собственных терминах. Конечно, они все еще должны мыслить критически и быть способными к логическому мышлению. Многие люди на нетехнических должностях не проходят обучение формальной логике, поэтому будьте осторожны и работайте с ними. Кодируя бизнес-знания в правилах, вы часто обнаруживаете пробелы в понимании бизнес-правил и процессов в настоящее время.

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


источник
1

Вы должны учитывать, что событие не основано исключительно на решении пользователя. Как вы заметили, какое-то событие должно добавляться, если при принятии ряда последовательностей решений и затем добавлено что-то еще (например, через два дня).

Я думаю, что вам нужен способ моделирования событий и способ его запуска. В то время как первый более ограничен вашим конкретным случаем, последний может моделироваться с помощью иерархического конечного автомата (HSM), который прямо или косвенно инициирует ваши события.

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

В этом сценарии у вас есть основные события (пользовательские решения, время, изменение погоды и т. Д.), Которые обрабатываются как HSM, так и базовыми обратными вызовами событий. HSM предоставляет модель для «памяти», а обратные вызовы предоставляют способ описать, как эта память должна использоваться для вычисления последствий последовательности решений / внешних событий.

Вы также можете в конечном итоге использовать диктонар (или некоторую другую структуру коллекции) HMS, по одному для каждого «аспекта» статуса, который вы должны вычислить. Примером может быть использование связанного с событием HMS и одного для решений, которые обратные вызовы принимают для запуска событий.

Вся эта инфраструктура служит для того, чтобы имитировать поведение человека, ведущего Мастера подземелий: он обычно запоминает текущую ситуацию (HMS ["external"]) из-за решений игрока и условий окружающей среды; когда что-то добавляется, оно может принимать решения с использованием своей ментальной записи и также записывать некоторый статус внутренней стратегии (HSM ["internal"]), чтобы избежать аналогичной реакции, если, например, добавляется некоторая ситуация.

FXIII
источник