在 Unity 中正確的暫停方法



前言

在初期大家一開始在學習 Unity 的暫停功能時, 相信大家最常用的就是 Time.timeScale, 而這個方法是藉由改變時間來達到此效果, 同時在小專案上也能快速實現, 但這個做法會有一些問題, 像是介面的動畫效果、場景加載、音效觸發等問題, 當然,用 Time.timeScale 也並非不能解決以上講的問題, 但當專案大時為了讓程式碼更乾淨, 同時能更好控管遊戲裡物件的狀態, 所以接下來要講的方法是, 藉由一個管理器來處理遊戲內暫停的功能, 而裡面有用到的知識有列舉、委派、事件, 以及最重要的單例模式。

程式碼

GameState

public enum GameState
{
    Gameplay,
    Pause
}

GameStateManager

public class GameStateManager
{
    private static GameStateManager _instance;

    public static GameStateManager Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new GameStateManager();
            }

            return _instance;
        }
    }

    public GameState CurrentGameState { get; private set; }

    public delegate void GameStateChangeHandler(GameState newGameState);

    public event GameStateChangeHandler OnGameStateChanged;

    private GameStateManager()
    {
    }

    public void SetState(GameState newGameState)
    {
        if (newGameState == CurrentGameState)
        {
            return;
        }

        CurrentGameState = newGameState;
        OnGameStateChanged?.Invoke(newGameState);
    }
}

PauseController

public class PauseController : MonoBehaviour
{
    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Escape))
        {
            GameState currentGameState = GameStateManager.Instance.CurrentGameState;
            GameState newGameState = currentGameState == GameState.Gameplay
                ? GameState.Pause
                : GameState.Gameplay;

            GameStateManager.Instance.SetState(newGameState);
        }
    }
}

PlayerMove

using UnityEngine;

public class PlayerMove : MonoBehaviour
{
    [SerializeField] private float _speed;

    private Rigidbody2D _rigidbody2D;

    private void Start()
    {
        _rigidbody2D = GetComponent<Rigidbody2D>();

        GameStateManager.Instance.OnGameStateChanged += OnGameStateChanged;
    }

    private void OnDestroy()
    {
        GameStateManager.Instance.OnGameStateChanged -= OnGameStateChanged;
    }

    private void Update()
    {
        Vector2 movement = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
        _rigidbody2D.velocity = movement * _speed * Time.deltaTime;
    }

    private void OnGameStateChanged(GameState newGameState)
    {
        enabled = newGameState == GameState.Gameplay;

        if (newGameState == GameState.Pause)
        {
            _rigidbody2D.velocity = Vector2.zero;
        }
    }
}

結論

如果今天是開發中大型專案, 建議使用此方式, 假如是講求速度和功能的呈現, 那用原本的 Time.TimeScale 會比較有效率, 以上個人的小小見解和學習紀錄。

參考來源

comments powered by Disqus