GameManager in the math runner game orchestrates the entire title → game → game-over/clear flow. It uses Time.timeScale to pause the game, switches between UI panels, and updates a Slider in real time to show progress. SoundManager manages four event sounds as a singleton.
Game Structure
| Script | Role |
|---|---|
GameManager |
Game state (start/over/clear), UI panel switching, progress bar |
SoundManager |
Door hit, raptor death, game clear, game-over sound effects |
GameManager – Full Code
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using UnityEngine.SceneManagement;
public class GameManager : MonoBehaviour
{
public static GameManager instance;
public bool isGameStart;
public GameObject titlePanel;
public GameObject gamePanel;
public GameObject gameOverPanel;
public GameObject clearPanel;
public Slider progressBar;
public TextMeshProUGUI nowStageText;
public TextMeshProUGUI nextStageText;
private void Awake()
{
if (instance != null)
{
Destroy(gameObject);
}
else
{
instance = this;
}
}
public void Start()
{
Time.timeScale = 0f; // pause on title screen
progressBar.value = 0f;
titlePanel.SetActive(true);
gamePanel.SetActive(false);
gameOverPanel.SetActive(false);
clearPanel.SetActive(false);
nowStageText.text = MapManager.instance.GetStage().ToString();
nextStageText.text = (MapManager.instance.GetStage() + 1).ToString();
}
public void GameStart()
{
isGameStart = true;
titlePanel.SetActive(false);
gamePanel.SetActive(true);
Time.timeScale = 1f; // resume game
}
void Update()
{
SetDistanceProgressBar();
}
public void SetDistanceProgressBar()
{
if (!isGameStart) return;
float goalDistance = DinoController.instance.transform.position.z
/ MapManager.instance.GetGoalDistance();
progressBar.value = goalDistance;
}
public void GameOver()
{
SoundManager.instance.GameOverSoundPlay();
isGameStart = false;
Time.timeScale = 0f;
gamePanel.SetActive(false);
gameOverPanel.SetActive(true);
}
public void StageClear()
{
SoundManager.instance.GameClearSoundPlay();
isGameStart = false;
Time.timeScale = 0f;
gamePanel.SetActive(false);
clearPanel.SetActive(true);
}
public void RestartGame()
{
SceneManager.LoadScene(0);
}
}
SoundManager – Full Code
using UnityEngine;
public class SoundManager : MonoBehaviour
{
public static SoundManager instance;
public AudioSource doorHit;
public AudioSource dinoDie;
public AudioSource gameClear;
public AudioSource gameOver;
private void Awake()
{
if (instance != null)
{
Destroy(this.gameObject);
}
else
{
instance = this;
}
}
public void DoorHitSoundPlay() { doorHit.Play(); }
public void DinoDieSoundPlay() { dinoDie.Play(); }
public void GameClearSoundPlay() { gameClear.Play(); }
public void GameOverSoundPlay() { gameOver.Play(); }
}
Key Concept Explanations
1. Time.timeScale – Controlling Game Time
Time.timeScale = 0f; // pause
Time.timeScale = 1f; // normal speed
Time.timeScale = 2f; // double speed
Time.timeScale is the multiplier applied to Unity's physics engine and Time.deltaTime.
| Value | Effect |
|---|---|
| 0 | All movement, physics, and animations freeze |
| 1 | Normal speed |
| 0.5 | Slow motion (half speed) |
| 2 | Double speed |
When Time.timeScale = 0:
Time.deltaTimebecomes 0.- Rigidbody physics stops.
Animatoranimations freeze.Update()still runs. UI clicks still work.
This lets the title screen pause the game while the Start button remains clickable.
// Title screen: start with the game paused
Time.timeScale = 0f;
// Start button → GameStart() called
public void GameStart()
{
isGameStart = true;
Time.timeScale = 1f; // resume
}
2. Multiple UI Panel Switching
titlePanel.SetActive(true);
gamePanel.SetActive(false);
gameOverPanel.SetActive(false);
clearPanel.SetActive(false);
Only one panel is active at a time; the others are hidden.
| State | titlePanel | gamePanel | gameOverPanel | clearPanel |
|---|---|---|---|---|
| Before start | ✅ | ❌ | ❌ | ❌ |
| Playing | ❌ | ✅ | ❌ | ❌ |
| Game over | ❌ | ❌ | ✅ | ❌ |
| Stage clear | ❌ | ❌ | ❌ | ✅ |
Each panel is an empty GameObject under the Canvas containing buttons, text, and other UI elements.
3. Slider – Progress Bar
public Slider progressBar;
// Initialize
progressBar.value = 0f; // 0 to 1
// Update every frame
float goalDistance = DinoController.instance.transform.position.z
/ MapManager.instance.GetGoalDistance();
progressBar.value = goalDistance;
Slider.value is a float between 0 and 1. Dividing the dinosaur's current Z position by the goal's Z position yields a 0–1 progress ratio:
Progress = current Z / goal Z
Start (Z = 0): 0/100 = 0.0 → Slider 0%
Halfway (Z = 50): 50/100 = 0.5 → Slider 50%
Goal (Z = 100): 100/100 = 1.0 → Slider 100%
Slider Inspector settings:
Min Value: 0Max Value: 1Whole Numbers: unchecked
4. AudioSource – Sound Effect Management
public AudioSource doorHit;
public void DoorHitSoundPlay()
{
doorHit.Play();
}
AudioSource.Play() is the basic way to play a sound in Unity.
audioSource.Play(); // play from the beginning
audioSource.PlayOneShot(clip); // allows overlapping playback (good for short effects)
audioSource.Stop(); // stop
audioSource.Pause(); // pause
Play() vs PlayOneShot():
Play(): If already playing, restarts from the beginning.PlayOneShot(): Multiple overlapping instances can play simultaneously.
This game uses one AudioSource per event for simplicity. Using a single AudioSource with PlayOneShot for multiple clips is also a common pattern.
5. isGameStart Flag – Gating Game Logic
// DinoController
void Update()
{
if (GameManager.instance.isGameStart) // only run while playing
{
DinoMove();
DoorCheck();
}
}
// DinoPositionController
void Update()
{
if (!GameManager.instance.isGameStart) return;
SetDinoPosition();
if (raptors.childCount <= 0)
GameManager.instance.GameOver();
}
A single isGameStart flag gates the logic in multiple scripts at once. Combined with Time.timeScale:
Time.timeScale = 0: freezes physics and animations.isGameStart = false: stops Update logic.
Both are set together during game-over and stage-clear.
6. SceneManager.LoadScene – Restarting the Scene
public void RestartGame()
{
SceneManager.LoadScene(0); // load scene at build index 0
}
SceneManager.LoadScene navigates to the specified scene. You can use an index or a name:
SceneManager.LoadScene(0); // scene at index 0 in Build Settings
SceneManager.LoadScene("GameScene"); // by name
Reloading the scene reinitializes all GameObjects, while PlayerPrefs (which holds the stage number) is preserved — so the next stage loads automatically.
Complete Game Flow Summary
[App starts]
↓ MapManager.Start(): build stage map
↓ GameManager.Start(): Time.timeScale=0, show title panel
[Start button clicked]
↓ GameManager.GameStart(): isGameStart=true, Time.timeScale=1
[Playing]
↓ DinoController: auto-advance, left/right movement, door/goal collision
↓ DinoPositionController: adjust raptor count, Fibonacci layout
↓ Enermy: detect, chase, capture raptors
↓ GameManager.Update: update progress bar
[Raptor count reaches 0]
↓ DinoPositionController → GameManager.GameOver()
↓ Time.timeScale=0, show game-over panel
[Goal reached]
↓ DinoController → save PlayerPrefs → GameManager.StageClear()
↓ Time.timeScale=0, show clear panel
[Restart button]
↓ SceneManager.LoadScene(0): reinitialize scene, load next stage map
Unity Scene Setup
| Item | Setting |
|---|---|
GameManager |
Attach to an empty GameObject; assign all UI references |
SoundManager |
Attach to a separate GameObject; create child AudioSources for each sound |
titlePanel |
Contains Start button; wire button OnClick to GameManager.GameStart |
gameOverPanel |
Contains Restart button; wire OnClick to GameManager.RestartGame |
clearPanel |
Contains Next Stage button; wire OnClick to GameManager.RestartGame |
progressBar |
Add a Slider to the Canvas; set Min=0, Max=1 |