Maemaemae

Unityでプロフェッショナルなポーズメニューを実装する方法

ゲームプレイ中に「一時停止」機能を提供することは、現代のゲーム開発において不可欠な要素です。プレイヤーがゲームを中断したり、設定を調整したり、必要に応じて終了したりできるようにすることで、ユーザーエクスペリエンスが大幅に向上します。

この記事では、Unityでプロフェッショナルなポーズメニューを実装する方法を、ステップバイステップで解説します。ESCキーでの一時停止、再開ボタン、終了機能を備えた基本的なポーズシステムの構築から始め、さらに発展的な機能についても触れていきます。

目次

  1. 基本的なポーズ機能の仕組み
  2. 実装手順
  3. コード解説
  4. UIデザインのポイント
  5. 拡張アイデア
  6. よくある問題と解決法
  7. まとめ

基本的なポーズ機能の仕組み

Unityでゲームを一時停止するための基本的なメカニズムは、Time.timeScaleプロパティを操作することです。このプロパティは、ゲーム内時間の進行速度を制御します:

ポーズ状態では、物理演算、アニメーション、時間ベースの処理が停止しますが、入力検出やUIの操作は引き続き機能します。これにより、ポーズメニューのボタンを操作して、ゲームを再開したり終了したりすることが可能になります。

実装手順

1. ポーズメニューUIの作成

まず、ポーズメニュー用のUIを作成します:

  1. Hierarchyウィンドウで右クリック → UI → Canvas を選択してキャンバスを作成
  2. 作成したCanvasの子として、Panel(背景用)を追加
  3. Panelの子として以下のUI要素を追加:
    • Text - "PAUSED"(タイトル)
    • Button - "RESUME"(再開ボタン)
    • Button - "QUIT"(終了ボタン)

ポーズメニューのCanvasは初期状態で非アクティブ(非表示)にしておきます。

2. PauseMenuスクリプトの作成

次に、ポーズ機能を管理する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"); // メインメニューシーンの名前を指定
    }
}

3. スクリプトの適用

  1. 空のGameObjectを作成し、「GameManager」と名前を付ける
  2. 作成したPauseMenuスクリプトをGameManagerにアタッチ
  3. インスペクターで以下の参照を設定:
    • Pause Menu UI: 作成したポーズメニューのパネルをドラッグ&ドロップ
    • Resume Button: 再開ボタンをドラッグ&ドロップ
    • Quit Button: 終了ボタンをドラッグ&ドロップ

コード解説

重要なメソッド

TogglePause()

このメソッドは、現在の状態(ポーズ中か通常プレイ中か)を切り替えます。ESCキーが押されたときに呼び出されます。

PauseGame()

ゲームを一時停止する主要な処理を行います:

ResumeGame()

ゲームを再開する処理を行います:

QuitGame()

ゲームを終了する処理を行います:

UIデザインのポイント

効果的なポーズメニューを設計するためのヒント:

  1. シンプルさを保つ - 必要最小限の機能に絞り、視覚的にもシンプルに
  2. コントラスト - 背景とテキスト/ボタンのコントラストを高くし、視認性を確保
  3. 半透明の背景 - ゲーム画面が少し見えるよう、背景パネルは半透明にすると良い
  4. アニメーション - 表示/非表示時に軽いアニメーション効果を加えるとより洗練された印象に
  5. フォーカス管理 - ポーズメニュー表示時に「再開」ボタンに自動的にフォーカスを設定

拡張アイデア

基本機能を実装したら、以下の機能を追加してポーズメニューを強化することができます:

1. 設定オプション

ポーズメニューに設定パネルを追加し、音量調整、グラフィック設定などのオプションを提供します。

[SerializeField] private Slider volumeSlider;

private void Start()
{
    // 既存のコード...

    // ボリュームスライダーの初期値設定
    volumeSlider.value = AudioListener.volume;
    volumeSlider.onValueChanged.AddListener(SetVolume);
}

private void SetVolume(float volume)
{
    AudioListener.volume = volume;
}

2. 保存オプション

ポーズメニューからゲームの進行状況を保存できる機能も便利です:

public void SaveGame()
{
    // セーブゲーム処理
    Debug.Log("ゲームを保存しました");

    // 任意: 保存確認メッセージを表示
    ShowSaveConfirmation();
}

3. 背景ぼかし効果

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以降のバージョンを前提に作成されています。新しいバージョンでは仕様が変更されている可能性があるため、適宜公式ドキュメントを参照してください。

参考リンク

UnityC#ゲーム開発UI