Complete GameStateManager.cs
Here's the complete, ready-to-use code for managing game states with singleton in Unity:
using System;
using UnityEngine;
public class GameStateManager : MonoBehaviour
{
// Singleton instance
private static GameStateManager _instance;
public static GameStateManager Instance
{
get
{
if (_instance == null)
{
_instance = FindObjectOfType<GameStateManager>();
if (_instance == null)
{
GameObject go = new GameObject("GameStateManager");
_instance = go.AddComponent<GameStateManager>();
}
}
return _instance;
}
}
// Define all possible game states
public enum GameState
{
MainMenu,
Playing,
Paused,
GameOver,
Victory
}
// Event that fires when state changes
public event Action<GameState, GameState> OnStateChanged;
// Current state storage
private GameState _currentState;
public GameState CurrentState
{
get { return _currentState; }
private set
{
if (_currentState != value)
{
GameState previousState = _currentState;
_currentState = value;
HandleStateChange(previousState, _currentState);
OnStateChanged?.Invoke(previousState, _currentState);
}
}
}
void Awake()
{
// Enforce singleton pattern
if (_instance != null && _instance != this)
{
Destroy(gameObject);
return;
}
_instance = this;
DontDestroyOnLoad(gameObject);
// Set initial state
CurrentState = GameState.MainMenu;
}
// Public method to change state
public void ChangeState(GameState newState)
{
CurrentState = newState;
}
// Handle what happens when state changes
private void HandleStateChange(GameState previous, GameState current)
{
Debug.Log($"Game State changed from {previous} to {current}");
switch (current)
{
case GameState.MainMenu:
Time.timeScale = 1f;
Cursor.visible = true;
Cursor.lockState = CursorLockMode.None;
break;
case GameState.Playing:
Time.timeScale = 1f;
Cursor.visible = false;
Cursor.lockState = CursorLockMode.Locked;
break;
case GameState.Paused:
Time.timeScale = 0f;
Cursor.visible = true;
Cursor.lockState = CursorLockMode.None;
break;
case GameState.GameOver:
Time.timeScale = 0f;
Cursor.visible = true;
Cursor.lockState = CursorLockMode.None;
break;
case GameState.Victory:
Time.timeScale = 0f;
Cursor.visible = true;
Cursor.lockState = CursorLockMode.None;
break;
}
}
// Helper methods for common state checks
public bool IsPlaying()
{
return CurrentState == GameState.Playing;
}
public bool IsPaused()
{
return CurrentState == GameState.Paused;
}
public bool IsGameOver()
{
return CurrentState == GameState.GameOver;
}
}
Example Usage Scripts
PlayerController.cs
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public float moveSpeed = 5f;
public int health = 100;
void Update()
{
// Only allow movement when playing
if (GameStateManager.Instance.IsPlaying())
{
HandleMovement();
}
// Pause/Unpause with Escape key
if (Input.GetKeyDown(KeyCode.Escape))
{
if (GameStateManager.Instance.IsPlaying())
{
GameStateManager.Instance.ChangeState(GameStateManager.GameState.Paused);
}
else if (GameStateManager.Instance.IsPaused())
{
GameStateManager.Instance.ChangeState(GameStateManager.GameState.Playing);
}
}
}
void HandleMovement()
{
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
Vector3 movement = new Vector3(horizontal, 0, vertical);
transform.Translate(movement * moveSpeed * Time.deltaTime);
}
public void TakeDamage(int damage)
{
health -= damage;
if (health <= 0)
{
Die();
}
}
void Die()
{
Debug.Log("Player died!");
GameStateManager.Instance.ChangeState(GameStateManager.GameState.GameOver);
}
}
UIManager.cs
using UnityEngine;
using UnityEngine.UI;
public class UIManager : MonoBehaviour
{
[Header("UI Panels")]
public GameObject mainMenuPanel;
public GameObject pausePanel;
public GameObject gameOverPanel;
public GameObject victoryPanel;
public GameObject hudPanel;
[Header("Buttons")]
public Button playButton;
public Button resumeButton;
public Button restartButton;
public Button quitButton;
void Start()
{
// Subscribe to state changes
GameStateManager.Instance.OnStateChanged += HandleStateChange;
// Setup button listeners
if (playButton != null)
playButton.onClick.AddListener(OnPlayButtonClicked);
if (resumeButton != null)
resumeButton.onClick.AddListener(OnResumeButtonClicked);
if (restartButton != null)
restartButton.onClick.AddListener(OnRestartButtonClicked);
if (quitButton != null)
quitButton.onClick.AddListener(OnQuitButtonClicked);
// Initialize UI based on current state
UpdateUI(GameStateManager.Instance.CurrentState);
}
void OnDestroy()
{
// Unsubscribe from events
if (GameStateManager.Instance != null)
{
GameStateManager.Instance.OnStateChanged -= HandleStateChange;
}
}
private void HandleStateChange(GameStateManager.GameState previous, GameStateManager.GameState current)
{
UpdateUI(current);
}
private void UpdateUI(GameStateManager.GameState state)
{
// Hide all panels first
if (mainMenuPanel != null) mainMenuPanel.SetActive(false);
if (pausePanel != null) pausePanel.SetActive(false);
if (gameOverPanel != null) gameOverPanel.SetActive(false);
if (victoryPanel != null) victoryPanel.SetActive(false);
if (hudPanel != null) hudPanel.SetActive(false);
// Show appropriate panel
switch (state)
{
case GameStateManager.GameState.MainMenu:
if (mainMenuPanel != null) mainMenuPanel.SetActive(true);
break;
case GameStateManager.GameState.Playing:
if (hudPanel != null) hudPanel.SetActive(true);
break;
case GameStateManager.GameState.Paused:
if (pausePanel != null) pausePanel.SetActive(true);
if (hudPanel != null) hudPanel.SetActive(true);
break;
case GameStateManager.GameState.GameOver:
if (gameOverPanel != null) gameOverPanel.SetActive(true);
break;
case GameStateManager.GameState.Victory:
if (victoryPanel != null) victoryPanel.SetActive(true);
break;
}
}
// Button callbacks
private void OnPlayButtonClicked()
{
GameStateManager.Instance.ChangeState(GameStateManager.GameState.Playing);
}
private void OnResumeButtonClicked()
{
GameStateManager.Instance.ChangeState(GameStateManager.GameState.Playing);
}
private void OnRestartButtonClicked()
{
UnityEngine.SceneManagement.SceneManager.LoadScene(
UnityEngine.SceneManagement.SceneManager.GetActiveScene().name
);
GameStateManager.Instance.ChangeState(GameStateManager.GameState.Playing);
}
private void OnQuitButtonClicked()
{
#if UNITY_EDITOR
UnityEditor.EditorApplication.isPlaying = false;
#else
Application.Quit();
#endif
}
}
EnemyController.cs
using UnityEngine;
public class EnemyController : MonoBehaviour
{
public float moveSpeed = 3f;
public int damage = 10;
private Transform player;
void Start()
{
player = GameObject.FindGameObjectWithTag("Player")?.transform;
}
void Update()
{
// Only move when game is playing
if (GameStateManager.Instance.IsPlaying() && player != null)
{
ChasePlayer();
}
}
void ChasePlayer()
{
Vector3 direction = (player.position - transform.position).normalized;
transform.position += direction * moveSpeed * Time.deltaTime;
}
void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.CompareTag("Player"))
{
PlayerController playerController = collision.gameObject.GetComponent<PlayerController>();
if (playerController != null)
{
playerController.TakeDamage(damage);
}
}
}
}
GameWinTrigger.cs
using UnityEngine;
public class GameWinTrigger : MonoBehaviour
{
void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
Debug.Log("Player reached the goal!");
GameStateManager.Instance.ChangeState(GameStateManager.GameState.Victory);
}
}
}
Setup Instructions
- Create an empty GameObject in your scene and name it "GameStateManager"
- Attach the GameStateManager.cs script to it
- Create UI Canvas with panels for MainMenu, Pause, GameOver, Victory, and HUD
- Create another empty GameObject and attach UIManager.cs
- Assign all UI panels and buttons in the UIManager inspector
- Attach PlayerController.cs to your player GameObject
- Tag your player as "Player" in the inspector
- Attach EnemyController.cs to enemy GameObjects
- Create a goal trigger with GameWinTrigger.cs attached
Now your game will properly manage states throughout gameplay!
Comments