Core Architecture of Undertale-Changer-Template: A Unity Framework Breakdown
The Undertale-Changer-Template uses a modular, service-oriented Unity architecture that separates game logic into distinct layers—Core management, ScriptableObject data containers, runtime Services, and scene-specific controllers—to enable flexible, data-driven game development.
The Undertale-Changer-Template is an open-source Unity framework designed for creating Undertale-like RPGs. Understanding the core architecture of Undertale-Changer-Template reveals how it balances singleton managers with ScriptableObject assets to create a scalable, maintainable game engine.
Architectural Layers Overview
The framework organizes functionality into eight distinct layers, each with specific responsibilities and entry points:
| Layer | Responsibility | Main Entry Point |
|---|---|---|
| Core | Global state, scene initialization, singleton management, language-pack handling, audio setup, and overall game flow. | MainControlSummon.cs |
| Core – Runtime | Per-scene behavior (overworld, battle, UI) and runtime services. | MainControl.cs |
| Settings | In-game configuration UI, key-binding editing, language-pack selection, screen-resolution toggles. | SettingsController.cs |
| Control (ScriptableObjects) | Data containers for Overworld, Battle, Audio, Language packs, player stats, etc. | OverworldControl.cs, AudioControl.cs |
| Service | Helper utilities (text processing, input handling, math helpers, data loading, XML parsing). | TextProcessingService.cs |
| EventSystem | Decoupled event tables and rules that drive dialogue, battles and overworld triggers. | EventController.cs |
| Overworld / Battle / UI | MonoBehaviour implementations that render the overworld, handle combat, and draw UI elements. | OverworldPlayerBehaviour.cs, BattleControl.cs |
How the Core Components Interact
The architecture follows a strict initialization sequence that ensures singleton persistence and proper scene state management:
1. Scene Entry and Singleton Guarantees
Every scene contains a MainControlSummon object. In its Awake method located at Assets/Scripts/UCT/Core/MainControlSummon.cs, the system:
- Detects the current
sceneState(Overworld, Battle, or Normal) - Calls
SetupController<T>for the main camera, canvas, audio source, and theMainControlinstance - Guarantees a single instance of
MainControlpersists across scene loads
2. MainControl as the Scene Engine
MainControl at Assets/Scripts/UCT/Core/MainControl.cs serves as the central manager. In its Awake method, it loads language packs, player data, and references to ScriptableObjects (AudioControl, BattleControl, OverworldControl). The Start method creates the UI canvas, initializes volume, and calls InitializationScene(), which branches based on the stored sceneState.
3. Scene-Specific Initialization
The framework handles three primary scene states:
- Overworld:
StartWithSceneState()creates the player character, global 2D light, and chase UI (OverworldChaseUIController) - Battle:
InitializationBattle()loads the battle configuration (defaulting toDemoBattleif none present) and spawns the battle UI - Normal: Basic scene setup without specialized game mode logic
4. Settings UI Integration
SettingsController at Assets/Scripts/UCT/Settings/SettingsController.cs manages the in-game configuration interface. It reads the current SettingsLayer from SettingsStorage, builds dynamic option lists (resolution, sound volume, key bindings, language packs), and renders them using TextMeshPro. User interaction is handled by polling InputService and applying changes via the underlying ScriptableObjects.
5. Data Flow Architecture
All static game data resides as ScriptableObjects in the Resources folder. DataHandlerService loads these at runtime, while TextProcessingService parses plain-text language packs. This separation allows designers to modify game content (dialogue, item stats, enemy configurations) without touching engine code.
6. Event-Driven Logic
EventController at Assets/Scripts/UCT/EventSystem/EventController.cs loads tables of facts, rules, and events (FactTable, RuleTable) from resources. These tables drive dialogue branching and overworld triggers through a decoupled event system, eliminating hard-coded logic in MonoBehaviours.
Key Implementation Details
Singleton Management with MainControlSummon
The MainControlSummon class uses a generic SetupController<T> helper method to enforce singleton patterns. This utility either configures an existing singleton instance or instantiates a new one if the instance is missing, ensuring that critical managers like MainControl, the main camera, and audio sources persist across scene transitions.
Scene State Initialization
The MainControl class maintains a sceneState enum that determines initialization behavior. When InitializationScene() is called, it evaluates this state to determine whether to spawn overworld entities, initialize battle systems, or perform standard scene setup. This branching logic ensures that each game mode receives appropriate resources and configuration.
Data-Driven Configuration
The framework relies heavily on ScriptableObjects for configuration management. Classes like OverworldControl, AudioControl, and BattleControl inherit from ScriptableObject and store references to assets such as mixer groups, font configurations, and battle turn scripts. This architecture allows designers to tweak game parameters through the Unity Inspector without modifying source code.
Practical Code Examples
Instantiating the Core Manager in a New Scene
Place this script on an empty GameObject in any new Unity scene to bootstrap the framework:
// Place this script on an empty GameObject in any new Unity scene.
using UnityEngine;
using UCT.Core;
public class SceneBootstrap : MonoBehaviour
{
void Awake()
{
// MainControlSummon will ensure MainControl and related singletons persist.
gameObject.AddComponent<MainControlSummon>();
}
}
The MainControlSummon script automatically runs its Awake method which detects the current sceneState (Overworld, Battle, Normal) and calls SetupController for the main camera, canvas, audio source, and the MainControl instance.
Switching Language Packs at Runtime
using UCT.Core;
using UnityEngine;
public class LanguageSwitcher : MonoBehaviour
{
// Call this with the index of the language pack you want to load.
public void SetLanguage(int languageIndex)
{
// MainControl holds the current languagePackId and exposes Initialization().
MainControl.Instance.languagePackId = languageIndex;
MainControl.Instance.Initialization(languageIndex);
// Refresh the UI to reflect new strings.
SettingsController.Instance.OpenSetting("SettingLanguagePackageLayer");
}
}
This method mirrors the logic inside SettingsController.GetKeyDownToLanguagePackage() which updates MainControl.Instance.languagePackId and calls MainControl.Instance.Initialization().
Opening the Settings Menu from Gameplay
using UCT.Core;
using UnityEngine;
public class SettingsOpener : MonoBehaviour
{
void Update()
{
// Press V to open the settings UI (same key used by the engine).
if (Input.GetKeyDown(KeyCode.V) && !MainControl.Instance.overworldControl.isSetting)
{
SettingsController.Instance.OpenSetting();
}
}
}
The OpenSetting method (see SettingsController.cs lines 553-564) fades in the settings canvas, sets the initial selected option, and displays the control description.
Critical Source Files
| File | Path | Role |
|---|---|---|
MainControlSummon.cs |
Assets/Scripts/UCT/Core/MainControlSummon.cs |
Guarantees singletons, persists core objects across scenes, starts MainControl. |
MainControl.cs |
Assets/Scripts/UCT/Core/MainControl.cs |
Central manager for game state, language packs, audio, event tables, and scene-specific initialization. |
SettingsController.cs |
Assets/Scripts/UCT/Settings/SettingsController.cs |
Implements the in-game settings UI, handles user input, and writes changes back to ScriptableObjects. |
OverworldControl.cs |
Assets/Scripts/UCT/Control/OverworldControl.cs |
ScriptableObject that stores overworld-specific data (camera limits, UI fonts, etc.). |
AudioControl.cs |
Assets/Scripts/UCT/Control/AudioControl.cs |
Holds audio mixer references and default clips for BGM and SFX. |
EventController.cs |
Assets/Scripts/UCT/EventSystem/EventController.cs |
Loads fact, rule, and event tables that power dialogue and overworld triggers. |
TextProcessingService.cs |
Assets/Scripts/UCT/Service/TextProcessingService.cs |
Parses language-pack text assets and provides string lookup for UI and dialogs. |
DataHandlerService.cs |
Assets/Scripts/UCT/Service/DataHandlerService.cs |
Centralized loading of resources, language detection, and item data handling. |
OverworldPlayerBehaviour.cs |
Assets/Scripts/UCT/Overworld/OverworldPlayerBehaviour.cs |
Player controller used in the overworld state, interacts with MainControl. |
BattleControl.cs |
Assets/Scripts/UCT/Control/BattleControl.cs |
Stores the current battle configuration (BGM, turn scripts, enemy data). |
These files together illustrate the modular, service-oriented architecture of the Undertale-Changer-Template: a thin Unity MonoBehaviour layer delegates most logic to singleton managers, ScriptableObjects, and utility services, making it straightforward to swap out or extend parts (e.g., adding new battle configs or language packs) without touching core engine code.
Summary
- The Undertale-Changer-Template employs a layered architecture separating Core management, Runtime behavior, Settings UI, and Data layers.
- Singleton persistence is enforced by
MainControlSummon.cs, which guarantees thatMainControland essential services survive scene transitions. - ScriptableObjects (
OverworldControl,BattleControl,AudioControl) store configuration data, enabling designer-friendly tweaking without code changes. - Scene state branching in
MainControl.csautomatically initializes Overworld, Battle, or Normal modes based on the currentsceneStateenum. - Decoupled event logic via
EventController.csdrives dialogue and triggers through data tables rather than hard-coded MonoBehaviour logic.
Frequently Asked Questions
What is the role of MainControlSummon in the Undertale-Changer-Template?
MainControlSummon acts as the entry point for every scene in Assets/Scripts/UCT/Core/MainControlSummon.cs. It ensures that singleton instances like MainControl, the main camera, and audio sources persist across scene loads using the generic SetupController<T> helper, preventing duplicate manager objects and maintaining global game state.
How does the template handle different game modes like Overworld and Battle?
The MainControl class in Assets/Scripts/UCT/Core/MainControl.cs stores a sceneState enum that determines initialization behavior. When InitializationScene() is called, it branches to StartWithSceneState() for Overworld mode (spawning the player and chase UI) or InitializationBattle() for combat mode (loading battle configurations from BattleControl ScriptableObjects).
Why does the architecture rely heavily on ScriptableObjects?
ScriptableObjects such as OverworldControl, AudioControl, and BattleControl (located in Assets/Scripts/UCT/Control/) separate data from behavior. This design allows game designers to modify camera limits, audio mixer groups, and enemy turn scripts directly in the Unity Inspector without modifying C# code, facilitating rapid iteration and modular content creation.
What services handle text and data loading in the framework?
The TextProcessingService in Assets/Scripts/UCT/Service/TextProcessingService.cs parses plain-text language packs for localization, while DataHandlerService in Assets/Scripts/UCT/Service/DataHandlerService.cs manages centralized resource loading and item data handling. These utility services abstract file I/O operations, allowing the Core and UI layers to access game data through simple API calls.
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 →