Система диалогов
Система диалогов — это центральный узел движка для всего модального UI — от простых текстовых окон до полноэкранных меню, таких как Сумка, Покедекс или Состав монстров. Она построена вокруг синглтона DialogManager и использует архитектуру на основе очереди с анимацией пишущей машинки, локализацией и ветвящимися вариантами выбора.
DialogManager
Расположение: Assets/OpenMon/Core/Runtime/UI/Dialogs/DialogManager.cs
Пространство имён: OpenMon.MCE.UI.Dialogs
DialogManager расширяет Singleton<DialogManager> и реализует IInputReceiver. Он предоставляет полностью статический API — все публичные методы вызываются как DialogManager.ShowDialog(...), DialogManager.ShowChoiceMenu(...) и т.д.
Создание через фабрику Zenject
DialogManager НЕ размещается в сцене вручную. Он создаётся во время выполнения через Zenject:
// DialogManagerInstaller.cs
[CreateAssetMenu(menuName = "MCE/Dependency Injection/Dialog Manager Installer",
fileName = "DialogManagerInstaller")]
public class DialogManagerInstaller : ScriptableObjectInstaller
{
public DialogManager Prefab;
public override void InstallBindings() =>
Container.BindFactory<DialogManager, DialogManager.Factory>()
.FromComponentInNewPrefab(Prefab);
}
DialogManager.Factory — это GameObjectFactory<DialogManager>, который создаёт префаб и позволяет Zenject внедрить все зависимости.
Внедрённые зависимости
[Inject] private IInputManager inputManager;
[Inject] private ILocalizer localizer;
[Inject] private Roster playerRoster;
[Inject] private EvolutionManager evolutionManager;
[Inject] private MapSceneLauncher mapSceneLauncher;
Базовые текстовые диалоги
Показ простого диалога
// Забыл-и-запустил: показывает текст и ждёт ввода игрока
DialogManager.ShowDialog("Dialogs/Welcome");
// С говорящим персонажем
DialogManager.ShowDialog("Dialogs/ProfGreeting", character: professorData);
// С текстовыми модификаторам и (интерполяция строк через локализацию)
DialogManager.ShowDialog("Dialogs/XPGain/Single",
localizableModifiers: false,
modifiers: new[] { monsterName, "500" });
Показ и ожидание (Корутина)
Для последовательных потоков диалогов используйте вариант с корутиной:
yield return DialogManager.ShowDialogAndWait("Dialogs/Moves/Learnt",
localizableModifiers: false,
modifiers: new[] { monsterName, moveName });
Это блокирует корутину, пока игрок не закроет диалог.
Полная сигнатура ShowDialog
public static void ShowDialog(
string localizationKey,
CharacterData character = null,
MonsterInstance monster = null,
bool acceptInput = true,
float typewriterSpeed = .01f,
float switchToNextAfterSeconds = -1, // -1 = ждать ввода
bool localizableModifiers = true,
BasicDialogBackground background = BasicDialogBackground.Normal,
HorizontalAlignmentOptions horizontalAlignment = HorizontalAlignmentOptions.Left,
params string[] modifiers
);
Описание параметров
| Параметр | Назначение |
|---|---|
localizationKey | Ключ в системе локализации (например, "Dialogs/Welcome") |
character | Если установлен, показывает имя персонажа в панели говорящего |
monster | Если установлен, показывает имя/никнейм монстра в панели говорящего |
acceptInput | Может ли игрок продвигать/закрывать диалог |
typewriterSpeed | Секунд на символ для анимации пишущей машинки |
switchToNextAfterSeconds | Автопереход через N секунд (-1 = вручную) |
localizableModifiers | Если true, строки модификаторов обрабатываются как ключи локализации; если false, используются как буквальный текст |
background | Normal или Sign (разные визуальные фоны) |
modifiers | Значения, подставляемые в {0}, {1} и т.д. в локализованной строке |
Структура DialogText
Каждый диалог внутренне представлен как readonly структура DialogText:
public readonly struct DialogText
{
public readonly string LocalizationKey;
public readonly string[] Modifiers;
public readonly bool LocalizableModifiers;
public readonly CharacterData Character;
public readonly MonsterInstance Monster;
public readonly float TypewriterSpeed;
public readonly float SwitchToNextAfterSeconds;
public readonly BasicDialogBackground Background;
public readonly HorizontalAlignmentOptions HorizontalAlignment;
}
Метод ToString(ILocalizer) разрешает ключ локализации и подставляет модификаторы:
public string ToString(ILocalizer localizer)
{
string text = localizer[LocalizationKey];
for (int i = 0; i < Modifiers.Length; i++)
{
string modifier = Modifiers[i];
text = text.Replace("{" + i + "}", LocalizableModifiers ? localizer[modifier] : modifier);
}
return text;
}
Очередь диалогов и поток
DialogManager использует внутреннюю очередь для обработки нескольких последовательных диалогов:
ShowDialog("key1") ──► Поставить DialogText в очередь
ShowDialog("key2") ──► Поставить DialogText в очередь
ShowDialog("key3") ──► Поставить DialogText в очередь
Игрок нажимает Выбрать:
├─ Если пишущая машинка всё ещё анимирует → SkipTypewriter()
└─ Если текст полностью показан → NextDialogInternal()
├─ Если есть ещё страницы в текущем тексте → перейти на следующую
├─ Если есть ещё диалоги в очереди → извлечь и показать следующий
└─ Если очередь пуста → освободить ввод, скрыть диалог, установить active = false
Ожидание диалогов
// WaitUntil очередь диалогов пуста
yield return DialogManager.WaitForDialog;
// WaitWhile пишущая машинка всё ещё анимирует
yield return DialogManager.WaitForTypewriter;
Автопереход
Когда switchToNextAfterSeconds > 0, диалог автоматически переходит дальше после завершения пишущей машинки и истечения указанной задержки:
private IEnumerator NextDialogAfterSeconds(float seconds)
{
yield return new WaitWhile(() => Text.IsTypewriting);
yield return new WaitForSeconds(seconds);
NextDialog();
}
Анимация пишущей машинки
Текст отображается с использованием LocalizedTypeWriterTMP, который обёртывает MCETextAnimator — пишущую машинку на основе твинов. Тип DOTween
ниже — это собственный фасад MCE OpenMon.Foundation.Tweening.DOTween (поставляется с движком),
а не ассет Demigiant:
// MCETextAnimator.ShowText()
textComponent.maxVisibleCharacters = 0;
typewriterTween = DOTween.To(
() => textComponent.maxVisibleCharacters,
x => textComponent.maxVisibleCharacters = x,
totalCharacters,
totalCharacters * waitForNormalChars
).SetEase(Ease.Linear)
.OnComplete(() => onTextShowed.Invoke());
Управление скоростью
LocalizedTypeWriterTMP.SetTypewriterSpeed(float) устанавливает три временных уровня:
| Уровень | Переменная | Назначение |
|---|---|---|
| Обычный | waitForNormalChars | Базовая задержка на символ |
| Средний | waitMiddle | Задержка на запятых (по умолчанию 5x) |
| Длинный | waitLong | Задержка на точках (по умолчанию 20x) |
Текстовые эффекты
MCETextAnimator поддерживает опциональные эффекты вершин (отключены по умолчанию):
<wave>— синусоидальное вертикальное колебание на символ.<shake>— случайное смещение на символ каждый кадр.
Включите с помощью enableVertexEffects = true на компоненте.
Пропуск
Нажатие кнопки выбора во время анимации пишущей машинки вызывает SkipTypewriter(), который мгновенно показывает все символы.
Панель говорящего
Когда передаются CharacterData или MonsterInstance, диалог показывает панель с именем говорящего:
if (nextText.Character != null)
{
CharacterName.SetValue(nextText.Character.LocalizableName);
CharacterPanel.Show();
}
else if (nextText.Monster != null)
{
CharacterName.Text.SetText(nextText.Monster.GetNameOrNickName(localizer));
CharacterPanel.Show();
}
else
CharacterPanel.Show(false);
Меню выбора
Диалоги выбора используют ChoiceMenuController — подкласс MenuSelector, поддерживающий 1–11 вариантов с авторазмером:
DialogManager.ShowChoiceMenu(
options: new List<string> { "Common/True", "Common/False" },
callback: choice => { /* 0 = Да, 1 = Нет */ },
onBackCallback: () => { /* Обработать как отмену */ },
showDialog: true,
localizationKey: "Menu/Save/Confirmation"
);
Полная сигнатура ShowChoiceMenu
public static void ShowChoiceMenu(
List<string> options, // Ключи локализации для каждого варианта
Action<int> callback, // Вызывается с индексом выбора
Transform position = null, // Пользовательская позиция (null = по умолчанию)
Action onBackCallback = null, // Если задано, кнопка назад закрывает и вызывает это
CharacterData character = null, // Говорящий
MonsterInstance monster = null, // Говорящий
bool showDialog = false, // Показать текстовый диалог рядом с выбором
string localizationKey = "", // Ключ локализации текста диалога
bool localizableModifiers = true,
params string[] modifiers
);
Как это работает внутри
ChoiceMenuController.SetNumberOfOptions(n)включает первые N элементовChoiceOptionи изменяет размер панели.LocalizedTextMeshProкаждого варианта устанавливается на предоставленный ключ локализации.- Меню выбора появляется ПОСЛЕ завершения пишущей машинки (когда
showDialogравен true). - При выборе срабатывает колбэк, и меню выбора автоматически скрывается.
- При возврате (если разрешено) срабатывает
onBackCallback.
Общий шаблон: Да/Нет с корутиной
int choice = -1;
DialogManager.ShowChoiceMenu(
new List<string> { "Common/True", "Common/False" },
playerChoice => choice = playerChoice,
onBackCallback: () => choice = 1,
showDialog: true,
localizationKey: "Menu/Save/Confirmation"
);
yield return new WaitUntil(() => choice != -1);
if (choice == 0)
{
// Игрок выбрал Да
}
Интеграция локализации
Весь текст диалогов проходит через систему ILocalizer:
LocalizedTextMeshPro.SetValue(key, localizableModifiers, modifiers)разрешает ключ и подставляет{0},{1}и т.д.- Когда
localizableModifiersравен true, строки модификаторов сами разрешаются как ключи локализации. - Когда false, модификаторы вставляются как буквальные строки (полезно для имён игроков, чисел и т.д.).
// Локализуемый модификатор: разрешаются и "Dialogs/Caught", и "Monsters/Pikachu"
DialogManager.ShowDialog("Dialogs/Caught", modifiers: "Monsters/Pikachu");
// Буквальный модификатор: "500" вставляется как есть
DialogManager.ShowDialog("Dialogs/XPGain/Single",
localizableModifiers: false,
modifiers: new[] { monsterName, "500" });