この記事で作るもの
ゲーム画面の「ポーズ」を押すとメニューを開き、「再開」を押すとゲームを続けられる仕組みを作ります。対象はUnity 6・uGUIを使う、1人用のローカルゲームです。キーボード入力を直接読まず、画面のボタンで操作します。
Time.timeScaleはゲーム内の時間倍率です。通常の1から0にすると、時間に合わせて動く処理を止められます。再開時には停止前の値へ戻します。最初から0.5で動いていたゲームを、再開時に勝手に1にしないためです。
1. ボタンとパネルを配置する
まず、最初のUIボタンの手順を参考に、CanvasとEventSystemを用意します。Canvasには有効なGraphic Raycasterが必要です。UIがクリックできる状態になってから次へ進みます。Graphic Raycasterの公式資料
Hierarchyを次の構成にします。PauseControllerはPausePanelの外に置き、常に有効にしておきます。
Main Camera
Cube ← 回転を確認する3Dオブジェクト
PauseController ← ButtonPauseMenu.cs
Canvas ← Screen Space - Overlay
PauseButton ← 表示文字「ポーズ」
PausePanel ← 全画面に広げた半透明のImage
ResumeButton ← 表示文字「再開」
EventSystem
PauseButtonを画面右上に配置します。サイズは幅160・高さ48程度にします。PausePanelのRectTransformを全方向ストレッチにし、上下左右の余白を0にします。Imageの色は半透明の黒にし、Raycast Targetをオンにします。ResumeButtonをPausePanelの中央に配置します。Canvas内ではパネルをポーズボタンより後に置き、手前に表示します。- 下のコードを
ButtonPauseMenu.csとして保存し、PauseControllerへ付けます。 - Inspectorの
Pause Button、Resume Button、Pause Panelへ対応するオブジェクトをドラッグします。
ボタンのOn Click()は空のままにします。コード内でクリックを登録するため、Inspectorでも同じ処理を登録すると二重に呼ばれます。EventSystemの入力モジュールは、プロジェクトで使用する入力方式に合わせてください。
2. 停止と再開のコードを書く
using UnityEngine;
using UnityEngine.UI;
public sealed class ButtonPauseMenu : MonoBehaviour
{
[SerializeField] private Button pauseButton;
[SerializeField] private Button resumeButton;
[SerializeField] private GameObject pausePanel;
private bool isPaused;
private float previousTimeScale = 1f;
private void OnEnable()
{
if (pauseButton == null || resumeButton == null || pausePanel == null)
{
Debug.LogError("ポーズUIの参照を3つ設定してください。", this);
enabled = false;
return;
}
pausePanel.SetActive(false);
pauseButton.interactable = true;
pauseButton.onClick.AddListener(Pause);
resumeButton.onClick.AddListener(Resume);
}
private void OnDisable()
{
if (pauseButton != null) pauseButton.onClick.RemoveListener(Pause);
if (resumeButton != null) resumeButton.onClick.RemoveListener(Resume);
Resume();
}
public void Pause()
{
if (!isActiveAndEnabled || isPaused) return;
previousTimeScale = Time.timeScale;
isPaused = true;
Time.timeScale = 0f;
pauseButton.interactable = false;
pausePanel.SetActive(true);
}
public void Resume()
{
if (isPaused)
{
Time.timeScale = previousTimeScale;
isPaused = false;
}
if (pausePanel != null) pausePanel.SetActive(false);
if (pauseButton != null) pauseButton.interactable = true;
}
}
isPausedで二重停止を防ぎます。停止済みの状態で再びpreviousTimeScaleに0を保存してしまうと、再開しても動かなくなるからです。OnDisable()でも再開処理を呼び、ポーズ中に管理オブジェクトを無効にした場合の解除漏れを防ぎます。
参照の確認とクリック登録はOnEnable()で行います。参照が足りない場合はエラーを出して自分を無効にし、登録処理へ進みません。参照を直して再有効化したときにも、同じ確認が働きます。OnEnableの公式資料
この教材では、時間倍率を変更する管理役をこのコンポーネント1つに限定します。別のポーズ機能やスローモーション機能も同時に時間倍率を変更する場合は、共通の管理役へまとめてから組み合わせてください。
3. 止まるオブジェクトを用意する
空の3DシーンならCubeを作り、Main Cameraから見える位置に置きます。次のコードを別ファイルPauseDemoSpinner.csに保存し、Cubeへ付けます。
using UnityEngine;
public sealed class PauseDemoSpinner : MonoBehaviour
{
private void Update()
{
transform.Rotate(0f, 90f * Time.deltaTime, 0f);
}
}
この回転はTime.deltaTimeを掛けているため、ポーズ中は回転量が0になります。Update()自体の呼び出しを止めているわけではありません。
4. 通常・ポーズ・再開を確認する
| 操作 | 期待する結果 |
|---|---|
| Playを開始する | Cubeが回転し、ポーズパネルは非表示 |
| 「ポーズ」を押す | Cubeが止まり、パネルと再開ボタンが表示される |
| 「再開」を押す | パネルが閉じ、Cubeが再び回転する |
| 停止と再開を数回繰り返す | 毎回再開でき、クリックの登録が増えない |
| ポーズ中にPauseControllerを無効にする | 保存した時間倍率へ戻る |
| PauseControllerを再び有効にする | パネルは閉じたまま、ポーズボタンを再び使える |
timeScaleだけでは止まらないもの
timeScale = 0ではFixedUpdate()やWaitForSecondsの待機が進まなくなります。一方、Time.unscaledDeltaTimeで進める処理は時間倍率の影響を受けません。停止中にも動くメニュー演出には、非スケール時間を使う方法があります。Time.timeScaleの公式資料、Time.unscaledDeltaTimeの公式資料
このコードはBGMの停止や、プレイヤー入力の一括無効化までは行いません。移動コードが毎フレーム固定距離を加算している場合や、時間と無関係に攻撃を発生させる場合は、別途ポーズ状態を確認する必要があります。オンラインゲーム全体の停止にも使えません。
うまく動かないとき
- 再開ボタンが押せない:CanvasのGraphic Raycaster、EventSystem、入力モジュール、UIの重なりを確認します。Panelの子にResumeButtonがあることも確認します。
- 参照未設定のエラーで止まった:3つの参照を登録し、ButtonPauseMenuコンポーネントを有効にし直します。編集時に直した場合はPlayもやり直します。
- Play開始直後に管理役まで消える:PauseControllerをPausePanelの子にしていないか確認します。
- Cubeが止まらない:回転コードに
Time.deltaTimeが掛かっているか確認します。 - 再開しても動かない:Consoleのエラーと、別のスクリプトによる
Time.timeScaleの変更を確認します。
UIの受付状態をさらに学ぶなら、購入ボタンの連打防止へ進めます。
この記事のまとめ
Unity 6とuGUIでポーズ・再開ボタンを実装します。UIの配置、停止前のtimeScaleの保存、解除漏れへの対処、停止中も動く処理の違いをコードと図で確認します。