如何优化游戏单例状态机?避免暂停逻辑代码冗余的技术问询
游戏暂停状态机优化方案
问题背景
我是游戏编程专业学生,之前对比过Time.timeScale和单例状态机实现游戏暂停的差异,最终选择用单例状态机,因为它能提供更精细的控制。但目前遇到的问题是:要暂停动画、重力这类元素时,必须在每个涉及的脚本里单独写暂停/恢复逻辑,导致代码大量冗余。我希望升级状态机,让它能自动检测对象中需要处理的组件,统一完成暂停/恢复操作。
现有代码问题分析
现有GameStateHandler代码
#region GameStateHandler using System.Collections; using UnityEngine; using UnityEngine.SceneManagement; /* This is a gamestate handler that uses the singleton design pattern. Its primary function for this project is for pausing and resuming from pause. I chose to use a state machine for handling the pause functionality in our game due to the fact that it enables a larger amount of control than in comparison to something like TimeScale = 0. In order for something to be paused a reference to this script would be called like so: if (GameStateHandler.Instance != null && GameStateHandler.Instance.CurrentState == GameState.Paused) { return; } This will stop the object once was pause has been activated. */ public enum GameState { Playing, Paused, //GameOver } public class GameStateHandler : MonoBehaviour { public static GameStateHandler Instance { get; private set; } public GameState CurrentState { get; private set; } = GameState.Playing; public Animator[] animators; public Health health; private PlayerMovement playerMovement; public static bool Paused; public bool isGameOver = false; private UIManager uiManager; //While Asynchronistic loading isn't exactly necessary for handling pause/playing state I have included it to ensure smooth loading between the menu and levels. public void SceneChange(string name) { StartCoroutine(LoadSceneAsync(name)); } private IEnumerator LoadSceneAsync(string sceneName) { AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName); while (!asyncLoad.isDone) { yield return null; } } void Awake() { if (Instance == null) { Instance = this; } else { Destroy(gameObject); } uiManager = GetComponent<UIManager>(); if (uiManager == null) { Debug.Log("UIManager not found in scene, please attach."); } } void Start() { if (uiManager != null) { uiManager.OnResumePress(); } } public void ChangeState(GameState newState) //Handlles the transition between states. { if (CurrentState == newState) return; CurrentState = newState; switch (newState) { case GameState.Paused: PauseGame(); break; case GameState.Playing: ResumeGame(); break; //case GameState.GameOver: // GameOver(); // break; } } public void TogglePause() { if (CurrentState == GameState.Playing) { ChangeState(GameState.Paused); } else if (CurrentState == GameState.Paused) { ChangeState(GameState.Playing); } } public void PauseGame() { Debug.Log("Game Paused"); //stop animations foreach (Animator animator in animators) { animator.enabled = false; //pauses all animations } } public void ResumeGame() { if (uiManager != null) { uiManager.OnResumePress(); } Debug.Log("Game Resumed"); ChangeState(GameState.Playing); } public void GoToMainMenu() { if (GameStateHandler.Instance.CurrentState == GameState.Paused) { return; } SceneManager.LoadScene(0); //Assuming menu = scene 0 } public void QuitGame() { Application.Quit(); Debug.Log("See ya later!"); } //The Game Over state ended up not being used in this project. //public void GameOver() //{ // if (CurrentState == GameState.Paused) // { // ResumeGame(); // } // ChangeState(GameState.GameOver); // isGameOver = true; // Debug.Log("Game Over"); // if (playerMovement != null) // { // playerMovement.enabled = false; // } // Paused = false; //} } #endregion
冗余的暂停逻辑代码(分散在各个脚本的Update中)
void Update() { if (GameStateHandler.Instance != null && GameStateHandler.Instance.CurrentState == GameState.Paused) { if (ani != null && ani.speed != 0) { previousAnimatorSpeed = ani.speed; ani.speed = 0; } if (rb.useGravity) { isGravityEnabled = true; rb.useGravity = false; previousLinearVelocity = rb.linearVelocity; //store previous velocity otherwise you can spam pause and keep going forward rb.linearVelocity = Vector3.zero; // Stop all movement } transform.position = previousPosition; return; } else { // Restore gravity when resumed if (!isGravityEnabled && rb.useGravity == false) { rb.useGravity = true; } // Restore animation speed when resumed if (ani != null && ani.speed == 0 && previousAnimatorSpeed != 0) { ani.speed = previousAnimatorSpeed; } //restore velocity when resumed otherwise you can spam pause and keep going forward if (rb.linearVelocity == Vector3.zero) { rb.linearVelocity = previousLinearVelocity; } } { rb.useGravity = isGravityEnabled; if (ani.speed == 0 && previousAnimatorSpeed != 0) { ani.speed = previousAnimatorSpeed; } } previousPosition = transform.position; }
优化思路
核心思路是将分散的暂停逻辑收拢到统一的组件中,由状态机统一调度:
- 定义一个
PauseableComponent脚本,挂载到所有需要暂停的对象上,自动检测自身的Animator、Rigidbody等组件,并保存这些组件的状态(如动画速度、重力开关、刚体速度)。 - 让
GameStateHandler维护一个PauseableComponent的列表,提供注册/注销接口,PauseableComponent在Awake时自动注册到状态机,OnDestroy时注销。 - 当状态机切换到暂停/恢复状态时,遍历列表调用所有
PauseableComponent的暂停/恢复方法,统一处理组件状态。
具体实现代码
1. 统一的PauseableComponent
using UnityEngine; public class PauseableComponent : MonoBehaviour { private Animator _animator; private Rigidbody _rigidbody; // 保存状态的变量 private float _prevAnimatorSpeed; private bool _prevGravityState; private Vector3 _prevRigidbodyVelocity; private Vector3 _prevPosition; void Awake() { // 自动检测组件 _animator = GetComponent<Animator>(); _rigidbody = GetComponent<Rigidbody>(); // 注册到状态机 GameStateHandler.Instance.RegisterPauseable(this); } void OnDestroy() { // 从状态机注销 GameStateHandler.Instance.UnregisterPauseable(this); } public void Pause() { // 保存当前状态并暂停 if (_animator != null) { _prevAnimatorSpeed = _animator.speed; _animator.speed = 0; } if (_rigidbody != null) { _prevGravityState = _rigidbody.useGravity; _prevRigidbodyVelocity = _rigidbody.linearVelocity; _rigidbody.useGravity = false; _rigidbody.linearVelocity = Vector3.zero; } _prevPosition = transform.position; } public void Resume() { // 恢复之前的状态 if (_animator != null) { _animator.speed = _prevAnimatorSpeed; } if (_rigidbody != null) { _rigidbody.useGravity = _prevGravityState; _rigidbody.linearVelocity = _prevRigidbodyVelocity; } } // 如果需要固定位置防止暂停时位移,在Update中处理 void Update() { if (GameStateHandler.Instance.CurrentState == GameState.Paused) { transform.position = _prevPosition; } else { _prevPosition = transform.position; } } }
2. 修改后的GameStateHandler
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; public enum GameState { Playing, Paused, //GameOver } public class GameStateHandler : MonoBehaviour { public static GameStateHandler Instance { get; private set; } public GameState CurrentState { get; private set; } = GameState.Playing; private UIManager _uiManager; private List<PauseableComponent> _pauseableComponents = new List<PauseableComponent>(); void Awake() { if (Instance == null) { Instance = this; DontDestroyOnLoad(gameObject); // 可选,跨场景保留状态机 } else { Destroy(gameObject); } _uiManager = GetComponent<UIManager>(); if (_uiManager == null) { Debug.Log("UIManager not found in scene, please attach."); } } void Start() { _uiManager?.OnResumePress(); } public void RegisterPauseable(PauseableComponent component) { if (!_pauseableComponents.Contains(component)) { _pauseableComponents.Add(component); } } public void UnregisterPauseable(PauseableComponent component) { if (_pauseableComponents.Contains(component)) { _pauseableComponents.Remove(component); } } public void ChangeState(GameState newState) { if (CurrentState == newState) return; CurrentState = newState; switch (newState) { case GameState.Paused: PauseGame(); break; case GameState.Playing: ResumeGame(); break; } } public void TogglePause() { ChangeState(CurrentState == GameState.Playing ? GameState.Paused : GameState.Playing); } private void PauseGame() { Debug.Log("Game Paused"); foreach (var component in _pauseableComponents) { component.Pause(); } } private void ResumeGame() { _uiManager?.OnResumePress(); Debug.Log("Game Resumed"); foreach (var component in _pauseableComponents) { component.Resume(); } } public void SceneChange(string name) { StartCoroutine(LoadSceneAsync(name)); } private IEnumerator LoadSceneAsync(string sceneName) { AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName); while (!asyncLoad.isDone) { yield return null; } } public void GoToMainMenu() { if (CurrentState == GameState.Paused) return; SceneManager.LoadScene(0); } public void QuitGame() { Application.Quit(); Debug.Log("See ya later!"); } }
优化后的使用方式
- 移除所有脚本中分散的暂停逻辑代码(比如之前的Update里的判断)。
- 给所有需要暂停的对象(如敌人、道具、玩家等)挂载
PauseableComponent脚本。 - 状态机切换状态时,会自动调用所有挂载了该组件的对象的暂停/恢复方法,无需手动维护Animator数组或单独写逻辑。
扩展性说明
如果后续需要新增其他需要暂停的组件(比如粒子系统、AudioSource),只需要在PauseableComponent中添加对应组件的检测和状态保存逻辑即可,无需修改状态机或其他业务脚本,完全符合开闭原则。
内容的提问来源于stack exchange,提问作者Indra Cosimo Bowen-Paoli
相关产品推荐
相关产品推荐

