Розробка квестових ланцюжків та нарративного дизайну ігор
Квестова система ламається не у написанні сценарію — вона ламається у менеджменті станів. Квест з трьома цілями, двома можливими фіналами та залежністю від трьох інших квестів — це мінімум 12 прапорів станів, які потрібно коректно ініціалізувати, оновлювати та перевіряти. Якщо ця логіка розкидана по PlayerPrefs, hardcоду та випадковим if-перевіркам у NPC-скриптах — система гарантовано ламається на граничних випадках.
Архітектура квестової системи
Квест — це об'єкт даних з ідентифікатором, списком цілей (QuestObjective[]) та поточним станом (QuestState). Стани: Locked, Available, Active, ObjectivesComplete, Completed, Failed. Переходи лише через QuestManager, ніколи напрямику.
QuestManager — синглтон (або сервіс-локатор) з Dictionary<string, QuestData> за quest ID. Методи: StartQuest(id), CompleteObjective(questId, objectiveId), FailQuest(id). Кожен виклик публікує подію OnQuestStateChanged(QuestData) — на неї підписуються UI, NPC-контролери, аналітика.
QuestData ScriptableObject зберігає статику квеста: назва, опис, список цілей з текстом та типом (KillObjective, CollectObjective, ReachLocationObjective, TalkObjective), список prerequisite quest ID. Runtime-стан квеста живе окремо — у QuestRuntimeData, серіалізуємому у save-файл.
Розділення статики та runtime — ключове. ScriptableObject для квеста не змінюється в play mode; QuestRuntimeData живе лише у пам'яті та у збереженнях. Це виключає випадкову мутацію даних квеста в редакторі при тестуванні.
Ланцюжки та залежності
Квестова ланцюжок — це DAG (спрямований ациклічний граф) квестів, де кожен наступний квест має prerequisites — список квестів, які повинні бути Completed перед розблокуванням. QuestManager перевіряє prerequisites при спробі StartQuest() і при кожній зміні стану будь-якого квеста автоматично оновлює Locked → Available для розблокованих.
Циклічні залежності (квест A вимагає B, квест B вимагає A) — баг, який потрібно ловити в Editor-скрипті при збереженні asset, не в рантаймі. QuestDependencyValidator : AssetPostprocessor обходить граф DFS і логує помилку при знаходженні циклу.
Нарративний дизайн: інтеграція історії у геймплей
Нарративний дизайн — це не «написати хороший текст». Це інтеграція історії в механіки. Найкращі нарративні моменти в іграх працюють тому, що механіка і нарватив говорять про одне й те саме. У Papers, Please механіка перевірки документів — це нарватив про конформізм та моральний вибір. У Celeste складність платформерів — метафора боротьби з тривогою.
Narrative pillars — три-п'ять тезисів, що описують емоційну суть історії. Кожен квест, діалог та механіка перевіряються на відповідність цим тезисам. Якщо квест не працює ні на один pillar — чому він тут?
Момент розкриття інформації — нарративний інструмент, який сильно впливає на дизайн квестів. Гравець дізнається щось важливе в момент дії, а не до неї. «Убий зрадника» — тривіальний квест. «Знайди вбивцю мера» → гравець збирає улики → в фіналі розуміє, що це був його наставник — це нарватив через геймплей.
Типи квестових цілей та їхня реалізація
KillObjective — найпростіший тип: підписатися на подію EnemyDeath(EnemyType), інкрементувати лічильник, перевірити count >= required. Проблема виникає при зміні сцени: якщо лічильник живе в MonoBehaviour на квест-об'єкті, який вивантажується — дані втрачаються. Лічильник повинен жити у QuestRuntimeData, який персистентний.
CollectObjective — завязаний на InventorySystem: підписатися на OnItemAdded(ItemDefinition, quantity), перевірити відповідність потрібному предмету. Нюанс: якщо предмет квестовий і його нельзя викинути, потрібен прапор isQuestItem та перевірка в InventoryContainer.TryRemove().
ReachLocationObjective — trigger-зона (BoxCollider isTrigger) з компонентом QuestTrigger, при вході в яку публікується подія LocationReached(questId, objectiveId). Важливо: OnTriggerEnter не срабатывает якщо об'єкт телепортується в зону — потрібна додаткова перевірка позиції при завантаженні сцени.
Нелінійність та ветвні фіналеи
Ветвний фінал квеста вимагає, щоб обидві гілки були повністю написані та реалізовані — половина студій скорочує кути та робить одну «справжню» концовку та одну заглушку. Гравці це відчувають.
Технічно: фінал визначається набором прапорів, зібраних по ходу квеста (choiceA_taken, evidence_found, npc_alive). У QuestCompleteHandler логіка: if (flagSet.Contains("evidence_found") && flagSet.Contains("npc_alive")) → outcome = "JusticeEnding". Прапори — рядки в HashSet<string> всередину QuestRuntimeData.
Орієнтовні строки
| Масштаб | Склад | Строк |
|---|---|---|
| Один квест | 3–5 цілей, лінійний | 3–5 днів |
| Квестова ланцюжок | 5–10 квестів, залежності, прості ветвлення | 2–4 тижні |
| Основний сюжет | 20–40 квестів, нелінійність, багато фіналів | 2–4 місяці |
| Повна нарративна система | + інструментарій, редактор, локалізація | 4–6 місяців |
Процес
Проектування розпочинається з квестового графа в Miro або Articy:Draft — візуалізація всіх залежностей. Потім QuestData ScriptableObject створюється для кожного квеста з заповненими prerequisites. Код QuestManager пишеться та покривається тестами раніше, ніж створюється перший квест контенту. Це звучить як overhead, але економить тижні правок пізніше.





