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.
Navigation History and Excluded Scenes
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:
- State Validation: Confirms the active
MainControlinstance is configured forOverworldstate. - Language Pack Generation: Executes
CreateLanguagePackFilesto generate empty text files in bothResources/LanguagePacks/andAssets/Resources/TextAssets/LanguagePacks/directories for the current scene name. - ScriptableObject Creation: Calls
EnsureScriptableObjectsto instantiate three required data assets underAssets/Resources/Tables/<SceneName>/:FactTable.assetEventTable.assetRuleTable.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.
Menu Navigation
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
- Create a scene using the
Template/Overworldtemplate. - Add the scene to Build Settings.
- Enter Play mode to trigger automatic generation of language packs and table assets.
Summary
- State-Driven Architecture:
MainControl.csdefines three scene states (Normal, Overworld, Battle) and orchestrates subsystem initialization throughStartWithSceneState(). - Unified Transition API:
GameUtilityService.csprovidesSwitchScene()for instant loads andFadeOutAndSwitchScene()for polished transitions with BGM control and callbacks. - Smart Navigation History: The system tracks
lastScenewhile excluding menu and utility scenes (Menu,Battle,GameOver, etc.) from the history stack. - Automated Asset Generation:
OverworldSceneHandler.csautomatically 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →