この記事で作るもの
「開始/やり直し」ボタンを押すと5秒から数え、最後に「終了」と表示する画面を作ります。途中でもう一度押すと、残り時間を5秒へ戻します。対象はGodot 4.5・GDScript・2DのUIです。
時間の計測はTimerノードへ任せ、Labelはtime_leftを読み取って表示します。_process()で自前の残り時間を減らす方式とは分けて考えます。Timerの公式資料
1. ノードと画面を作る
新しいシーンのルートをControlにしてCountdownDemoと名付け、countdown_demo.tscnへ保存します。ルートのレイアウトをFull Rectにし、次の親子関係を作ります。
CountdownDemo (Control) ← CountdownDemo.gd
Timer
MarginContainer
VBoxContainer
TimeLabel (Label)
RestartButton (Button)
MarginContainerのアンカーは左上のまま、Positionを(24, 24)、Sizeを(360, 160)にします。その子のVBoxContainerとUIの位置はコンテナが調整するので、手で座標をずらしません。
| 対象 | 設定 |
|---|---|
| Timer | Wait Time 5.0、One Shotオン、Autostartオフ |
| Timer | Pausedオフ、Ignore Time Scaleオフ、Process CallbackはIdle |
| TimeLabel | Text 未開始、文字サイズの例は28 |
| RestartButton | Text 開始/やり直し、Custom Minimum SizeのYを48 |
| VBoxContainer | Theme OverridesのConstants > Separationを16 |
文字サイズはTimeLabelのTheme Overrides > Font Sizes > Font Sizeで変えられます。この例は標準のEngine.time_scale = 1.0で試し、シーンの一時停止は使いません。
2. シグナルをコードで接続する
ルートへCountdownDemo.gdを付け、次のコードを保存します。エディターのシグナル一覧からの追加接続は行いません。pressedとtimeoutはコードから接続します。
extends Control
const DURATION_SECONDS: float = 5.0
@onready var timer: Timer = $Timer
@onready var time_label: Label = $MarginContainer/VBoxContainer/TimeLabel
@onready var restart_button: Button = $MarginContainer/VBoxContainer/RestartButton
func _ready() -> void:
timer.stop()
timer.one_shot = true
restart_button.pressed.connect(_start_countdown)
timer.timeout.connect(_on_timeout)
time_label.text = "未開始"
func _start_countdown() -> void:
timer.start(DURATION_SECONDS)
_refresh_time_label()
func _process(_delta: float) -> void:
if not timer.is_stopped():
_refresh_time_label()
func _refresh_time_label() -> void:
time_label.text = "残り: %d秒" % ceili(timer.time_left)
func _on_timeout() -> void:
time_label.text = "終了"
@onreadyは、ノードの準備ができた段階で子ノードへの参照を取得するために使います。$の後ろはルートから見たノードのパスなので、名前と階層をそろえます。シグナルの考え方を先に確認したい場合はSignalの基本も参照できます。
3. 再開始・終了・表示を区別する
timer.start(5.0)は、停止中なら開始し、動作中なら5秒にリセットします。残り3秒から押しても8秒にはなりません。 この例は一時停止からの再開を扱うものではなく、毎回最初の5秒へ戻します。
one_shot = trueなので、時間切れでtimeoutを1回通知して停止します。コードでも同じ設定にして、繰り返し動作を防いでいます。_process()は停止中に表示を上書きしないため、「終了」がそのまま残ります。Timerの開始・停止と各プロパティ
ceili()は小数を切り上げて整数にする関数です。たとえば残り4.2秒は5秒と表示します。表示だけを丸め、Timer自身の計測値は変えていません。ceiliの公式資料
動作確認
このシーンを開き、F6で現在のシーンを実行します。
| 操作 | 期待する結果 |
|---|---|
| シーン開始直後 | 未開始のままで、勝手に減らない |
| ボタンを押す | 残り: 5秒から表示が減る |
| 残り3秒程度で押し直す | 5秒へ戻り、そこから数え直す |
| 押し直した後、ボタンを押さずに待つ | 約5秒後に終了になる |
| 終了後さらに待つ | 自動で次のカウントを始めない |
| 終了後にボタンを押す | 再び5秒から始まる |
画面表示の更新はフレームごとです。実時間の厳密な計測や、ネットワーク上の全端末で同じ締め切りを扱う例ではありません。
うまく動かないとき
- ノードが見つからない:
Timer、MarginContainer、VBoxContainer、TimeLabel、RestartButtonの大文字・小文字と階層を確認します。 - 画面にボタンがない:Controlをルートにし、MarginContainerの位置・サイズ、ButtonのVisibleを確認します。
- 最初から数え始める:TimerのAutostartや、別スクリプトの
start()、重複したシグナル接続を確認します。 - 押しても時間が進まない:TimerのPaused、シーンの一時停止、
Engine.time_scaleを確認します。start()はPausedを自動で解除しません。 - 秒数の減り方が想像と違う:表示は切り上げです。また、Ignore Time ScaleがオフならTimerは
Engine.time_scaleの影響を受けます。
まとめ
計測はTimer、残り秒数はtime_left、終了の通知はtimeoutへ分けます。途中のstart(5.0)は残り時間への加算ではなく、5秒からのやり直しです。シーン移動へつなぐ場合はボタンによるシーン切り替えへ進めます。
この記事のまとめ
Godot 4.5のTimerで残り秒数を表示し、ボタンで5秒へ戻す例です。one_shot、time_left、timeout、表示用の切り上げを図とコードで整理します。
