ゲームプレイ中に「一時停止」機能を提供することは、現代のゲーム開発において不可欠な要素です。プレイヤーがゲームを中断したり、設定を調整したり、必要に応じて終了したりできるようにすることで、ユーザーエクスペリエンスが大幅に向上します。
この記事では、Unityでプロフェッショナルなポーズメニューを実装する方法を、ステップバイステップで解説します。ESCキーでの一時停止、再開ボタン、終了機能を備えた基本的なポーズシステムの構築から始め、さらに発展的な機能についても触れていきます。
Unityでゲームを一時停止するための基本的なメカニズムは、Time.timeScaleプロパティを操作することです。このプロパティは、ゲーム内時間の進行速度を制御します:
Time.timeScale = 1.0f - 通常速度(デフォルト)Time.timeScale = 0.0f - 完全停止(ポーズ状態)Time.timeScale = 0.5f - 半分の速度(スローモーション)ポーズ状態では、物理演算、アニメーション、時間ベースの処理が停止しますが、入力検出やUIの操作は引き続き機能します。これにより、ポーズメニューのボタンを操作して、ゲームを再開したり終了したりすることが可能になります。
まず、ポーズメニュー用のUIを作成します:
ポーズメニューのCanvasは初期状態で非アクティブ(非表示)にしておきます。
次に、ポーズ機能を管理するC#スクリプトを作成します:
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class PauseMenu : MonoBehaviour
{
    [SerializeField] private GameObject pauseMenuUI;  // ポーズメニューのUIパネル
    [SerializeField] private Button resumeButton;     // 再開ボタン
    [SerializeField] private Button quitButton;       // 終了ボタン
    private bool isPaused = false;
    private void Start()
    {
        // パネルを初期状態で非表示に
        pauseMenuUI.SetActive(false);
        // ボタンにリスナーを追加
        resumeButton.onClick.AddListener(ResumeGame);
        quitButton.onClick.AddListener(QuitGame);
    }
    private void Update()
    {
        // ESCキーでポーズ切り替え
        if (Input.GetKeyDown(KeyCode.Escape))
        {
            TogglePause();
        }
    }
    public void TogglePause()
    {
        if (isPaused)
        {
            ResumeGame();
        }
        else
        {
            PauseGame();
        }
    }
    private void PauseGame()
    {
        // ゲーム時間を停止
        Time.timeScale = 0f;
        // UIを表示
        pauseMenuUI.SetActive(true);
        // ポーズ状態を更新
        isPaused = true;
    }
    public void ResumeGame()
    {
        // ゲーム時間を通常に戻す
        Time.timeScale = 1f;
        // UIを非表示
        pauseMenuUI.SetActive(false);
        // ポーズ状態を更新
        isPaused = false;
    }
    public void QuitGame()
    {
        // ゲーム時間を元に戻す
        Time.timeScale = 1f;
        // アプリケーション終了(エディタでは動作しない)
        #if UNITY_EDITOR
            UnityEditor.EditorApplication.isPlaying = false;
        #else
            Application.Quit();
        #endif
    }
    // シーンをロードする機能も追加可能
    public void LoadMainMenu()
    {
        Time.timeScale = 1f;
        SceneManager.LoadScene("MainMenu"); // メインメニューシーンの名前を指定
    }
}
このメソッドは、現在の状態(ポーズ中か通常プレイ中か)を切り替えます。ESCキーが押されたときに呼び出されます。
ゲームを一時停止する主要な処理を行います:
Time.timeScale = 0fでゲーム内時間を停止ゲームを再開する処理を行います:
Time.timeScale = 1fでゲーム内時間を通常に戻すゲームを終了する処理を行います:
効果的なポーズメニューを設計するためのヒント:
基本機能を実装したら、以下の機能を追加してポーズメニューを強化することができます:
ポーズメニューに設定パネルを追加し、音量調整、グラフィック設定などのオプションを提供します。
[SerializeField] private Slider volumeSlider;
private void Start()
{
    // 既存のコード...
    // ボリュームスライダーの初期値設定
    volumeSlider.value = AudioListener.volume;
    volumeSlider.onValueChanged.AddListener(SetVolume);
}
private void SetVolume(float volume)
{
    AudioListener.volume = volume;
}
ポーズメニューからゲームの進行状況を保存できる機能も便利です:
public void SaveGame()
{
    // セーブゲーム処理
    Debug.Log("ゲームを保存しました");
    // 任意: 保存確認メッセージを表示
    ShowSaveConfirmation();
}
UIパネルの背景だけでなく、ポーズ中はゲーム画面全体をぼかす効果を追加すると、没入感が高まります。これはポストプロセッシングエフェクト(Blur)を使用して実現できます。
問題: Time.timeScale = 0fを設定しても、すべてのスクリプト処理が自動的に停止するわけではありません。
解決策1: シングルトンパターンを使用したグローバルなポーズ状態の管理
// PauseManager.cs - シングルトンとして実装
public class PauseManager : MonoBehaviour
{
    public static PauseManager Instance { get; private set; }
    public bool IsPaused { get; private set; }
    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }
    public void SetPauseState(bool paused)
    {
        IsPaused = paused;
        Time.timeScale = paused ? 0f : 1f;
    }
}
// 他のスクリプトでの使用例
public class EnemyController : MonoBehaviour
{
    private void Update()
    {
        // ポーズ中は処理をスキップ
        if (PauseManager.Instance.IsPaused)
            return;
        // 通常の処理
        MoveEnemy();
        CheckCollisions();
    }
}
解決策2: インターフェースを使用した統一的な一時停止機能
// IPausable.cs - 一時停止可能なオブジェクト用インターフェース
public interface IPausable
{
    void OnGamePaused();
    void OnGameResumed();
}
// PauseMenu.cs - ポーズ処理の一部を変更
public class PauseMenu : MonoBehaviour
{
    // 既存のコード...
    private void PauseGame()
    {
        Time.timeScale = 0f;
        pauseMenuUI.SetActive(true);
        isPaused = true;
        // シーン内のすべてのIPausable実装オブジェクトを検索して通知
        IPausable[] pausableObjects = FindObjectsOfType<MonoBehaviour>().OfType<IPausable>().ToArray();
        foreach (IPausable obj in pausableObjects)
        {
            obj.OnGamePaused();
        }
    }
    public void ResumeGame()
    {
        Time.timeScale = 1f;
        pauseMenuUI.SetActive(false);
        isPaused = false;
        // シーン内のすべてのIPausable実装オブジェクトを検索して通知
        IPausable[] pausableObjects = FindObjectsOfType<MonoBehaviour>().OfType<IPausable>().ToArray();
        foreach (IPausable obj in pausableObjects)
        {
            obj.OnGameResumed();
        }
    }
}
// 実装例
public class EnemyAI : MonoBehaviour, IPausable
{
    private bool isPaused = false;
    private void Update()
    {
        if (isPaused)
            return;
        // 通常のAI処理
    }
    public void OnGamePaused()
    {
        isPaused = true;
        // 必要に応じて追加の一時停止処理
    }
    public void OnGameResumed()
    {
        isPaused = false;
        // 必要に応じて追加の再開処理
    }
}
解決策3: イベントシステムを使用した方法
// PauseEvents.cs
public static class PauseEvents
{
    public static event Action OnGamePaused;
    public static event Action OnGameResumed;
    public static void TriggerPause()
    {
        OnGamePaused?.Invoke();
    }
    public static void TriggerResume()
    {
        OnGameResumed?.Invoke();
    }
}
// PauseMenu.cs内で
private void PauseGame()
{
    Time.timeScale = 0f;
    pauseMenuUI.SetActive(true);
    isPaused = true;
    // イベント発行
    PauseEvents.TriggerPause();
}
// 各スクリプトで
private void Awake()
{
    PauseEvents.OnGamePaused += HandleGamePaused;
    PauseEvents.OnGameResumed += HandleGameResumed;
}
private void OnDestroy()
{
    PauseEvents.OnGamePaused -= HandleGamePaused;
    PauseEvents.OnGameResumed -= HandleGameResumed;
}
private void HandleGamePaused()
{
    // 一時停止時の処理
    enabled = false; // Monobehaviourを無効化する方法もある
}
private void HandleGameResumed()
{
    // ゲーム再開時の処理
    enabled = true;
}
問題: Time.timeScale = 0fを設定しても特定のアニメーションが停止しない。
解決策: Animatorコンポーネントで「Update Mode」を「Normal」から「AnimatePhysics」に変更します。これにより、アニメーションがtime scaleの影響を受けるようになります。
問題: ゲームを一時停止してもBGMや効果音が鳴り続ける。
解決策: Time.timeScaleはオーディオには自動的に影響しないため、明示的に一時停止する必要があります:
[SerializeField] private AudioSource[] sourcesToPause;
private void PauseGame()
{
    // 既存のコード...
    // すべてのオーディオソースを一時停止
    foreach (var source in sourcesToPause)
    {
        source.Pause();
    }
}
private void ResumeGame()
{
    // 既存のコード...
    // すべてのオーディオソースを再開
    foreach (var source in sourcesToPause)
    {
        source.UnPause();
    }
}
問題: モバイルゲームではESCキーが使えない。
解決策: UI上に専用のポーズボタンを追加し、それをタップできるようにします:
[SerializeField] private Button pauseButton;
private void Start()
{
    // 既存のコード...
    // ポーズボタンのリスナー追加
    pauseButton.onClick.AddListener(PauseGame);
}
プロフェッショナルなポーズメニューは、ゲームプレイ体験を大きく向上させる重要な要素です。基本的な実装は比較的シンプルですが、細部にこだわることで、ユーザーフレンドリーで直感的なポーズシステムを構築することができます。
この記事で紹介した手法を基本として、自分のゲームに最適なポーズ機能をカスタマイズしてください。ゲームのジャンルや対象プラットフォームによって、ポーズメニューに必要な機能は変わってくるでしょう。プレイヤーの視点に立ち、使いやすさを優先したデザインを心がけることが成功の鍵です。
この記事は、Unity 2022.3以降のバージョンを前提に作成されています。新しいバージョンでは仕様が変更されている可能性があるため、適宜公式ドキュメントを参照してください。