Перейти к основному содержимому

Zenject Demo

Демо показывает, как интегрировать WorldGraphEditor с DI-контейнером Zenject: собственная реализация ITransitionManager, разделение ответственности на маленькие сервисы вокруг перехода между сценами.

Обзор сервисов

СервисОтвечает за
TransitionManagerServiceУправление переходом: валидация, fade, загрузка сцены, spawn-позиция, graph-состояние
PlayerLifecycleServiceСпавн/pull игрока по данным менеджера
GraphDataServiceVisited-порты и shortcut-прогресс
GameCompletionViewServiceHUD: имя сцены, прогресс shortcut'ов
ScreenFadeServiceFade-анимация с поддержкой отмены

TransitionManagerService

Реализует ITransitionManager, IDisposable. Graph инжектится как IWorldGraph (не конкретный класс).

GoFromAsync(currentPortGuid) — обычный переход. Спрашивает IGraphDataService.IsPortVisited, затем Graph.CanPassTransition — граф сам решает, разрешён ли проход (visited-состояние, направление связи). Отказ — не ошибка, а LogWarning. В интерфейсе нет ignoreShortcuts — эта логика инкапсулирована в сервисе через IGraphDataService; кто хочет явный контроль, передаёт флаг через ITransitionContext.

GoToAsync(currentPortGuid, targetPortGuid) — прямой телепорт без CanPassTransition, для нестандартных переходов (fast travel и т.п.).

GoInternalAsync — общий пайплайн: TransitionStarted → fade in → SetPortVisited(source)SceneManager.LoadSceneAsync → резолв OutputTransitionComponent и NextSpawnPositionSetPortVisited(target)SceneLoaded → fade out → TransitionEnded.

Обёрнут в try/catch с двумя ветками: OperationCanceledException — штатная отмена (например, Dispose() во время перехода), логируется как Info; остальные Exception — баги, LogException с полным стектрейсом. Try/catch на границе fire-and-forget, потому что порты могут вызывать GoFromAsync без await.


PlayerLifecycleService

Единственная ответственность — создавать экземпляр игрока в правильном месте после завершения перехода. В конструкторе подписывается на TransitionEnded и сразу вызывает SpawnPlayer() — это спавн для стартовой сцены (когда перехода ещё не было).

SpawnPlayer инстанциирует префаб в NextSpawnPosition. Если OutputTransitionComponent реализует IPusher — применяет push-force через SetPushForce. Затем включает управление через EnableMovement().

PullPlayer срабатывает на TransitionStarted — деактивирует игрока до загрузки следующей сцены.


GraphDataService

Хранит игровое состояние графа в памяти (между сессиями не сохраняется — для реальной игры тут нужен был бы файл сохранения). Два HashSet<string>: _visitedPorts (все посещённые guid'ы) и _shortcuts (подмножество visited, где порт — shortcut-destination). Разделение на два множества даёт OpenedShortcuts за O(1) вместо пересчёта по всем visited.

SetPortVisited вызывается менеджером дважды за переход — для input и output порта. Публичный контракт (IGraphDataService) — три члена: OpenedShortcuts, SetPortVisited, IsPortVisited.


GameCompletionViewService

View-сервис для HUD: слушает TransitionManager.SceneLoaded, обновляет текст текущей сцены и прогресса shortcut'ов. Т.к. это MonoBehaviour, инжект идёт через [Inject]-метод (не конструктор) — Zenject вызывает его сразу после Awake.

_shortcutsCount (общее число shortcut-рёбер в графе) считается один раз в конструкторе и кэшируется. В OnSceneLoaded guid текущего порта берётся из OutputTransitionComponent, а на первом запуске (до первого перехода, когда он ещё null) — fallback на TransitionComponentUtility.FindAny().


ScreenFadeService

Fade-анимация экрана: ShowAsync(CancellationToken) / HideAsync(CancellationToken), оба принимают токен отмены — чтобы TransitionManagerService.Dispose() мог прервать анимацию, если переход отменяется по ходу.