Полная документация Questwright
Все 12 руководств на одной странице · Обновлено 12 июл. 2026 г.
Начало работы
Questwright компилирует квесты, написанные простыми текстовыми файлами, в играбельные сцены диалогов MetaHuman: с озвучкой, липсинком, постановкой, камерами, целями и журналом. Это руководство проведёт вас от чистой установки до говорящего NPC.
Требования
| Требование | Значение |
|---|---|
| Unreal Engine | 5.6 – 5.8 |
| Запекание липсинка | Плагин MetaHuman + DX12 (опционально, см. ниже) |
| Персонажи | MetaHuman для полной лицевой демонстрации; иначе любой персонаж на скелетном меше |
| Озвучка | Опционально: бесплатный локальный TTS-сервер, ElevenLabs (ваш ключ) или ваши собственные WAV-файлы |
| Тип проекта | C++ или только Blueprint |
Прежде чем начать, стоит понимать две вещи:
- MetaHuman не обязателен. Диалоги, квесты, постановка, камеры, журнал и сохранения работают с любым персонажем на скелетном меше. Пайплайн MetaHuman отвечает только за запечённый липсинк и послойную лицевую анимацию; без него сборка корректно деградирует до нейтрального лица.
- Ничего не встроено, ничего не «звонит домой». Синтез голоса и машинный перевод используют ваши собственные сервисы и ключи; с вашими WAV-файлами плагин работает полностью офлайн.
Установка из Fab
- Откройте страницу Questwright на Fab и добавьте плагин в свою библиотеку.
- В Epic Games Launcher найдите его в Library → Fab Library и нажмите Install to Engine.
- Откройте проект, зайдите в Edit → Plugins, найдите Questwright, поставьте галочку Enabled и перезапустите редактор.
Для сборок из исходников или CI вместо этого включите плагин в .uproject:
{
"Plugins": [
{ "Name": "Questwright", "Enabled": true }
]
}
Настройка проекта в один клик
Откройте Tools → Quest Studio. Studio представляет собой единое окно, из которого управляется всё; остальная документация подробно проводит по нему экскурсию.

Нажмите шестерёнку Global Setup на панели библиотеки. Она подключает к Blueprint’ам вашего Player pawn и PlayerController нужные компоненты Questwright: взаимодействие, клиент диалогов, квестовый HUD. Один клик; операция идемпотентна и обратима.

Назначьте персонажей
Откройте вкладку Cast и выберите Blueprint персонажа для каждого NPC-говорящего: перетащите его или воспользуйтесь пикером. Затем отметьте, какая запись соответствует Player. Колонка проверки состояния показывает, чего не хватает каждому персонажу; Fix now сразу добавляет компоненты (этап Build сделал бы это и автоматически).

Для Blueprint’а, только что собранного в MetaHuman, есть необязательный раздел Fresh MetaHuman auto-setup: один раз укажите базовый класс персонажа и Animation Blueprint локомоции, после чего кнопка Auto-setup у каждого говорящего перепривязывает родителя Blueprint’а, назначает Anim Blueprint на меш Body, выравнивает меш по капсуле и добавляет компоненты диалога. Весь ручной ритуал выполняется в один клик.

Сборка и запуск
Откройте вкладку Build и нажмите RUN. Пайплайн компилирует квест:
parse → validate → voice-over → facial animation → asset generation → bind & stage
Живой трекер этапов показывает прогресс; ошибки появляются в предполётном чек-листе над кнопкой RUN, у каждой есть ссылка Fix →, которая переносит на нужную вкладку.

Нажмите Play (PIE) и поговорите с NPC.

Весь сгенерированный контент пишется в ваш проект, в /Game/Questwright/Generated/.
Собственная папка контента плагина остаётся чистой, а поставляемый демо-квест можно удалить,
ничего не сломав.
Что дальше
- Quest Studio: экскурсия по каждой вкладке.
- Написание квестов: формат
.quest.yaml. - Справочник настроек: объяснение каждой настройки проекта.
- Предпочитаете читать офлайн? Полную документацию можно скачать одной страницей для печати или Markdown-бандлом.
Quest Studio
Одно закрепляемое окно (Tools → Quest Studio) управляет всем пайплайном. Эта страница проводит по каждой зоне; руководства по ссылкам разбирают стоящие за ними рабочие процессы подробнее.

Библиотека
Левая колонка перечисляет все *.quest.yaml, найденные в проекте (по умолчанию в
Content/Quests/; файлы появляются автоматически, без шага импорта). У каждого квеста
есть бейдж статуса:
| Бейдж | Значение |
|---|---|
| Generated & valid | Ассеты соответствуют актуальной версии файла |
| Changed | Файл изменился после последней сборки; просто запустите RUN ещё раз |
| Validation errors | В файле ошибки; откройте квест, чтобы увидеть находки с переходом к источнику |
| Not built | Ещё ни разу не компилировался |
+ New Quest создаёт файл из шаблона. New Chain объединяет квесты в сюжетную
цепочку: перетаскивайте квесты в папку цепочки, чтобы задать их порядок. Цепочки хранятся как
чистые данные: перетаскивание записывает available_when / priority прямо в YAML; никакого
скрытого манифеста, который можно повредить, нет.

Вкладка Quest: конструктор
Весь квест редактируется прямо на месте: сцены, реплики, выборы, эмоции, планы, связи шагов, эффекты, условия, награды и барки, а сведения о цепочке и предусловиях показаны на карточке квеста. Всё, что пишет конструктор, остаётся обычным YAML: правка файла в текстовом редакторе всегда эквивалентна, и обе стороны остаются синхронными.

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

Вкладка Cast
Сопоставляет каждый ключ говорящего с Blueprint’ом персонажа и отмечает Player. Этап привязки добавляет компоненты Questwright в ваши Blueprint’ы идемпотентно; Remove Questwright components возвращает персонажа в исходное чистое состояние. Раздел Fresh MetaHuman auto-setup превращает Blueprint, только что собранный в MetaHuman, в готового к диалогам персонажа одним кликом (см. Начало работы).

Вкладка Voice
Выберите TTS-провайдера на проект, назначьте голос каждому говорящему, причём для каждого языка, прослушивайте отдельные реплики или синтезируйте их пакетно, либо подложите свои WAV-файлы. Подробности в Озвучке.

Вкладка Localization
Добавляйте языки, смотрите покрытие перевода по квестам и переводите недостающие реплики машинным переводом со своим ключом Claude или OpenAI (результаты помечаются как «машинный, требует вычитки»). Подробности в Локализации.

Вкладка Build
Предполётный чек-лист (у каждой ошибки есть ссылка Fix → на нужную вкладку), чекбоксы языков, кнопка RUN, живой трекер этапов, Play (PIE) и Show Assets.

Сборки идемпотентны: уже озвученные реплики пропускаются, изменённые синтезируются и запекаются заново, а ассеты удалённых реплик и шагов вычищаются автоматически.
Написание квестов
Квест целиком живёт в одном файле .quest.yaml. Файл является источником истины: Studio компилирует
его в ассеты, а вкладка Quest служит лишь его представлением; правка вручную и правка в конструкторе
всегда эквивалентны. Эта страница проходит по формату; полный справочник по каждому полю
(написанный так, чтобы заодно служить промптом для ИИ) доступен для
скачивания.
Где лежат файлы
Кладите квесты в <Project>/Content/Quests/, и они появятся в библиотеке Studio автоматически.
Называйте их <Name>.quest.yaml, один квест на файл.
Минимальный скелет
quest: Region.MyQuest # unique id/slug (required)
name: "My Quest"
giver: Bran # speaker key of the NPC who gives it
source_culture: en
steps: # required, at least 1
- id: MyQuest_Offer
scene:
speakers: [Bran, Player]
lines:
- { by: Bran, id: greet, text: "Hello.", emotion: neutral }
- { by: Player, id: hi, text: "Hi." }
choices:
- { id: accept, text: "I'll help.", goto: MyQuest_Do }
on_end: { quest: accept }
- id: MyQuest_Do
end: completed
Золотые правила
- Стабильные id в нижнем регистре у каждой реплики, выбора и цели. По id ключуются локализация и сгенерированные ассеты голоса и лица; вставка нового контента никогда не должна перенумеровывать существующие id.
- Используйте только документированные поля, эмоции, планы, виды условий и глаголы эффектов. Валидатор ловит всё, что выходит за рамки контракта, с переходом к источнику по каждой находке.
- Каждое значение GameplayTag должно быть зарегистрировано в проекте (нативно в C++ или в
DefaultGameplayTags.ini). Незарегистрированный тег молча не срабатывает; отсюда причина №1 жалобы «моя цель не засчитывается». См. Цели и прогресс.
Шаги
Шаг представляет собой один узел квеста: сцену диалога, отслеживаемую цель или терминальный маркер.
| Поле | Назначение |
|---|---|
id | Обязателен, стабилен. Автоматически получает пространство имён <ShortQuest>_<id>. |
scene | Сцена диалога (реплики + выборы) |
objective | Отслеживаемая цель: убить / собрать / добраться / поговорить / доставить |
next | Переход к следующему шагу по умолчанию (когда в сцене нет выборов) |
end | Терминальный маркер: completed, declined или failed |
require | Условие входа: например, пустить на шаг сдачи квеста только после выполнения цели |
Сцены: реплики и выборы
scene:
speakers: [Bran, Mira, Player] # every speaker key present in this scene
lines:
- { by: Bran, id: greet, text: "Come here.", emotion: worried, shot: cu }
- { by: Mira, id: warn, text: "Don't trust him.", to: Player }
- { by: Player, id: what, text: "What's wrong?" }
choices:
- { id: help, text: "I'll help.", goto: MyQuest_Accept }
- { id: refuse, text: "Not my problem.", goto: MyQuest_Refuse }
on_end: { quest: accept }
Сцена поддерживает любое число говорящих: перечислите все ключи в speakers и задайте
by у каждой реплики. В рантайме каждый ключ привязывается к актору, уже размещённому на
уровне, либо, если такого нет, к заспавненному по карте говорящих, записанной этапом Cast, и
поставленному рядом с квестодателем. Заспавненные персонажи автоматически убираются по
окончании диалога.
Полезные поля реплики:
to:указывает, кому адресована реплика; управляет кадрированием «через плечо» и поворотом головы в сценах с 3+ говорящими.emotion:принимает одно из значенийneutral,happy,sad,angry,afraid,surprised(плюс алиасы вродеworried→afraid). Управляет аддитивным лицевым настроением и тоном озвучки.shot:выбирает план:auto(по умолчанию; компилятор кадрирует сцену сам),wide,ots,cu,reactionили id пользовательской метки камеры (см. Постановку и камеры).echo_of:означает, что реплика повторяет текст выбора; укажите id выбора, чтобы они делили один ключ локализации.
Выборы могут проверять и выдавать GameplayTag: require: делает выбор доступным, только если
у игрока есть тег(и), grant: выдаёт теги при выборе.
Динамический текст
Текст поддерживает плейсхолдеры {PlayerName} и пользовательские {Token}, подставляемые в
момент отображения безопасным для локализации способом. О том, как задавать значения токенов
из игры, см. Интеграцию с рантаймом.
Авторинг с ИИ-ассистентом
Самый быстрый путь — готовый GPT
Questwright Quest Builder:
он уже знает формат, так что просто опишите завязку («смотритель маяка просит игрока найти его
пропавшего брата») — и получите готовый .quest.yaml.
Предпочитаете другого ассистента? Полный справочник формата намеренно самодостаточен: вставьте
файл справочника в ChatGPT или Claude вместе с
вашей завязкой и попросите .quest.yaml.
В любом случае положите результат в Content/Quests/: валидатор поймает любые отклонения от
контракта ещё до того, как они дойдут до сборки.
Цели и прогресс
Здесь описано всё между «принять квест» и «сдать квест»: как засчитываются цели, как условия ограничивают доступ к контенту, как эффекты меняют состояние и как квесты связываются в цепочки.
Цели
Отслеживаемая цель живёт внутри шага. Шаг использует обобщённый id (Goal, Deliver),
а конкретный вид цели задаётся в objective.type, поэтому вид можно поменять, ничего не
переименовывая:
- id: Goal
objective:
type: Kill
target: MyGame.Enemy.Wolf # GameplayTag; MUST be registered
count: 3
location: MyGame.Area.Farm # optional area gate
desc: "Kill the wolves near the farm"
next: Deliver
| Тип | Смысл | Как засчитывается |
|---|---|---|
Kill | убить N врагов с тегом | ваш код смерти сообщает событие Kill |
Collect | собрать N предметов с тегом | ваш код подбора сообщает событие Collect |
Reach | добраться до места с тегом | событие Reach или CompleteObjective из триггера |
TalkTo | поговорить с кем-то | автоматически: начало разговора засчитывает цель |
Deliver | вернуться к квестодателю | выполняется самим актом сдачи квеста |
Custom | что угодно ещё | игра сообщает Custom + ваш собственный тег |
Два удобных момента, о которых стоит знать:
- Целью
TalkToслужит ключ говорящего, а не тег (вtarget: Maryстоит тот же ключ, что вspeakers:и на вкладке Cast). Любой разговор с этим NPC засчитывает цель только тому игроку, который говорит, и только пока цель активна, поэтому разговор с NPC до принятия квеста корректно не засчитывает ничего. Deliverникогда не блокирует сдачу квеста: он существует, чтобы в трекере была строка «вернуться с докладом», и автоматически отмечается при сдаче квеста.
Засчитывание из вашей игры сводится к одному вызову (Blueprint или C++), серверно-авторитетному
и идемпотентному по EventSourceId:
Comp->ReportObjectiveEvent(Instigator, EQuestwrightObjectiveEventType::Kill,
MatchTags, LocationTags, EventSourceId);
Сопоставление тегов иерархическое: цель с target: Enemy.Wolf засчитывается событием с
тегом Enemy.Wolf.Alpha, так что перечислять каждый подтип не нужно.

GameplayTags: одно критическое правило
Каждое значение target, location, require/grant у вариантов выбора, tag в условиях и
grant_tag в эффектах представляет собой GameplayTag, который уже должен быть зарегистрирован в вашем
проекте, иначе он разрешается в невалидный тег и молча игнорируется. Это причина №1 жалобы
«моя цель не засчитывается». Регистрировать теги можно двумя способами:
- Нативно в C++ (предпочтительно для тегов, уходящих в релиз):
UE_DEFINE_GAMEPLAY_TAG_COMMENT(...). - Project Settings → Project → GameplayTags (пишет в
Config/DefaultGameplayTags.ini).
Используйте иерархию через точки (MyGame.Enemy.Wolf, MyGame.Area.Farm), и иерархическое
сопоставление заработает само собой. Единственное исключение: в цели TalkTo указывается ключ
говорящего, а не тег.
Условия
Условия ограничивают доступность квеста (available_when) и вход в шаг (require). Атомарные
виды:
| Форма | Смысл |
|---|---|
{ quest: <QuestId>, state: <state> } | другой квест находится в состоянии (available, active, completed, failed, declined) |
{ objective: <ObjId>, state: <state> } | цель active / complete / failed / inactive |
{ flag: <Name>, equals: <Value> } | квестовая переменная текущего прохождения равна значению |
{ tag: <GameplayTag> } | у игрока есть gameplay-тег |
{ item: <ItemId>, min: <N> } | в инвентаре ≥ N предметов (через ваш провайдер инвентаря) |
{ attribute: <AttrId>, min: <N> } | характеристика ≥ N (через ваш провайдер статов) |
Составные: { all: [...] } (И), { any: [...] } (ИЛИ), { not: <cond> }.
available_when:
all:
- { quest: Region.Intro, state: completed }
- { not: { flag: Betrayed, equals: "true" } }
Эффекты (on_end:)
Применяются при выходе из сцены:
on_end:
quest: accept # accept | complete | decline | fail | turnin
set: { RewardTier: Negotiated } # write flags, read back by flag conditions
grant_tag: Region.MetBran # grant gameplay tag(s) to the player
grant_quest: Region.FollowUp # auto-accept follow-up quest(s)
Сцена сдачи квеста обычно заканчивается на on_end: { quest: turnin } плюс end: completed.
Ветвление по выбору игрока
Чтобы квестодатель отреагировал на сделанный ранее выбор, установите флаг в момент выбора, а
затем опишите два шага сдачи: шаг, закрытый флагом, поставьте первым, а универсальный
оставьте последним. Маршрутизация квестодателя выбирает первый шаг сдачи, чей require проходит:
- id: Haggle
scene: { ... }
on_end: { quest: accept, set: { RewardTier: Negotiated } }
- id: DeliverHaggled # specific branch, goes before the catch-all
require:
all:
- { objective: Goal, state: complete }
- { flag: RewardTier, equals: Negotiated }
scene: { ... }
on_end: { quest: turnin }
end: completed
- id: Deliver # default branch, carries the tracked objective
require: { objective: Goal, state: complete }
objective: { type: Deliver, count: 1, desc: "Report back" }
scene: { ... }
on_end: { quest: turnin }
end: completed
Демо-квест из поставки использует ровно этот приём (торг ведёт к другой сцене расплаты).
Цепочки квестов
Цепочка представляет собой просто данные, никакого отдельного ассета:
- у квеста B
available_when: { quest: A, state: completed }(пререквизит), и/или - у квеста A
on_end: { grant_quest: B }(авто-принятие продолжения).
priority упорядочивает несколько квестов, предлагаемых одним квестодателем (квест с большим
значением предлагается первым).
UI New Chain в Studio записывает эти значения за вас перетаскиванием; ручное написание
эквивалентно. Валидация ловит циклы в цепочках и висячие ссылки на квесты и цели.
Барки
Фоновые барки (barks:) звучат над головой квестодателя по случайному таймеру; барки простоя
(idle_barks:) звучат при взаимодействии, когда квестодателю больше нечего предложить. И те и
другие проходят через тот же пайплайн озвучки и лицевой анимации голосом квестодателя:
barks:
interval: [8, 16]
lines:
- "Wolves howling again..."
- { text: "After dark, nobody sets foot in the yard.", weight: 2 }
idle_barks:
lines:
- "Rest easy. The farm's safe, thanks to you."
Награды
rewards:
base:
currency: 20
xp: 50
items:
- { def: SmokedMeat, count: 1 }
currency, xp и items записываются при сдаче квеста; сами блага ваша игра выдаёт через шов
провайдера инвентаря (см. Интеграция в рантайме).
Постановка и камеры
Без какой-либо постановки Questwright просто ставит каждого говорящего лицом к собеседнику и кадрирует сцену автоматически; многим квестам большего и не нужно. Постановка нужна для мизансцен с несколькими NPC, спавна статистов, перемещений по ходу диалога и камер, расставленных вручную.
Схема постановки
Правая зона Studio представляет собой схему сцены с видом сверху вокруг её якоря (размещённого маркера диалога или трансформа самого квестодателя, если маркер не поставлен). Перетаскивайте метки, чтобы расставить персонажей, поворачивайте их, добавляйте путевые точки и размещайте камеры. Поставьте курсор на реплику диалога, и схема подсветит, кто где стоит в этот момент: занятость меток отслеживается пореплично по всей сцене.

Всё, что редактируется на схеме, записывается в блок staging: файла квеста; как и во всём
остальном Studio, ручное написание полностью равнозначно:
staging:
anchor: DialogueMarker_Barn # a placed BP_QW_DialogueMarker; omit → the giver's transform
settle_timeout: 4.0 # seconds a walk may take before falling back to teleport
marks:
- { id: giver, actor: Bran, pos: [-100, -30], face: hero, keep: true }
- { id: hero, actor: Player, pos: [-100, 60], face: giver }
- { id: m1, actor: Mira, pos: [ 140, 30], face: anchor, appear: arrive }
- { id: m2, actor: Mira, pos: [ 40, -80], face: anchor } # waypoint
Ключевые поля метки:
| Поле | Назначение |
|---|---|
id | Стабильный идентификатор: line.stage.to и камеры ссылаются на метки по id |
actor | Ключ говорящего; опустите для чистой путевой точки |
pos | [x, y] в сантиметрах относительно якоря; высота в рантайме прижимается к земле |
face / yaw | Автоповорот к якорю / говорящему / метке, либо угол вручную |
appear | placed (стоит на месте с начала сцены) или arrive (спавнится вне поля зрения и подходит; его первая реплика ждёт прибытия) |
keep | Остаётся ли заспавненный участник после конца диалога (по умолчанию деспавнится) |
Участники, уже находящиеся на уровне (и Player), перемещаются; всех остальных плагин спавнит из состава сцены и автоматически убирает.
Перемещение по ходу диалога
Прикрепите переход stage: к любой реплике; реплика подождёт, пока перемещение завершится
(с откатом на телепорт по истечении settle_timeout):
- { by: Mira, id: step, text: "Let me see the tracks.",
stage: [ { who: Mira, to: m2, approach: walk } ] }
Одна метка вмещает одного участника за раз: валидатор помечает двух человек на одной метке в один момент как ошибку, а схема показывает конфликт.
Автоматические камеры
По умолчанию компилятор кадрирует каждую сцену по грамматике планов: устанавливающий общий план
на первой реплике, крупные планы после нескольких реплик подряд от одного говорящего,
кадрирование через плечо по адресату to: каждой реплики. Переопределения для отдельной
реплики задаются через shot: (wide, ots, cu, reaction), а политика на уровне квеста
настраивается:
camera:
cu_run_threshold: 2 # close-up after N consecutive lines from one speaker (default 3)
establishing: true # establishing wide on the first line (default true)
choice_shot: wide # shot while the player is choosing (default wide)
Собственные метки камер
Для полного режиссёрского контроля размещайте камеры прямо на схеме. У каждой есть живой предпросмотр зоны обзора, так что вы точно видите, что попадает в кадр:

staging:
cameras:
- { id: cam_quick, class: dynamic, pos: [200, 0], height: 200, look_at: anchor }
- { id: cam_window, class: BP_MyCam_Voyeur, pos: [-320, 90], height: 150, look_at: giver, fov: 28 }
- { id: cam_dutch, class: BP_MyCam_Dutch, pos: [140, 200], height: 120, yaw: -135, pitch: -8 }
# on a line: shot: cam_window
# while choices unfold: camera: { choice_shot: cam_high }
Как всё это устроено:
- Метка описывает конкретный экземпляр: стабильный id, позицию относительно якоря и
направление. Реплики ссылаются на id метки через
shot:, никогда на класс или координаты. - Переиспользуемая оптика (объектив / DOF / filmback) живёт в классе камеры, то есть в любом
Blueprint-наследнике
QuestwrightDialogueSceneCamera. Встроенномуclass: dynamicBlueprint не нужен вовсе (оптика по умолчанию; добавьтеfov:для быстрого нестандартного ракурса). look_atнаводится на якорь, говорящего или метку, и разрешается в момент смены плана, поэтому камера держит объект в кадре даже после перемещений по ходу диалога.- Автоматическая грамматика никогда не выбирает собственные метки; они применяются только как
явные переопределения на реплике (или через
choice_shot). - Если класс камеры не загрузился в рантайме, реплика деградирует до запасного общего плана с предупреждением; сцена никогда не ломается.
Заметки о дизайне
Планы сменяются мгновенно (SetViewTargetWithBlend с нулевым блендом), а персонажи двигаются
штатной AI-локомоцией движка: без Sequencer и запечённых синематиков, поэтому сцены
продолжают работать, когда меняются ваш уровень, персонажи или анимации. Все ссылки идут на
стабильные идентификаторы (метки, реплики, квесты), а не на координаты или внутренности классов.
Озвучка
Каждая реплика диалога, реплика-барк и фоновый барк могут нести озвучку. Стадия сборки resolve-audio синтезирует (или импортирует) один аудиоассет на реплику и на язык, а лицевая стадия затем запекает из этого аудио липсинк (см. Лицевую анимацию).
Провайдеры
Три взаимозаменяемых провайдера, выбираются во вкладках Voice/Build:
| Провайдер | Что это | Стоимость |
|---|---|---|
| Provided | Вы кладёте WAV-файлы, названные по id реплик, в VO_Source/<culture>/ | — |
| OmniVoice | Локальный HTTP-сервер TTS; ставится и запускается одним кликом Setup & Launch на вкладке Voice | бесплатно, локально |
| ElevenLabs | Облачный TTS с вашим API-ключом | ваш аккаунт |
В комплекте ничего не поставляется и ничто никуда не «звонит»: OmniVoice работает на вашей машине, ElevenLabs использует ваш ключ, а с готовыми WAV-файлами (Provided) плагин работает полностью офлайн.
Назначение голосов
Голоса назначаются на говорящего и на язык во вкладке Voice: у одного персонажа может быть английский голос и русский с другим кастингом. Можно прослушать отдельную реплику, перегенерировать одну реплику или пакетно синтезировать всё недостающее.

Синтез идемпотентен: уже озвученные реплики при пересборке пропускаются, изменённые
синтезируются заново, а аудио удалённых реплик вычищается. Сгенерированные звуковые ассеты
называются по стабильным id реплик (<Step>_<line>_<Speaker>); именно поэтому id никогда
нельзя перенумеровывать.
Настройка OmniVoice
OmniVoice — локальный TTS-сервер с открытым исходным кодом: github.com/debpalash/OmniVoice-Studio. Склонируйте или скачайте этот репозиторий в любое место на машине — дальше Studio всё сделает сама.
Укажите в Repo path путь к скачанному репозиторию и нажмите Setup & Launch (это нужно,
только если вы не запустили сервер сами заранее — для уже работающего сервера достаточно
указать его URL). Кнопка устанавливает зависимости и запускает сервер, обходя подводные камни
консольного окружения, которые ломают запуск вручную; при первом запуске дополнительно
скачиваются ML-модели, так что он может идти несколько минут без вывода. Дождитесь, пока
строка статуса станет зелёной. Если сервер не стартует, посмотрите _qw_omnivoice.log рядом с
файлами сервера.

Когда сервер поднят, нажмите Fetch voices — все доступные на сервере голоса подтянутся в выпадающие списки по говорящим.

Нужно больше разнообразия? Откройте список Add preset с подобранными голосовыми пресетами сервера и выберите нужный — после клика он автоматически появится как опция в выпадающих списках голосов ваших говорящих.

Где живёт конфигурация
Провайдер/модель на культуру и карта голосов по говорящим относятся к настройкам проекта: вкладка
Voice редактирует Project Settings → Plugins → Questwright Studio (BuildCultures,
VoiceByCulture), которые хранятся в DefaultGame.ini и потому общие для всей команды. См.
Справочник настроек.
API-ключи никогда не хранятся в файлах проекта. Ключи живут в пользовательском ini
(игнорируется git) с запасным вариантом через переменную окружения (QW_ELEVENLABS_KEY), что
безопасно для команд и CI.
Ваши собственные записи
Чтобы использовать записанную озвучку, выберите провайдер Provided и положите WAV-файлы,
названные по id реплик, в VO_Source/<culture>/. Смешивать можно: на время производства
синтезируйте заглушки через OmniVoice, а позже подмените их студийными записями: id не
меняются, а значит не меняется и всё остальное.
Лицевая анимация
Questwright не нуждается в захвате мимики: лицевая анимация запекается из аудио озвучки. Если у реплики есть озвучка, у неё будет липсинк, и неважно, пришло аудио из TTS или из ваших собственных записей.
Запекание (на этапе сборки)
Лицевая стадия сборки прогоняет каждую озвученную реплику через MetaHuman Animator и запекает
липсинк-UAnimSequence на реплику (FA_<LineId>), выводя настроение из значения emotion:
реплики. Как и любая стадия, она идемпотентна: неизменённые реплики пропускаются, осиротевшие
лицевые анимации удалённых реплик вычищаются.
Требования этой стадии: включённый плагин MetaHuman и DX12. Оба проверяются предполётной проверкой во вкладке Build; там же переключатель лицевой стадии позволяет пропустить её целиком (быстрые итерационные сборки).
Наложение слоёв в рантайме
В рантайме плагин собирает лицо послойно в коде на любом MetaHuman; Animation Blueprints вашего персонажа никогда не модифицируются:
lip-sync slot → procedural blinking → additive emotion → eye gaze → the character's own RigLogic
Поворот головы к текущему говорящему применяется через рантаймовый post-process Anim Blueprint на теле (у MetaHuman кость головы leader-позируется от тела), поэтому персонажи естественно смотрят на того, кто говорит, в том числе после перемещений постановки по ходу диалога.

Всегда мягкая деградация
Каждая часть лицевого пайплайна деградирует мягко, а не ломает сборку или сцену:
| Ситуация | Результат |
|---|---|
| Нет плагина MetaHuman / нет DX12 | Лицевая стадия пропущена с предупреждением; диалог играет с нейтральным лицом |
| Персонаж не является MetaHuman | Диалог, постановка и камеры работают; запечённого липсинка нет |
| Лицевой ассет не загрузился | Нейтральное лицо для этой реплики, предупреждение в логе |
Классы Anim Blueprint лица и поворота головы заданы мягкими ссылками, которые разрешаются лениво в рантайме, поэтому плагин нормально загружается в проектах вообще без контента MetaHuman.
Локализованные лица
Каждый язык получает собственную озвучку и собственное запекание липсинка: русская реплика двигает рот по-русски. В упакованных сборках ассеты по культурам подменяет система локализации движка (об оговорке для PIE см. Локализацию).
Локализация
Локализация встроена в пайплайн, а не прикручена сбоку: каждый язык получает собственный текст, озвучку и анимацию липсинка. Всё это производит одна и та же сборка.
Как это устроено
- Квесты пишутся на одном исходном языке (
source_cultureквеста, напримерen). - Переводы хранятся в CSV-сайдкарах
Loc/<Quest>.<culture>.csv, ключом служат стабильные id реплик. Непереведённые ключи откатываются к исходному тексту; исходный файл никогда не перезаписывается. - Культуры, которые собирает проект, перечислены в Project Settings → Plugins → Questwright Studio → Build Cultures (вкладка Localization управляет этим списком за вас).
Вкладка Localization
Добавляйте языки, смотрите покрытие перевода по каждому квесту и переводите недостающие реплики машинно со своим ключом Claude или OpenAI. Машинные результаты помечаются как «машинный, требует вычитки», так что ручную проверку легко отслеживать. Ключи хранятся у каждого пользователя локально и никогда не попадают в репозиторий, точно так же, как ключи для озвучки.

Сборка языков
На вкладке Build есть чекбокс для каждой культуры. Каждый собранный язык получает:
- собственные String Tables (диалоги, варианты выбора, текст журнала);
- собственную озвучку с голосовым составом для этого языка с вкладки Voice;
- собственное запекание липсинка из аудио этого языка.
Ассеты каждой культуры лежат в /L10N/<culture>/, а подменяет их штатная система
локализации движка.
Единственный нюанс: PIE и упакованная сборка
Unreal подменяет ассеты по культуре (аудио, лицевую анимацию) только в упакованных/приготовленных сборках; в PIE вы всегда слышите голос исходной культуры. Текстовая локализация работает везде, включая PIE. Это поведение движка, а не настройка, которую можно поправить; проверяйте локализованную озвучку в упакованной сборке.
Динамический текст и локализация
Плейсхолдеры {PlayerName} и собственные {Token} переживают перевод: подстановка происходит
в момент отображения через FText::Format, поэтому переводчики могут свободно переставлять
токены внутри предложения.
Интеграция в рантайме
Questwright нативен для движка и самодостаточен; поверхность интеграции сознательно небольшая. Эта страница описывает все точки соприкосновения плагина с кодом вашей игры (C++ или Blueprint).
Квестовый компонент
Прогресс квеста представляет собой факт о паре (игрок, квест): он живёт в UQuestwrightQuestComponent,
который автоматически добавляется каждому PlayerState (и никогда не хранится на NPC). Получить
его можно откуда угодно:
UQuestwrightQuestComponent* Comp = UQuestwrightQuestComponent::Get(SomeActor);
UQuestwrightQuestComponent* Local = UQuestwrightQuestComponent::GetLocalPlayer(World);
Засчитывание целей
Когда происходит что-то значимое для квеста (убийство, подбор предмета), сделайте один вызов
(BlueprintCallable, серверно-авторитетный, идемпотентный по EventSourceId):
Comp->ReportObjectiveEvent(Instigator, Type, MatchTags, LocationTags, EventSourceId);
Каждая активная цель, чьи type и (иерархически сопоставленные) теги target/location
подходят под событие, увеличивает счётчик. Если передать убийцу в Instigator, засчитается
только этому игроку; если никого не передать, засчитается всем (запасной вариант для общего мира). Для
сюжетных моментов триггер или диалог может вместо этого вызвать:
Comp->CompleteObjective(QuestId, ObjectiveId);
UI из коробки
Добавьте UQuestwrightQuestHUDComponent на ваш PlayerController, и вы получите экранный трекер
целей, журнал квестов (привяжите клавишу к ToggleJournal) и всплывающие уведомления о
состоянии квестов.

Все виджеты написаны на C++ и заменяются вашими подклассами. Либо пропустите их вовсе и постройте собственный UI на делегатах, описанных ниже.
Швы провайдеров
Вместо собственного инвентаря или статов Questwright разговаривает с вашими через интерфейсы с работающими реализациями по умолчанию:
| Шов | Интерфейс | Где реализуется |
|---|---|---|
| Инвентарь (условия по предметам, награды предметами) | IQuestwrightInventoryProvider | Blueprint или C++ |
| Статы / атрибуты (условия по атрибутам) | IQuestwrightStatProvider | Blueprint или C++ |
| Бэкенд сохранений | IQuestwrightSaveProvider | C++ |
| Синтез голоса | IQuestwrightVoiceProvider | C++ (редактор) |
| Приёмник лицевой анимации, smart objects | интерфейсы провайдеров | C++ |
Состояние квестов как GameplayTags
Вызовите SetTagTargetActor(Pawn), и компонент спроецирует теги Questwright.Quest.State.*
на AbilitySystemComponent: состояние квестов становится доступным в требованиях активации
способностей и в любой вашей системе условий на тегах.
Делегаты
Для собственного UI или игровых реакций:
OnQuestStateChanged: принят / завершён / отклонён / провален;OnObjectivesChanged: сдвинулся счётчик или завершилась цель;OnJournalDirty: изменилось что-либо из отображаемого в журнале;OnProgressLoaded: применён сохранённый прогресс (см. Сохранения и мультиплеер).
Токены динамического текста
Плейсхолдеры {PlayerName} и собственные {Token} в диалогах и тексте журнала подставляются
в момент отображения подсистемой GameInstance:
UQuestwrightTextVarsSubsystem* Vars = GameInstance->GetSubsystem<UQuestwrightTextVarsSubsystem>();
Vars->SetPlayerName(TEXT("Ellie"));
Vars->SetTextVar(TEXT("Faction"), TEXT("Rangers")); // used as {Faction}
Оба сеттера помечены как BlueprintCallable. Подстановка использует FText::Format, поэтому она безопасна
для локализации.
Мост «диалог → квест»
Диалоговые сцены меняют состояние квеста через свои эффекты on_end (quest: accept / turnin / decline / fail, флаги set:, grant_tag, grant_quest), поэтому склеивающий код между
разговорами и квестами писать не нужно. См.
Цели и прогресс.
Сохранения и мультиплеер
Прогресс квестов сохраняется из коробки: в одиночной игре вы не пишете ни строчки кода, а та же модель масштабируется до выделенных серверов через один интерфейс.
Автосейв / автозагрузка
Квестовый компонент каждого игрока загружает сохранённый прогресс при старте и сохраняет после изменений (принятие квеста, засчитывание цели, переход на следующий шаг, флаги, завершение). Сохранения объединяются: серия изменений в одном кадре стоит одну запись. Финальное сохранение выполняется при выходе, смене карты и логауте, а автозагрузка автоматически восстанавливает прогресс при переходах между уровнями.
Политика настраивается в Project Settings → Plugins → Questwright Quests (подробности в справочнике настроек):

Автоматическое сохранение включается для компонентов, принадлежащих PlayerState (штатная
схема развёртывания); компоненты на обычных акторах остаются полностью в ручном режиме
SaveProgress() / LoadProgress().
Что сохраняется
Только записи прогресса каждого игрока: состояние, текущий шаг, счётчики целей, флаги, временные метки. Контент квеста живёт в скомпилированных ассетах, а активная сцена диалога относится к презентации и никогда не сохраняется. Сохранённый прогресс переживает обновления контента: запись, чей сохранённый шаг больше не существует, откатывается к первому шагу квеста (с предупреждением в логе), а записи квестов, неизвестных текущей сборке, остаются нетронутыми.
Три уровня
| Развёртывание | Что нужно сделать |
|---|---|
| Одиночная игра | Ничего. Один слот USaveGame (Questwright_Quests). |
| Кооператив на listen-сервере | Ничего. Каждый игрок получает отдельный слот по его онлайн-идентификатору (Questwright_Quests_<id>). |
| Выделенный сервер / свой бэкенд | Реализуйте один C++-интерфейс (ниже). |
Бэкенд подключается через интерфейс IQuestwrightSaveProvider:
class UMyDbSaveProvider : public UObject, public IQuestwrightSaveProvider
{
// PlayerKey is the player's UniqueNetId string; use it as your database key.
virtual bool SaveQuestRecords(const FString& PlayerKey,
const TArray<FQuestwrightQuestRecord>& Records) override;
virtual bool LoadQuestRecords(const FString& PlayerKey,
TArray<FQuestwrightQuestRecord>& Out) override;
// Optional non-blocking variant for REST/database fetches:
// virtual void LoadQuestRecordsAsync(...) override;
};
Зарегистрируйте его как SaveProviderClass на странице настроек или внедрите экземпляр вызовом
SetSaveProvider() при логине. Всё выполняется на стороне сервера; клиенты получают загруженный
прогресс через обычную репликацию.
Ручное управление
Для игры с точками сохранения: установите AutoSavePolicy в Manual и вызывайте
SaveProgress() / LoadProgress() сами. OnProgressLoaded срабатывает после применения
сохранённого прогресса.
Модель мультиплеера
Questwright работает в standalone, на listen-сервере и на выделенном сервере со строгим серверно-авторитетным разделением:
- Прогресс индивидуален для каждого игрока: он хранится в компоненте на PlayerState и реплицируется только владельцу, поэтому игроки не видят внутренности чужого журнала.
- Все изменения серверно-авторитетны. Клиентские вызовы (принятие квеста из UI, сообщение о событии) автоматически идут через Server RPC, так что API выглядит одинаково с обеих сторон.
- Засчитывание убийства достаётся игроку-инициатору, если ваш код смерти передаёт убийцу; иначе засчитывается всем игрокам (фолбэк для общего мира).
- Презентация никогда не выполняется на сервере. Камеры, лица, UI диалога и взгляд работают только на клиенте-владельце; выделенный сервер не выполняет ничего из этого.
- Компоненты создаются автоматически при логине: присоединившиеся позже игроки получают свои без дополнительных действий.
Справочник настроек
Questwright регистрирует три страницы в Project Settings → Plugins. Все они действуют на
уровне проекта (DefaultGame.ini) и разделяются с командой через систему контроля версий.
Единственное намеренное исключение: API-ключи здесь никогда не хранятся (они лежат в
индивидуальном, игнорируемом git ini-файле с фолбэком на переменные окружения).
Questwright Quests
Сохранение прогресса квестов. Секция конфига:
[/Script/QuestwrightRuntime.QuestwrightQuestSettings].

| Настройка | По умолчанию | Что делает |
|---|---|---|
Auto Load Progress (bAutoLoadProgress) | Вкл. | Загружает сохранённый прогресс при старте квестового компонента игрока (на стороне сервера). Также восстанавливает прогресс при смене карты, когда PlayerState пересоздаётся. |
Auto Save Policy (AutoSavePolicy) | On any change | Когда прогресс автосохраняется. Manual: никогда (вы вызываете SaveProgress()). On quest state change: только переходы жизненного цикла (принятие / завершение / отказ / провал). On any change: также переходы шагов, засчитывание целей и флаги. Сохранения объединяются: серия изменений в одном кадре стоит одну запись. |
Save On End Play (bSaveOnEndPlay) | Вкл. | Страховочный сброс на диск при завершении работы компонента (выход / смена карты / логаут). Отключите вместе с Manual для полностью ручного сохранения. |
Save Provider Class (SaveProviderClass) | None | Бэкенд хранилища (C++-класс, реализующий IQuestwrightSaveProvider). None = встроенный провайдер на слотах USaveGame. См. Сохранения и мультиплеер. |
Автоматическое сохранение включается только для квестовых компонентов, принадлежащих PlayerState (штатная схема развёртывания). Компоненты на обычных акторах остаются полностью в ручном режиме сохранения/загрузки.
Questwright Dialogue
Темп диалогов в рантайме. Секция конфига:
[/Script/QuestwrightRuntime.QuestwrightDialogueSettings].

| Настройка | По умолчанию | Что делает |
|---|---|---|
Advance Mode (AdvanceMode) | Manual | Как продвигаются реплики. Manual: после каждой реплики ждём клика, темп задаёт читатель. Auto: реплики проигрываются и продвигаются сами после озвучки; ввода ждут только выборы (кинематографичный темп). |
Auto Advance Buffer Seconds (AutoAdvanceBufferSeconds) | 0.4 | Режим Auto: дополнительная пауза в секундах после озвучки реплики перед переходом дальше, небольшой вдох между репликами. |
Auto Advance No Voice Seconds (AutoAdvanceNoVoiceSeconds) | 2.5 | Режим Auto: сколько остаётся на экране реплика без озвучки, чтобы текст успели прочитать. |
Оба значения режима Auto доступны для редактирования только когда Advance Mode установлен в Auto.
Questwright Studio
Культуры локализации и конфигурация озвучки для каждой культуры. Обычно они редактируются через
вкладки Localization и Voice, а не вручную. Секция конфига:
[/Script/QuestwrightStudio.QuestwrightStudioSettings].

| Настройка | Что делает |
|---|---|
Build Cultures (BuildCultures) | Набор культур, которые собирает проект (например en, ru). Первая запись служит фолбэком исходной культуры, когда квест не объявляет собственную source_culture. Пусто = авторинг в одной культуре. |
Voice By Culture (VoiceByCulture) | Конфигурация озвучки по культурам; ключом служит код культуры. Каждая запись содержит TTS-провайдера (например OmniVoice, ElevenLabs), модель провайдера, подсказку формата вывода (например wav_24000) и назначения голосов говорящим (ключ говорящего → id голоса у провайдера). |
Чего нет в Project Settings
| Значение | Где живёт |
|---|---|
| API-ключи ElevenLabs / Claude / OpenAI | Индивидуальный ini (игнорируется git) + фолбэк на переменные окружения, например QW_ELEVENLABS_KEY |
| Контент квестов, цепочки, приоритеты | Сами файлы .quest.yaml; скрытого манифеста нет |
| Сгенерированные ассеты | Папка /Game/Questwright/Generated/ вашего проекта |
Решение проблем и FAQ
Решение проблем
| Симптом | Причина / решение |
|---|---|
| Цель никак не засчитывается | GameplayTag target/location не зарегистрирован в проекте; см. Цели и прогрессия. Проблема номер один. |
| Диалог играет, но липсинка нет | Не включён плагин MetaHuman или нет DX12, поэтому лицевая стадия намеренно деградирует до нейтрального лица. Устраните требование и перезапустите сборку. |
| Кастомная камера не переключается | shot: реплики должен ссылаться на объявленный id из staging.cameras. Если класс камеры не загружается, сцена откатывается к общему плану с предупреждением. |
| OmniVoice не запускается | Используйте кнопку Setup & Launch на вкладке Voice (она обходит подводные камни окружения консоли/venv); проверьте _qw_omnivoice.log рядом с сервером. |
| Кнопка RUN неактивна | В предполётном чек-листе есть красные пункты; у каждого есть ярлык Fix → к нужной вкладке. |
| Голоса/лица не на том языке в PIE | Это ожидаемо: подмена ассетов по культурам работает только в cooked-сборке; локализация текста работает везде. См. Локализация. |
| Квест изменён, а ассеты устарели | Библиотека помечает его как «changed»; просто запустите RUN ещё раз. Генерация идемпотентна и подчищает осиротевшие ассеты озвучки/лиц удалённых реплик. |
| Два участника оказываются в одной точке | Две метки (или перемещение посреди диалога) ставят двух персонажей на одну метку в один момент; конфликтующую реплику подсвечивают и валидатор, и схема постановки. |
FAQ
Нужны ли MetaHuman?
Нет. Диалоги, квесты, постановка, камеры, журнал и сохранения работают с любым персонажем на скелетном меше. Пайплайн MetaHuman отвечает только за запечённый липсинк и слоёную лицевую анимацию; без него сборка изящно деградирует до нейтрального лица.
Нужно ли писать на C++?
Нет. Настройка выполняется в один клик, цели засчитываются одной BlueprintCallable-нодой, а точки
подключения инвентаря/статов реализуемы в Blueprint. C++ требуется только для кастомного
бэкенда сохранений (IQuestwrightSaveProvider).
Работает ли в мультиплеере / кооперативе?
Да: standalone, listen-сервер и выделенный сервер. Прогресс индивидуален для каждого игрока, репликация идёт только владельцу, все изменения серверно-авторитетны, а сохранение в кооперативе работает без кода. См. Сохранения и мультиплеер.
Можно ли генерировать квесты процедурно в рантайме?
В рантайме нет: квест остаётся авторским контентом, который компилируется в ассеты Studio (или
commandlet-компилятором в CI). Зато можно автоматизировать авторинг: .quest.yaml хранится
обычным текстом, поэтому скрипты или ИИ-ассистент могут генерировать файлы квестов, которые вы
затем собираете как обычно.
Можно ли импортировать диалоги из внешних инструментов (articy, Twine, таблиц)?
Встроенного импортёра нет, но целевой формат открыт: это документированный текстовый файл, и справочник авторинга описывает каждое поле. Конвертация из любого структурированного источника сводится к небольшому скрипту, а вставка справочника вместе с вашим исходным текстом в ИИ-ассистент тоже отлично работает.
Можно ли перестилизовать или заменить UI?
Да. Все виджеты (диалог, выборы, журнал, трекер, тосты, барки) написаны на C++ с классами,
наследуемыми в Blueprint, а делегаты OnQuestStateChanged / OnObjectivesChanged /
OnJournalDirty позволяют вместо этого построить полностью свой UI.
Зависит ли плагин от других платных плагинов или онлайн-сервисов?
Нет. Плагин самодостаточен и использует только средства движка. Синтез голоса и машинный перевод опциональны и работают через ваши собственные сервисы/ключи; с вашими WAV-файлами он полностью работает офлайн.
Какие версии движка поддерживаются?
Unreal Engine 5.6 – 5.8. Слой совместимости проверяется на этапе компиляции, поэтому несовпадение версий громко падает при сборке, а не молча в рантайме.
Моя игра стилизованная / не фотореалистичная. Кинематографичная камера обязательна?
Планы задаются для каждой реплики, поэтому вы сами решаете, сколько «кино» получит сцена: от полностью кастомных меток камер до простых разговоров. UI-виджеты при этом можно перестилизовать под жанр.
Всё ещё не получается?
Напишите на support@phoenixtools.dev, указав версию движка, по возможности файл квеста и соответствующие строки Output Log.