# How the Battle System Is Implemented in Undertale-Changer-Template: A Complete Technical Guide

> Explore the battle system implementation in Undertale-Changer-Template. Learn about its data-driven ScriptableObject architecture, turn logic, and player heart mechanics for advanced game development.

- Repository: [Archived AIk/undertale-changer-template](https://github.com/arch-aik/undertale-changer-template)
- Tags: how-to-guide
- Published: 2026-02-25

---

**The Undertale-Changer-Template battle system uses a data-driven ScriptableObject architecture that separates configuration, turn logic, and player heart mechanics, allowing developers to define enemy attacks via the `IBattleConfig` interface while the `TurnController` manages bullet spawning through object pools and the `BattlePlayerController` handles seven distinct heart colors with unique movement rules.**

The Undertale-Changer-Template repository by **arch-aik** provides a modular Unity framework for creating Undertale-style bullet-hell encounters. Understanding how the battle system is implemented in Undertale-Changer-Template requires examining its layered architecture, from the configuration-driven `IBattleConfig` interface to the color-specific heart mechanics in `BattlePlayerController`.

## Configuration Layer: IBattleConfig and Data-Driven Battles

The battle system is built around a **data-driven, scriptable-object architecture** that separates configuration from logic. The entry point for any battle is the `IBattleConfig` interface.

### The IBattleConfig Interface

Located in [`Assets/Scripts/UCT/Battle/BattleConfigs/IBattleConfig.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/Battle/BattleConfigs/IBattleConfig.cs), this contract defines everything needed to bootstrap a battle:

```csharp
public interface IBattleConfig {
    GameObject[] enemies { get; }
    Vector3?[] enemiesStartPosition { get; }
    GameObject backGroundModel { get; }
    Material skyBox { get; }
    VolumeProfile volumeProfile { get; }
    AudioClip bgmClip { get; }
    float volume { get; }
    float pitch { get; }
    IEnumerator<float> Turn(int turnNumber, ObjectPool bulletPool);
}

```

A config supplies enemy prefabs, visual assets (background, skybox, post-processing), audio settings, and the **turn script**—a coroutine that receives the current turn index and a bullet pool.

### Example: DemoBattle Implementation

The `DemoBattle` class in [`Assets/Scripts/UCT/Battle/BattleConfigs/DemoBattle.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/Battle/BattleConfigs/DemoBattle.cs) demonstrates a minimal implementation that spawns two NPC enemies, loads a Vaporwave background, plays a BGM, and defines demo turns that animate the battle box and fire a "CupCake" bullet pattern.

## Global State Management with BattleControl

`BattleControl` is a **ScriptableObject** (located in [`Assets/Scripts/UCT/Control/BattleControl.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/Control/BattleControl.cs)) that stores shared battle resources:

* **Color palettes** for bullets and hearts (`bulletColorList`, `playerColorList`, `playerMissColorList`).
* **Turn-related data** (`actSave`, `mercySave`, `enemiesNameSave`, `turnTextSave`, `turnDialogAsset`).
* A reference to the active `IBattleConfig` (`BattleConfig`).

These lists are loaded at battle start from language packs, allowing designers to localize UI text without recompiling.

## Turn Engine: TurnController and Object Pooling

The `TurnController` in [`Assets/Scripts/UCT/Battle/TurnController.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/Battle/TurnController.cs) orchestrates the alternating enemy-player loop.

### Enemy Turn Execution

The turn flow follows this sequence:

1. **EnterEnemyTurn** sets `isMyTurn = false` and runs `_TurnExecute`.
2. `_TurnExecute` first processes any **overlap turns** (shared turn numbers for multi-enemy encounters).
3. It then **awaits the battle config's `Turn` coroutine**, passing a `bulletPool` for spawning.
4. After the coroutine finishes, the turn counter increments, and `SelectUIController.EnterPlayerTurn()` enables player input.

### Bullet Pool Management

Object pools for **bullets, boards (orange-heart platforms), and yellow-bullet effects** are created once at start via `ObjectPool` instances, reducing runtime allocation and GC pressure.

## Player Heart Mechanics: BattlePlayerController

The player heart is implemented in [`Assets/Scripts/UCT/Battle/BattlePlayerController.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/Battle/BattlePlayerController.cs) as a `MonoBehaviour` that supports **seven distinct colors**, each with unique movement and interaction rules.

### The Seven Heart Colors

| Color | Behaviour |
|-------|-----------|
| **Red** | Standard movement, basic hit detection. |
| **Orange** | Continuous movement while a key is held (`PlayerContinuouslyMove`). |
| **Yellow** | Fires a yellow bullet when `Z` is pressed (`YellowTimerMax`). |
| **Green** | Shows a "ghost" arrow for targeting (implemented in `Update`). |
| **Cyan / Blue** | Gravity-based movement with board interaction; blue can jump. |
| **Purple** | Moves *along a line* selected by the UI (`PlayerMoveWithLine`). |

### Color-Specific Movement Logic

Core methods include:

* `UpdatePlayer()` – dispatches to the appropriate colour-specific handler.
* `FixedUpdatePlayer()` – resolves physics, reads input, and moves the transform.
* `PlayerColorMoving()` – a switch statement calling `PlayerCommonMove`, `PlayerContinuouslyMove`, `PlayerWithGravity`, etc.
* `ChangePlayerColor()` – animates colour/shape changes and updates `playerColor`.

All heart logic is accessed through `MainControl.Instance.battlePlayerController`, instantiated during `MainControl.InitializationBattle`.

## Bullet System: BulletController and Collision

Bullets are **pooled objects** managed by [`Assets/Scripts/UCT/Battle/BulletController.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/Battle/BulletController.cs). Each bullet:

* Holds a reference to its `BulletControl` data (sprite, collider sizes, colour).
* Updates its collider size each frame unless `followMode` is `NoFollow`.
* Detects collisions with the player heart using `OnTriggerStay2D`.
* Calls `HitPlayerInBattle` when appropriate, applying damage based on the current heart colour (e.g., orange bullets only hit a moving heart).

Bullet colours are mapped to visual palettes from `BattleControl.bulletColorList`.

## Scene Bootstrap: MainControl.InitializationBattle

When a scene's state is `Battle`, `MainControl.InitializationBattle()` in [`Assets/Scripts/UCT/Core/MainControl.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/Core/MainControl.cs) executes:

1. Loads the **battle config** (fallback to `DemoBattle` if none assigned).
2. Reads **language packs** for turn text, act/mercy options, and enemy names.
3. Instantiates references to `battlePlayerController`, `selectUIController`, and battle-specific cameras.
4. Starts the BGM via `AudioController`.

The **singleton pattern** (`MainControl.Instance`) provides global access for all battle components.

## Key Files Reference

| Role | File | Link |
|------|------|------|
| Battle state (colors, turn data) | [`Assets/Scripts/UCT/Control/BattleControl.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/Control/BattleControl.cs) | [BattleControl.cs](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/Control/BattleControl.cs) |
| Configuration interface | [`Assets/Scripts/UCT/Battle/BattleConfigs/IBattleConfig.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/Battle/BattleConfigs/IBattleConfig.cs) | [IBattleConfig.cs](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/Battle/BattleConfigs/IBattleConfig.cs) |
| Example config (demo) | [`Assets/Scripts/UCT/Battle/BattleConfigs/DemoBattle.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/Battle/BattleConfigs/DemoBattle.cs) | [DemoBattle.cs](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/Battle/BattleConfigs/DemoBattle.cs) |
| Turn orchestration & object pools | [`Assets/Scripts/UCT/Battle/TurnController.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/Battle/TurnController.cs) | [TurnController.cs](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/Battle/TurnController.cs) |
| Player heart logic | [`Assets/Scripts/UCT/Battle/BattlePlayerController.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/Battle/BattlePlayerController.cs) | [BattlePlayerController.cs](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/Battle/BattlePlayerController.cs) |
| Bullet behaviour & collision | [`Assets/Scripts/UCT/Battle/BulletController.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/Battle/BulletController.cs) | [BulletController.cs](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/Battle/BulletController.cs) |
| Scene bootstrap & audio setup | [`Assets/Scripts/UCT/Core/MainControl.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/Core/MainControl.cs) | [MainControl.cs](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/Core/MainControl.cs) |

## Summary

*   The **Undertale-Changer-Template battle system** uses a data-driven architecture centered on the `IBattleConfig` interface, allowing developers to define enemies, visuals, and turn logic via ScriptableObjects.
*   **Turn management** is handled by `TurnController`, which executes coroutine-based turn scripts from the config, utilizing object pools for efficient bullet spawning.
*   **Player mechanics** are implemented in `BattlePlayerController`, supporting seven distinct heart colors (Red, Orange, Yellow, Green, Cyan, Blue, Purple) with unique physics and input rules.
*   **Collision and damage** are processed by `BulletController`, which detects hits via `OnTriggerStay2D` and applies color-specific damage rules (e.g., orange bullets only damage moving hearts).
*   **Scene initialization** occurs through `MainControl.InitializationBattle`, which loads configs, language packs, and instantiates all battle components via a singleton pattern.

## Frequently Asked Questions

### How do I create a custom battle configuration in Undertale-Changer-Template?

Create a new class implementing the `IBattleConfig` interface located in [`Assets/Scripts/UCT/Battle/BattleConfigs/IBattleConfig.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/Battle/BattleConfigs/IBattleConfig.cs). Define properties for enemies, background assets, and audio, then implement the `Turn` coroutine to script bullet patterns using the provided `ObjectPool`. Assign your config to `BattleControl.BattleConfig` via the Unity Inspector or at runtime.

### What are the seven heart colors and how do they behave?

The `BattlePlayerController` supports seven colors defined in `BattleControl.PlayerColor`: **Red** (standard movement), **Orange** (continuous movement while key held), **Yellow** (shoots bullets when Z is pressed), **Green** (targeting arrow mode), **Cyan** (gravity-affected, stops on platforms), **Blue** (gravity with jump ability), and **Purple** (restricted to horizontal lines). Each color dispatches to specific methods like `PlayerContinuouslyMove` or `PlayerWithGravity`.

### How does bullet pooling improve performance?

The `TurnController` creates `ObjectPool` instances for bullets, boards, and yellow-bullet effects at battle start. Instead of instantiating and destroying bullets during gameplay, `BulletController` objects are retrieved via `bulletPool.GetFromPool<BulletController>()` and returned to the pool when no longer needed. This eliminates runtime garbage collection spikes during intense bullet-hell patterns.

### Can I implement multi-enemy encounters with overlapping turns?

Yes. The `TurnController._TurnExecute` method supports **overlap turns** for multi-enemy configurations. When processing a turn number, the controller first checks for and executes any shared turns across multiple enemies before running the primary turn coroutine from `IBattleConfig.Turn`. This allows synchronized attack patterns where multiple enemies fire bullets simultaneously while maintaining separate health pools and positions defined in the config's `enemies` and `enemiesStartPosition` arrays.