How the Scene Management System Works in Undertale-Changer-Template

The Undertale-Changer-Template scene management system relies on a centralized MainControl state machine that defines three logical scene states, while GameUtilityService provides a global API for instant and fade-out transitions with automatic navigation history tracking.

The Undertale-Changer-Template repository implements a production-ready scene management system tailored for Undertale-like narrative games. This architecture separates scene state definitions from transition mechanics, provides automated asset generation for Overworld scenes, and ensures consistent visual and audio handling across all scene switches through a unified service layer.

Scene State Management in MainControl

At the heart of the system lies MainControl.cs, which maintains the current operational mode through the SceneState enum. This enum defines three distinct logical states—Normal, Overworld, and Battle—that dictate which subsystems remain active during runtime.

When the game initializes, MainControl.Start() invokes StartWithSceneState() to configure the environment based on the current state:

private void StartWithSceneState()
{
    if (sceneState != SceneState.Overworld && OverworldPlayerBehaviour)
        Destroy(OverworldPlayerBehaviour.gameObject);

    switch (sceneState)
    {
        case SceneState.Overworld:
            // Instantiate OverworldPlayerBehaviour, bullet pools, global lighting, chase UI
            break;
        case SceneState.Battle:
            InitializationBattle();
            break;
        // Normal state requires no special initialization
    }
}

This method automatically cleans up Overworld-specific objects like OverworldPlayerBehaviour when switching away from Overworld scenes, and initializes battle subsystems when entering Battle state. The MainControl instance persists throughout the application lifecycle, maintaining references to the player controller, cameras, and current scene metadata at Assets/Scripts/UCT/Core/MainControl.cs.

Global Scene Switching API (GameUtilityService)

All runtime scene transitions route through GameUtilityService.cs, which exposes two primary methods for changing scenes located at Assets/Scripts/UCT/Service/GameUtilityService.cs.

Instant Scene Loading

The SwitchScene(string sceneName, bool isAsync = true) method performs immediate scene loads with asynchronous loading enabled by default. This utility sets the canvas frame sprite, records the last visited scene (unless excluded), loads the target scene, updates resolution settings, and resets the isSceneSwitching flag to prevent concurrent transition attempts.

Fade-Out Transitions

For polished visual transitions, FadeOutAndSwitchScene provides configurable fade effects with optional BGM muting and callback support:

public static void FadeOutAndSwitchScene(string scene,
    Color fadeColor,
    Action action = null,
    bool isBgmMuted = false,
    float fadeTime = 0.5f,
    bool isAsync = true)
{
    action += () => SwitchScene(scene, isAsync);
    MainControl.Instance.isSceneSwitching = true;
    // BGM muting logic handles fadeTime > 0, == 0, and < 0 cases
    SettingsStorage.Pause = true;
    // UI fade image and frame color updates execute here
}

This method constructs an action chain that executes user-provided callbacks before invoking SwitchScene, pauses the game during transition, and handles three fade timing scenarios (positive, zero, and negative values) to control transition speed and direction.

GameUtilityService maintains an ExcludedScenes hash set containing Menu, Rename, Story, Start, Battle, and GameOver. The SetLastScene() method records the currently active scene as playerControl.lastScene only when the current scene does not belong to this exclusion list. This mechanism enables "return to previous" functionality throughout the UI navigation system, allowing menus to automatically return players to their prior gameplay context.

Editor-Time Overworld Bootstrapping

The OverworldSceneHandler.cs editor script at Assets/Editor/Handler/OverworldSceneHandler.cs automatically prepares new Overworld scenes when entering Play mode. Its static constructor subscribes to EditorApplication.playModeStateChanged, triggering validation logic upon EnteredPlayMode:

  1. State Validation: Confirms the active MainControl instance is configured for Overworld state.
  2. Language Pack Generation: Executes CreateLanguagePackFiles to generate empty text files in both Resources/LanguagePacks/ and Assets/Resources/TextAssets/LanguagePacks/ directories for the current scene name.
  3. ScriptableObject Creation: Calls EnsureScriptableObjects to instantiate three required data assets under Assets/Resources/Tables/<SceneName>/:
    • FactTable.asset
    • EventTable.asset
    • RuleTable.asset

This automated pipeline ensures every new Overworld scene possesses the necessary narrative infrastructure without manual asset creation.

Practical Scene Transition Patterns

Story Sequence to Game Start

In StorySceneController.cs (located at Assets/Scripts/UCT/Scene/StorySceneController.cs), the system detects player input during a typewriter sequence and initiates a fade transition:

// Inside Update() method when key "Z" is pressed
GameUtilityService.FadeOutAndSwitchScene("Start", Color.black);

This pattern demonstrates the typical flow: detect input, stop active narrative systems, then invoke the fade service to transition to the target scene.

MenuController.cs at Assets/Scripts/UCT/Scene/MenuController.cs utilizes the same API for UI navigation, leveraging the excluded scenes mechanism to maintain clean navigation history when moving between configuration screens and gameplay.

Code Examples for Common Operations

Instant Scene Switching

Jump directly to the Battle scene without visual transition:

GameUtilityService.SwitchScene("Battle");

Configured Fade Transition

Execute a half-second fade to black while muting background music:

GameUtilityService.FadeOutAndSwitchScene(
    scene: "Overworld",
    fadeColor: Color.black,
    isBgmMuted: true,
    fadeTime: 0.5f,
    isAsync: true,
    action: () => Debug.Log("Fade complete, loading Overworld")
);

Returning to Previous Scene

Utilize the navigation history to return from a menu to the prior gameplay scene:

var previousScene = MainControl.Instance.playerControl.lastScene;
GameUtilityService.FadeOutAndSwitchScene(previousScene, Color.black);

Creating New Overworld Scenes

  1. Create a scene using the Template/Overworld template.
  2. Add the scene to Build Settings.
  3. Enter Play mode to trigger automatic generation of language packs and table assets.

Summary

  • State-Driven Architecture: MainControl.cs defines three scene states (Normal, Overworld, Battle) and orchestrates subsystem initialization through StartWithSceneState().
  • Unified Transition API: GameUtilityService.cs provides SwitchScene() for instant loads and FadeOutAndSwitchScene() for polished transitions with BGM control and callbacks.
  • Smart Navigation History: The system tracks lastScene while excluding menu and utility scenes (Menu, Battle, GameOver, etc.) from the history stack.
  • Automated Asset Generation: OverworldSceneHandler.cs automatically creates language pack files and ScriptableObjects (FactTable, EventTable, RuleTable) when entering Play mode on Overworld scenes.
  • Consistent Implementation: All scene changes route through the service layer, ensuring uniform resolution updates, frame handling, and state management across the project.

Frequently Asked Questions

How do I trigger a scene change with a fade effect in Undertale-Changer-Template?

Call GameUtilityService.FadeOutAndSwitchScene() with the target scene name and desired fade color. This method accepts optional parameters for BGM muting (isBgmMuted), transition duration (fadeTime), and completion callbacks (action) to execute custom logic before the scene loads.

What scenes are excluded from the navigation history?

The GameUtilityService maintains an ExcludedScenes hash set containing Menu, Rename, Story, Start, Battle, and GameOver. When switching away from these scenes, the system does not record them as the lastScene, ensuring that "return to previous" functionality always navigates back to meaningful gameplay contexts rather than utility screens.

How does the system handle different scene types like Overworld and Battle?

MainControl uses the SceneState enum to distinguish between Normal, Overworld, and Battle states. During initialization, StartWithSceneState() destroys Overworld-specific objects (like the player controller and bullet pools) when leaving Overworld scenes, and initializes battle subsystems when entering Battle scenes, ensuring clean state transitions without object leakage.

Do I need to manually create data assets for new Overworld scenes?

No. The OverworldSceneHandler editor script automatically generates required assets when you enter Play mode. It creates language pack text files in both Resources and LanguagePacks folders, and instantiates the three required ScriptableObjects—FactTable, EventTable, and RuleTable—in Assets/Resources/Tables/<SceneName>/.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →