Zenject Demo
The demo shows how to integrate WorldGraphEditor with the Zenject DI container: a custom ITransitionManager implementation with responsibilities split into small services around scene transitions.
Service overview
| Service | Responsible for |
|---|---|
TransitionManagerService | Transition management: validation, fade, scene loading, spawn position, graph state |
PlayerLifecycleService | Player spawn/pull based on manager data |
GraphDataService | Visited ports and shortcut progress |
GameCompletionViewService | HUD: scene name, shortcut progress |
ScreenFadeService | Fade animation with cancellation support |
TransitionManagerService
Implements ITransitionManager, IDisposable. Graph is injected as IWorldGraph (not the concrete class).
GoFromAsync(currentPortGuid) — regular transition. Queries IGraphDataService.IsPortVisited, then Graph.CanPassTransition — the graph itself decides whether passage is allowed (visited state, connection direction). Refusal is not an error, but a LogWarning. There is no ignoreShortcuts in the interface — that logic is encapsulated in the service via IGraphDataService; those who need explicit control pass a flag through ITransitionContext.
GoToAsync(currentPortGuid, targetPortGuid) — direct teleport without CanPassTransition, for non-standard transitions (fast travel, etc.).
GoInternalAsync — common pipeline: TransitionStarted → fade in → SetPortVisited(source) → SceneManager.LoadSceneAsync → resolve OutputTransitionComponent and NextSpawnPosition → SetPortVisited(target) → SceneLoaded → fade out → TransitionEnded.
Wrapped in try/catch with two branches: OperationCanceledException — normal cancellation (e.g. Dispose() during a transition), logged as Info; all other Exception — bugs, LogException with full stack trace. Try/catch at the fire-and-forget boundary, because ports may call GoFromAsync without await.
PlayerLifecycleService
Single responsibility — instantiate the player at the correct position after transition completes. Subscribes to TransitionEnded in constructor and immediately calls SpawnPlayer() — this handles the spawn for the starting scene (when no transition has occurred yet).
SpawnPlayer instantiates the prefab at NextSpawnPosition. If OutputTransitionComponent implements IPusher — applies push-force via SetPushForce. Then enables movement via EnableMovement().
PullPlayer fires on TransitionStarted — deactivates the player before the next scene loads.
GraphDataService
Stores the graph's game state in memory (not persisted between sessions — a real game would need a save file here). Two HashSet<string>: _visitedPorts (all visited GUIDs) and _shortcuts (a subset of visited, where the port is a shortcut-destination). Splitting into two sets gives O(1) OpenedShortcuts instead of recalculating from all visited.
SetPortVisited is called by the manager twice per transition — for the input and output port. The public contract (IGraphDataService) — three members: OpenedShortcuts, SetPortVisited, IsPortVisited.
GameCompletionViewService
View service for HUD: listens to TransitionManager.SceneLoaded, updates current scene name and shortcut progress text. Since it is a MonoBehaviour, injection goes through an [Inject] method (not constructor) — Zenject calls it immediately after Awake.
_shortcutsCount (total shortcut edges in the graph) is computed once in the constructor and cached. In OnSceneLoaded the current port GUID is taken from OutputTransitionComponent, and on first launch (before the first transition, when it is still null) — fallback to TransitionComponentUtility.FindAny().
ScreenFadeService
Screen fade animation: ShowAsync(CancellationToken) / HideAsync(CancellationToken), both accept a cancellation token — so that TransitionManagerService.Dispose() can interrupt the animation if a transition is cancelled mid-way.