You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何优化游戏单例状态机?避免暂停逻辑代码冗余的技术问询

游戏暂停状态机优化方案

问题背景

我是游戏编程专业学生,之前对比过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!");
    }
}

优化后的使用方式

  1. 移除所有脚本中分散的暂停逻辑代码(比如之前的Update里的判断)。
  2. 给所有需要暂停的对象(如敌人、道具、玩家等)挂载PauseableComponent脚本。
  3. 状态机切换状态时,会自动调用所有挂载了该组件的对象的暂停/恢复方法,无需手动维护Animator数组或单独写逻辑。

扩展性说明

如果后续需要新增其他需要暂停的组件(比如粒子系统、AudioSource),只需要在PauseableComponent中添加对应组件的检测和状态保存逻辑即可,无需修改状态机或其他业务脚本,完全符合开闭原则。

内容的提问来源于stack exchange,提问作者Indra Cosimo Bowen-Paoli

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 21:05:54