この記事について

ボタンを押してから数秒後に処理したいときは、Timerノードを使うと待ち時間と完了処理を分けて書けます。この記事では3秒カウントダウンを作り、残り時間の表示、途中からの再開、最初からのやり直しを試します。

Godot 4.6.3のGDScriptを使い、エディターの項目名は英語表記で説明します。画面全体を停止するポーズ機能ではなく、Timerだけの一時停止です。

この記事でわかること

  • TimerのWait TimeとOne Shotを設定する方法
  • timeoutシグナルで待ち時間後の処理を実行する方法
  • 残り時間を保った再開と、最初からのやり直しの違い

基本手順

完成サンプルはこちらのGodotプロジェクトZIPから開けます。ZIPを展開し、Project ManagerのImportでproject.godotを選んでください。自分で新規作成する場合は、レンダラーをCompatibilityにして進めます。

手順1:画面とTimerを作る

Scene → New Scene → User InterfaceからControlを作り、TimerUIへ名前変更してmain.tscnに保存します。TimerUIの子へVBoxContainerを追加してContent、別の子へTimerを追加してCountdownTimerと名付けます。TimerをContentの中に入れず、次の構成にそろえてください。

TimerUI (Control)
├ Content (VBoxContainer)
│ ├ TitleLabel (Label)
│ ├ StatusLabel (Label)
│ ├ StartButton (Button)
│ └ PauseButton (Button)
└ CountdownTimer (Timer)

ContentのLayout → TransformでPositionをX=40、Y=40、SizeのXを640にします。Separationは16、文字のFont Sizeは24、ボタンのCustom Minimum SizeのYは48です。TitleLabelは「3秒カウントダウン」、StatusLabelは「開始ボタンを押してください」、StartButtonは「開始 / 3秒からやり直す」、PauseButtonは「一時停止」とします。PauseButtonのDisabledをオンにします。

手順1:TimerはContentの外に置き、CountdownTimerと名付けます。
手順1:TimerはContentの外に置き、CountdownTimerと名付けます。

手順2:待ち時間を3秒にし、1回だけ動かす

CountdownTimerを選び、InspectorのWait Timeを3、One Shotをオン、Autostartをオフにします。One Shotは時間切れで止める設定、Autostartはシーンへ入った時に自動で開始する設定です。今回はボタンで開始するためAutostartを使いません。

手順2:Wait Timeは3、One Shotはオン、Autostartはオフです。
手順2:Wait Timeは3、One Shotはオン、Autostartはオフです。

手順3:開始・一時停止・完了のコードを書く

TimerUIにres://main.gdをAttach Scriptで付け、Templateのチェックを外して作成します。次の内容を入力してください。ButtonのpressedとTimerのtimeoutはコードで接続するため、Signalsタブでは接続しません。

extends Control

@onready var timer: Timer = $CountdownTimer
@onready var status_label: Label = $Content/StatusLabel
@onready var pause_button: Button = $Content/PauseButton
var completed: int = 0

func _ready() -> void:
	$Content/StartButton.pressed.connect(_start_countdown)
	pause_button.pressed.connect(_toggle_pause)
	timer.timeout.connect(_on_timeout)

func _process(_delta: float) -> void:
	if not timer.is_stopped():
		var state := "一時停止" if timer.paused else "カウント中"
		status_label.text = "%s: 残り %.1f 秒" % [state, timer.time_left]

func _start_countdown() -> void:
	timer.paused = false
	timer.start()
	pause_button.disabled = false
	pause_button.text = "一時停止"

func _toggle_pause() -> void:
	timer.paused = not timer.paused
	pause_button.text = "再開" if timer.paused else "一時停止"

func _on_timeout() -> void:
	completed += 1
	status_label.text = "完了! timeout: %d 回" % completed
	pause_button.disabled = true
	pause_button.text = "一時停止"

_process()は実行中のtime_leftを表示するだけです。完了回数を増やす処理は_on_timeout()にまとめています。_start_countdown()はpausedを解除してstart()を呼びます。_toggle_pause()はpausedを切り替え、残り時間を保持します。

手順3:timeoutは完了処理へ接続し、start()の前でpausedを解除します。
手順3:timeoutは完了処理へ接続し、start()の前でpausedを解除します。

手順4:開始から完了までを確かめる

保存してF6で実行し、「開始 / 3秒からやり直す」をクリックします。残り時間が減り、約3秒後に「完了! timeout: 1 回」と表示されれば成功です。そのままさらに待っても回数は増えません。もう一度開始すると、次の完了時には2回になります。

独立したウィンドウで試す場合は、保存後にProject → Quit to Project Listへ戻り、Project Managerでこのプロジェクトを選んでRunを押します。独立実行を終えるときは、その実行ウィンドウを閉じます。

表示は小数第1位へ丸めているため、終了直前に「残り0.0秒」と見えることがあります。表示の丸めと完了通知は別です。完了の判定には文字や丸めた数値ではなくtimeoutを使います。

手順4:開始後に残り時間が減り、完了すると1回と表示されます。
手順4:開始後に残り時間が減り、完了すると1回と表示されます。

手順5:途中再開とやり直しを比べる

開始後、残り約2秒で「一時停止」をクリックします。残り時間が止まり、ボタンが「再開」へ変わります。数秒待って「再開」を押すと、止めた残り時間から動きます。

次にカウント中、または一時停止中に「開始 / 3秒からやり直す」を押します。今度は3秒へ戻って動きます。start()は残り時間の再開ではなく、カウントをリセットする操作です。一時停止中のTimerへstart()だけ呼んでもpausedは解除されないため、このコードでは先にfalseへ戻しています。

手順5:一時停止では残り時間を保持し、開始ボタンでは3秒からやり直します。
手順5:一時停止では残り時間を保持し、開始ボタンでは3秒からやり直します。

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

3秒ごとに何度も完了する

CountdownTimerのOne Shotを確認します。オフなら時間切れの後も繰り返すため、今回のサンプルではオンにしてください。コードとSignalsタブで同じ関数へ重複接続していないかも確認します。

実行直後に勝手に始まる

Autostartがオンになっていないか確認します。Autostartはノードがシーンツリーへ入ったときに動作し、実行時にはフラグが自動でfalseへ戻ります。Remote側でfalseになっていても、ローカルの保存済み設定がオフとは限りません。

一時停止後に開始ボタンを押しても動かない

timer.paused = falseがstart()より前にあるか確認します。またSceneTree全体をpausedにしている場合は、TimerのProcess Modeによって処理が止まります。このサンプルではSceneTree.pausedを使いません。

正確に3秒で表示が切り替わらない

Timerはフレーム更新に合わせて進むため、処理が重い環境では表示や通知に遅れが生じます。Engine.time_scaleを変更している場合も待ち時間へ影響します。実時間の精密な計測器として使うのではなく、ゲーム内の待ち時間を扱うものとして考えてください。stop()で途中停止してもtimeoutは発生しません。

まとめ

Timerの基本は待ち時間を設定し、start()で開始し、timeoutで完了処理を行うことです。1回だけならOne Shotをオンにします。pausedの切り替えは残り時間を保ち、start()は最初からやり直すという違いを押さえると、用途に合わせて操作を作れます。