この記事について

一時停止はできるのに、再開ボタンまで反応しなくなった。Godotでは、ゲーム全体をpausedにしたとき、メニュー側にも停止が伝わることがあります。再開用のUIだけは停止中も処理できるように、Process Modeを分けます。

この記事では、横へ動く四角と経過時間を止め、画面の「再開する」ボタンで動きを再開する小さなサンプルを作ります。Godot 4.6.3のGDScriptを使います。外部画像や音声は不要で、項目名は英語表記です。

この記事でわかること

  • SceneTree.pausedで動作を止め、falseで再開する方法
  • 一時停止中だけ動かすメニューにWhen Pausedを設定する方法
  • 停止前・停止中・再開後の経過時間を見比べる確認方法

基本手順

手順1:動く四角と時間表示を用意する

Compatibilityの新規プロジェクトを作成します。Project → Project Settings → Display → WindowでViewport Widthを640、Viewport Heightを480にします。2D Sceneを作り、ルートのNode2DをPauseDemoへ名前変更して、pause_demo.tscnとして保存します。

PauseDemoの子にNode2Dを追加してWorldにします。Worldの子にPolygon2Dを追加してMover、Labelを追加してTimeLabelにします。Moverは画像なしで四角を描画できるノードです。

MoverのInspectorでData → Polygonの配列を開き、Sizeを4にして、要素0を(-24, -24)、要素1を(24, -24)、要素2を(24, 24)、要素3を(-24, 24)にします。Transform → PositionはX=80、Y=260にします。Colorを見やすい水色にします。

TimeLabelのLayout → Transform → PositionをX=40、Y=40、SizeをX=460、Y=40にし、Textへ「経過時間: 0.0秒」を入れます。Theme Overrides → Font Sizes → Font Sizeは24にします。

PauseDemo (Node2D)
└ World (Node2D)
  ├ Mover (Polygon2D)
  └ TimeLabel (Label)

Worldへworld.gdを付け、次のコードを入力します。

extends Node2D

var elapsed: float = 0.0
@onready var mover: Polygon2D = $Mover
@onready var time_label: Label = $TimeLabel

func _process(delta: float) -> void:
    elapsed += delta
    mover.position.x = 80.0 + fmod(elapsed * 100.0, 480.0)
    time_label.text = "経過時間: %.1f秒" % elapsed

elapsedは動作中の経過時間です。Moverは1秒で100ピクセル進み、右側まで来ると左へ戻ります。fmod()で移動距離の余りを求めています。時間表示も同じ_process()内で更新するため、停止したかどうかを数字で確かめられます。

手順1:Worldの下へMoverとTimeLabelを置き、動きと経過時間を用意します。
手順1:Worldの下へMoverとTimeLabelを置き、動きと経過時間を用意します。

手順2:停止ボタンと再開用メニューを作る

PauseDemoの子にCanvasLayerを2つ追加し、HUDとPauseMenuにします。HUDの子へButtonを追加してPauseButtonにします。PauseMenuの子にはPanelを追加し、Panelの子へLabelとButtonを追加してPausedLabel、ResumeButtonに名前変更します。

PauseDemo (Node2D)
├ World (Node2D)
│ ├ Mover (Polygon2D)
│ └ TimeLabel (Label)
├ HUD (CanvasLayer)
│ └ PauseButton (Button)
└ PauseMenu (CanvasLayer)
  └ Panel (Panel)
    ├ PausedLabel (Label)
    └ ResumeButton (Button)

各ControlのLayout → Transformで、次のPositionとSizeを設定します。Panelの子の位置はPanelを基準とした値です。

  • PauseButton:Position (40, 100)、Size (200, 56)、Text「一時停止」
  • Panel:Position (260, 70)、Size (270, 140)
  • PausedLabel:Position (20, 10)、Size (230, 40)、Text「一時停止中」
  • ResumeButton:Position (20, 64)、Size (230, 56)、Text「再開する」

LabelとButtonのFont Sizeを24にします。PauseMenuのCanvasLayer → Layerを2にし、CanvasLayer → Visibleをオフにします。開始時はメニューを隠し、停止したときだけ表示させます。WorldとHUDは標準のProcess Modeのままにしてください。

手順2:停止用HUDと再開用PauseMenuを別の枝に分けます。
手順2:停止用HUDと再開用PauseMenuを別の枝に分けます。

手順3:PauseMenuだけWhen Pausedにする

SceneドックでPauseMenuを選びます。InspectorのNode → Processを開き、ModeをWhen Pausedに変更します。見つからない場合はInspectorの検索欄へ「process」と入力すると絞り込めます。

Panel、PausedLabel、ResumeButtonのProcess ModeはInheritのままにします。これでPauseMenu以下だけが、一時停止中に処理される枝になります。PauseDemo全体をAlwaysへ変更すると、Worldの動きまで継続する可能性があるので、設定するノードを確認してください。

手順3:PauseMenuのProcess ModeをWhen Pausedにし、子はInheritにします。
手順3:PauseMenuのProcess ModeをWhen Pausedにし、子はInheritにします。

When Pausedは「停止中にだけ処理する」、Alwaysは「停止中も通常時も処理する」設定です。今回の再開メニューは通常時には隠れていてよいので、When Pausedが合っています。Visibleをオフにすることと、処理を停止することは別の設定です。

手順4:停止と再開をボタンへ接続する

HUDへhud.gdを付け、次のコードを入力します。$”../PauseMenu”はHUDの親へ1つ戻って、兄弟のPauseMenuを探すパスです。

extends CanvasLayer

@onready var pause_menu: CanvasLayer = $"../PauseMenu"

func _ready() -> void:
    $PauseButton.pressed.connect(_on_pause_pressed)

func _on_pause_pressed() -> void:
    pause_menu.show()
    get_tree().paused = true

「一時停止」を押すとメニューを表示し、get_tree().pausedをtrueにします。次に、PauseMenuへpause_menu.gdを付け、再開処理を書きます。

extends CanvasLayer

func _ready() -> void:
    $Panel/ResumeButton.pressed.connect(_on_resume_pressed)

func _on_resume_pressed() -> void:
    get_tree().paused = false
    hide()

こちらはpausedをfalseに戻してメニューを隠します。今回は_ready()でシグナルを接続しているため、Signalsタブからの接続を重ねないでください。スクリプトを付ける場所も、停止側はHUD、再開側はPauseMenuです。

手順5:実行し、停止中の時間が変わらないことを確かめる

シーンと3つのスクリプトを保存し、Run Current Scene(Windows・Linuxの標準ショートカットはF6)を押します。四角が横へ動き、経過時間が増えることを確認してから「一時停止」をクリックします。

「一時停止中」と「再開する」が表示されたら、表示時刻を覚えて数秒待ちます。停止中は四角の位置と経過時間が変わりません。「再開する」をクリックすると、停止していた時点から時間が増え、四角も動き出します。

手順5:左は停止直後、右は15秒以上待った後です。表示は31.4秒のままで、四角の位置も変わりません。
手順5:左は停止直後、右は15秒以上待った後です。表示は31.4秒のままで、四角の位置も変わりません。
手順5:左は停止中、右は再開ボタンを押した後です。メニューが消え、経過時間が31.9秒へ進んでいます。
手順5:左は停止中、右は再開ボタンを押した後です。メニューが消え、経過時間が31.9秒へ進んでいます。

停止と再開をもう一度繰り返して、2回目も同じように動くか確認します。実行を終了して最初から起動し直すと、経過時間は0から始まり、メニューは隠れた状態です。このサンプルの時間は「実行してからの現実の時間」ではなく、「_process()が動いていた時間」です。

うまくいかないときの確認箇所

停止できるのに、再開ボタンが反応しない

最初にPauseMenuのProcess ModeがWhen Pausedか確認します。Inheritのままだと、停止中に再開ボタンの入力処理も止まってしまいます。ResumeButtonだけを確認するのではなく、その親のPauseMenuまで見てください。

次に、ResumeButtonのpressed接続と、pause_menu.gdがPauseMenuに付いているかを調べます。DebuggerにNode not foundが出る場合は、Panel/ResumeButtonの名前と親子関係を見直してください。

メニューは表示されるが、四角が動き続ける

Worldまたはその親のProcess ModeがAlwaysになっていないか確認します。WorldはInheritのまま、PauseMenuだけWhen Pausedに分けます。また、show()だけでは停止しません。hud.gdにget_tree().paused = trueがあるか確認してください。

再開ボタンを押したのに、メニューだけ消えてゲームが止まったまま

hide()だけが実行されていないか確認します。メニューを隠す処理に加えて、get_tree().paused = falseが必要です。逆にfalseへ戻すだけならゲームは再開しますが、メニューは表示されたままになります。

停止中に動かしたい物理オブジェクトが動かない

Process ModeのAlwaysは、停止中の物理処理まで自動的に復帰させる設定ではありません。今回の四角は座標を更新するだけのサンプルです。物理演算を使うゲームへ応用するときは、UIの継続処理と物理サーバーの停止を分けて考えます。

まとめ

一時停止の基本はpaused = true、再開はpaused = falseです。そのうえで、再開ボタンを含むPauseMenuへWhen Pausedを設定すると、ゲームを止めた状態でもメニューを操作できます。動く対象とメニューの枝を分け、経過時間と実際のクリックで停止・再開を確かめましょう。

参考:Godot 4.6公式ドキュメント:Pausing games and process mode