Комментарии 34
Хмм, фактически, вы реализовали паттерны байткод и объект тип(в какой-то мере). Интересно, но для себя, возможно, изобрели велосипед
Дисклеймер: я далёк от геймдева, просто увидел заголовок и заинтересовался
идеей какой-то обобщённой системы для квестов, которую можно втыкать в
различные игры как библиотечку.
Что бросилось в глаза:
TextParser
по факту не парсит язык) А просто определяет тип объекта и
дальше дёргант класс квеста/спавна, чтобы он сам всё распарсил.
Мне кажется, это неправильно, и квестам/спавнам совершенно не нужно
отвечать за парсинг своей текстовой репрезентации. Не по SRP это, незачем
им про неё знать вообще.А для чего вообще собственный язык и парсер, если можно взять условный
JSON/YAML/XML и готовую библиотеку, которая его распарсит?
В языке из статьи например есть баг, что название квеста не может
содержать слово "description" (парсер, увидев его, подумает, что название
закончилось и надо переходить к описанию).
В JSON примеры из статьи выглядели бы так:{ "quests": [ { "name": "QuestName", "description": "QuestDesc", "type": "delivery", "from": 0, "to": 1, "dialogs": [1, -1] }, { "name": "QuestName", "description": "QuestDesc", "type": "chat", "id": 0, "to": 1, "dialogs": [1, -1] } ], "spawns": [ { "type": "CutSceneTrigger", "pos": [12.57, 1, 16.22], "scene": 0 } ] }
Интересно, почему решения именно такие.
Мне почему-то не пришло в голову, что можно использовать один с готовых языков. Идея с json мне очень нравиться. В проект точно перенесу эту идею. Спасибо
UPD: невнимательно прокопипастил ChatQuest, должно быть так:
{
"name": "QuestName",
"description": "QuestDesc",
"type": "chat",
"id": 0,
"autoStart": false,
"dialog": 2
}
Фактически вы изобрели систему, которая была в редакторе первого NWN. Спойлер — ей почти никто не пользовался :). Наверное потому, что в квестах лучше все же брать качеством, а не количеством.
Я попытался сделать систему, где пришлось бы использовать минимум жёстко заскриптованных моментов, но при этом оставалось достаточное пространство для творчество.
Не очень приятно, когда ты несколько часов скриптовал какой-то квест, а потом оказывается, что его надо полностью переделать. С этой системой подобные ситуации легче пережить (я надеюсь)
Не вижу, как тут решается проблема с переписыванием. Что код при изменении квеста надо было переписывать, что этот файлик со скриптами надо будет.
Но в целом согласен, что такой отдельный декларативный файлик удобнее, чем хардкод. Во-первых, быстрое редактирование без перекомпиляции проекта. Во-вторых, в теории им теперь действительно могут заниматься геймдизайнеры/модеры, не являющиеся программистами. В теории даже в блокноте. Знать им потребуется только синтаксис JSON (или другой формат, который вы выберете) и набор сущностей/полей, которые в нём можно объявлять. Его лучше задокументировать. В идеале - составить JSON Schema, тогда и валидация будет автоматизирована, и ИДЕшка при редактировании будет сама все поля подсказывать.
геймдизайнер не сможет по своей прихоти создать новый вид квестов самостоятельно
Эту проблему наверное можно решить, если предоставить скриптам более низкоуровневое API. Не готовые захардкоженные типы квестов, а набор всех доступных действий в игре: "разговор", "убийство" и т.п. Тогда квесты становятся просто контейнерами действий, плюс метаданные с name и description. Условный DeliveryQuest из статьи становится просто Quest, содержащим два Разговора:
{
"name": "QuestName",
"description": "QuestDesc",
"actions": [
{
"type": "Conversation",
"npc": 0,
"dialog": 1
},
{
"type": "Conversation",
"npc": 1,
"dialog": -1
}
]
}
И геймдизайнер может комбинировать действия как угодно в нужные ему квесты.
Этот мой пример с массивом действий всё ещё не особо гибкий: он определяет строгую последовательность действий, без альтернативных или опциональных шагов. Также он никак не описывает зависимости между разными квестами. Чтобы сделать всё правильно, есть смысл присмотреться к этому комментарию.
В итоге придем к скриптовому языку и необходимостью в программисте, чтобы на нем писать. Собственно, пример NWN и показывает, что что-либо годное там делалось только скриптованием, а не мастером квестов. Без скриптов геймдизайнер всегда будет загнан в рамки и простые инструменты быстро станут слишком простыми для его задач.
Единственный, как мне кажется, вариант — это мастер-генератор скрипта. Кстати, в NWN они тоже были и вот ими как раз и пользовались самые начинающие, пока не упирались в ограничения и не начали сами все писать с нуля (что, когда рука набита, быстрее).
Поздравляю с первой публикацией и с тем, что у вас получилось создать некую формальную вселенную. Мне часто приходится делать похожие вещи (формальные вселенные), но не как программисту, а как математику. В математике язык - это прежде всего круг обсуждаемых понятий и только потом - некая грамматика, способная эти понятия обозначать. В качестве совета порекомендую вам разделить описание вашего мира и его программную реализацию. Попробуйте в следующий раз сначала описать, какие объекты есть в вашем мире, какие между ними возможны отношения, а уже после изложите, каким образом вы моделируете эти объекты и отношения при помощи программных средств - возможно такой стиль повествование будет прозрачнее передавать его смысл.
Желаю успехов.
Не очень удачная идея придумывать отдельный скриптовый язык, замучаетесь поддерживать и обучать дизайнеров. Если прям нужен отдельный язык - то удобнее использовать F# для его написания или писать на c# script.
Но для квестов это излищне. Мы писали обычные иенумераторы для сценариев, чтобы была асинзронщина, а логику делали командами(and, or, not) и синхронизировали это с игровыми ивентами. Затем делался удобный нодовый редактор для геймдизов, который генерил уже чистый и читаемый с#, и наоборот.
Ссылки на гитхаб нет? Я бы поизучал вашу систему, уж довольно интересно звучит
Код не могу дать, NDA . Прототип ранний могу:
https://github.com/svLimones/SOLIDex/blob/master/ScenarioService.cs - сервис
https://github.com/svLimones/SOLIDex/blob/master/OrScenarioStep.cs - команда логики
https://github.com/svLimones/SOLIDex/blob/master/LibraryOpenDialogScenario.cs - пример сценария
Для этого нужна SOLID-архитектура и что-то похожее на RX-ивенты.
Нодовый редактор можно найти готовый, тулза больше всего по времени требует, но ничего сложного и можно ее сделать потом.
В видео лекциях SICP от 1986 года автор говорит примерно следующее, воспроизведу по памяти (простите, что взял на себя смелость префразировать, что то не могу быстро найти где он это говорить, а пересматривать многочасовые лекции нет времени):
"Если вы создаёте новый язык, подумайте о том, является ли ваш язык замкнутым в математическом смысле. Это очень важное свойство языка. А именно, если язык оперирует некоторыми сущностями, то можно ли эти сущности каким либо образом объединять (сумма, произведение, конкатенация, композиция, комбинация и т.п.) ? А если можно, то можно ли результатом такого объединение пользоваться как изначальной сущностью? Удобно, когда в языке можно сделать массив массивов, и очень неудобно когда нельзя, а ведь в некоторых языках нельзя."
Можно ли в вашем языке объединять квесты? Очевидно квесты это такие сущности, которые удобно было бы объединять. Причем различными способами: объединить два квеста в один, где неважен порядок исполнения квестов (комутативное сложение); где важен порядок (некомутативное сложение); объединить 2 квеста в один так, что игрок может для прохождения обойтись выполнением любого их них или выполнить оба (OR); выполнить только лишь один, а второй становится при этом недоступен (XOR). Возможно, что как то ещё. И самое главное, чтобы в результате выполнения таких объединенй получался новый квест, который тоже можно как-то объединять с любым другим квестом.
Ну и конечно нужен некоторый контракт - интерфейс обобщенного квеста, чтобы квестом можно было назвать всё, что удовлетворяет соглашению этого интерфейса, а функции оперирующие с квестами (в том числе и объединяющие несколько квестов в один) должны оперировать лишь с интерфейсом обобщенного квеста.
Хотелось бы увидеть дальнейшее развитие идеи. Причем интересует не столько способ хранения и редатирования (ноды, DSL, json, и т.д.), сколько формализация и возможность статический анализа. Например способность системы доказать, что квест сломать невозможно, или что наоборот его невозможно выполнить.
Когда-то я пытался размышлять на эту тему и вывел несколько соображений.
1) Зависимость между квестами должна задаваться обратно. Т.е. не квест1 после выполнения активирует квест2, а квест2 должен иметь в зависимостях квест1. Таким образом при добавлении новых квестов не нужно будет трогать старые, и не будет проблем если квест зависит от выполения любого из нескольких предыдущих. В принципе весь SOLID имеет смысл распространять на квесты. Проверка квестов при этом должна выполняться в прямом порядка, т.к. возможность завершения квеста не имеет значения если он не может быть даже начат.
2) Основные элементы квестовой системы для большинства РПГ это: Quest, Entity, Resource, и Location. В качестве Entity могут быть персонажи, сундуки, интерактивные объекты и т.д. В качестве Resource выступают валюты, предметы, навыки и т.д. Между Location есть переходы, которые могут быть открыты или закрыты. Entity могут перемещаться между Location, Resource могут перемещаться между Entity. У Entity могут быть разные состояния. Сами Quest можно сделать по разному, от простой пары предусловие-последствие до целого графа, где каждый узел может быть либо условием (включая всякие all/any), либо изменением состояния мира.
3) В зависимости от необходимости квесты могут не завязываться напрямую на другие квесты. Если квест требует доступности локации или предмета, имеет смысл делать предусловия напрямую на них. Однако иногда это может быть нежелательно, например если персонаж в ходе квеста лишь краем пробегает по какой-то локации, и после квеста она сразу недоступна.
4) Изменения состояния мира должны быть как можно более атомарными и естественными. Т.е. вместо "выключить объект персонажа в сцене 1, включить объект персонажа в сцене 2", должно быть "переместить персонажа в сцену 2". Таким образом ситуации когда персонажа и там и там показывают будут исключены.
5) Обобщенная проверка на валидность квестов звучит так: "Зависимости квеста не могут стать навсегда недоступными до его завершения.". Если второй квест ломает первый, то он либо не должен быть способен начат раньше чем будет закончен первый квест, либо после него должен быть выполним третий квест который вернет выполнимость первому.
6) Желательный список проверок: любой обязательный квест должен быть выполним (начинаем и завершаем), уникальные предметы/объекты не дублируются и не пропадают, альтернативные квесты могут быть пропущены не ломая обязательные квесты, обнаружение циклических зависимостей квестов.
Анализ зависимостей и выполнимости квестов звучит как задачка на графы. Видел недавно интересную статью как раз про квесты и графы.
Графы присутствуют, но это лишь часть решения. Существенная особенность в том, что в типичной РПГ игрок может брать/выполнять/фейлить/отменять много квестов сразу, причем в произвольном порядке. На небольших количествах квестов можно использовать полный перебор, но для универсального решения это не годится. Я предполагаю что нужно строить какое-то подобие таймлайна с диапазоном возможных инвариантов на точках или отрезках.
Думаю, это типичный анализ мертвого кода. Сомневаюсь, что у компилятора меньше переменных для определения того, нужно выкидывать ветку или нет
У компилятора есть лимиты глубины анализа. Если доказательство слишком сложное, он просто от него оказывается. При этом есть четкий порядок выполнения, пусть и содержащий ветления и циклы, позволяющий применить к нему логику Хоара. В ситуации когда есть 100 квестов, которые могут быть выполнены в любом порядке, есть 100! возможных перестановок, каждая из которых должна быть верна. Проверить их таким же образом не получится.
А нужно ли проверять каждую перестановку? По сути нас волнуют только перестановки внутри наборов квестов, у которых есть общий стейт: использование одного и того же персонажа/предмета/локации, захардкоженное влияние на доступность другого квеста, и т.п. И то, только тогда, когда у хотя бы одного квеста есть сайд-эффекты и этот стейт мутируется: персонаж погибает, квест блокируется и т.п. Наверное, как-то можно отфильтровывать такие ситуации и сильно уменьшить пространство для перебора. А если даже так всё слишком запутанно, слишком много тестов влияют друг на друга и слишком долго всё просчитывается, то может это уже повод дизайнеру задуматься. Потому что и игроку уследить за такими сложными и неочевидными взаимодействиями кучи квестов будет тяжело, и самому дизайнеру поддерживать, чтобы дальше ничего не сломалось.
А нужно ли проверять каждую перестановку?
Нужно, но невозможно. Поэтому нужно придумать способ проверки, при котором не будет необходимости в полном переборе. Общие объекты в квестах встречаются регулярно - например одинаковые персонажи или локации. Например ветки квестов гильгий в TES.
Потому что и игроку уследить за такими сложными и неочевидными взаимодействиями кучи квестов будет тяжело
Игрок может брать ровно по одному квесту за раз. От этого 100! перестановок не пропадают.
Общие объекты в квестах встречаются регулярно - например одинаковые персонажи или локации.
Согласен. Но большинство этих параллельно доступных квестов обычно никаких особых последствий для персонажа/локации не имеют. Скажем, есть локация Х, есть 20 квестов, которым нужно просто побывать в ней в какой-то момент, и есть 1 важный сюжетный квест, после прохождения закрывающий к ней доступ. В этой модели порядок прохождения 20 "обычных" квестов для нас ни на что не влияет, поэтому значимых для нас вариантов прохождения не 21!, а всего лишь 1'048'576 (сумма сочетаний по k вовремя пройденных квестов из 20, для возможных k от 0 до 20).
Например ветки квестов гильгий в TES.
Верно. Но они обычно слабо связаны с внешним миром. Например, в Обливионе ветки гильдий магов и воров не связаны, благодаря чему возможен мем, что архимаг выкрадывает посох сам у себя. Или в Скайриме тёмное братство, убивающее императора, никак не влияет на ход гражданской войны.
А сложность этой изолированной ветки внутри гильдии - см. первый абзац. Обычно на доступность контента влияет только прогресс по основному сюжету гильдии. А условные 10 одновременно доступных побочных квестов друг на друга не влияют.
Но большинство этих параллельно доступных квестов обычно никаких особых последствий для персонажа/локации не имеют.
Обычно не имеют. Но интересно разработать систему которая смогла бы распознавать потенциальные баги даже если имеют. Зависимости при этом могут быть неявными и опосредованными. Например необходимость передать персонажу предмет получаемый за квест со собором шкур падающих из монстров в локации доступ в которую может быть невозможен потому что игрок по сюжету другого квеста застрял на другом острове.
Но они обычно слабо связаны с внешним миром.
Это как раз примеры того, что могло бы быть обнаружено и исправлено. Разные ветки квестов скорее всего делали разные группы людей, а одной только внимательностью синхронизировать квесты в проекте такого размера нереально.
Если у вас 100 независимых квестов (т.е. в них участвуют разные персонажи, на разных локациях, нужны разные предметы и зависят от разных параметров игрока), то хочешь-не хочешь, а все равно проверять 100!
, никуда от этого не деться. Но обычно, как и в программе, зависимости есть
все равно проверять
100!
, никуда от этого не деться
В том то и дело, что есть паттерны, которые при распознании позволяют сокращать число вариантов. Например нахождение неявных зависимостей между квестами, перекрытие инвариантов (один более строгая версия другого), схлопывание инвариантов (альтернативные ветви дающие аналогичный результат) и т.д. Нужно только выявить все эти паттерные и свести в единую систему.
Причем речь не только о алгоритме анализатора, но и об дизайне самой квестовой системы. Она не обязана быть Тьюринг-полной. В самом строгом случае можно даже принудить все цепочки квестов возвращать мир к изначальному состоянию, а видимые для игрока побочки подпереть хитрым костылем. Но намного интересней найти решение для как можно более свободной системы.
Как человек со стажем в играх в качестве пользователя могу сказать, что наличие NPC для старта и завершения квеста не обязательно
Привет) Я не программист, но подскажу удобную систему для работы с квестами и диалогами которая по моему опыту работает в юнити из коробки очень хорошо, и хорошо масштабируема для работы команд как маленьких так и больших. В компаниях в которых я работал мы использовали его как в команде из 3х -5 человек так и до 200. Также есть опыт когда мы в 3 с нарративным дизайнером без участия программистов за 2 дня собирали нарративный детектив на 2000 слов.
Вот тут я немного писал про её использование на проектах где я работал, сейчас для плейрикса это стандарт, я же её внедрял как раз впервые с несколькими программистами, Основная сложность была в том что мы внедряли артиси в собственный движок, с юнити таких проблем почти нету, хотя там и есть не очень хорошие моменты)
Из интересного, мы генерили из артиси визуал поведения персонажей, во время диалогов, а также голосовую озвучку через апи replica и по ней снимали липсинк на персонажей ( и это все на мобилках работало)
https://m.youtube.com/playlist?list=PLFUFC6m_Hp2vpJ73BtwlbM1PiNUm-79dJ
Вот тут парень неплохо про систему рассказывает хотя и с некоторыми неточностями и ошибками, но в целом понятно что и как работает и что можно в этой системе сделать.
Надеюсь будет полезно)
Что у системы с валидацией значений и всякими constraint в частности? К примеру в Арканум квесты это взаимодействие нескольких разных систем которое не поддерживает проверки: есть ли такой предмет или npc в игре? Достижимы ли требуемые характеристики? Не ведет ли квест в никуда?
Язык описания квестов или как сделать квестовую систему на Unity