Godotでアクションゲームや物理挙動をデバッグしていると、
- 「この当たり判定、本当にこのフレームで起きてる?」
- 「敵AIのステート遷移、もうちょっとゆっくり見たい」
- 「逆に、長いカットシーンをサクッと2倍速で確認したい」
みたいな場面がよくありますよね。
素直にやろうとすると、
- 各キャラごとに「speed_scale」みたいな変数を持たせて掛け算する
- アニメーションは AnimationPlayer の
playback_speedを個別にいじる - Tween, Timer, Particle…それぞれバラバラに調整
と、「継承+個別対応地獄」にハマりがちです。ノード階層もどんどん深くなり、「このスピードはどこで決まってるんだっけ?」と迷子になるやつですね。
そこで今回は、どのシーンにもペタっと貼るだけで、キー入力でゲーム全体の時間スケールを変えられるデバッグ用コンポーネント、「TimeScaler」を作ってみましょう。
Global の Engine.time_scale をいじるだけなので、既存のプレイヤーや敵のコードを一切いじらずにスローモーション&倍速を実現できます。
【Godot 4】一発で全体スロー&倍速!「TimeScaler」コンポーネント
以下がフルコードです。任意のシーンに Node として追加しておくだけで動きます。
extends Node
class_name TimeScaler
## デバッグ用の時間スケール切り替えコンポーネント
## ・Engine.time_scale をまとめて制御
## ・キー入力で 0.1倍 / 1倍 / 2倍 などに瞬時に変更
## ・本番ビルドでは無効化も可能
## --- 設定パラメータ ---
@export var enabled: bool = true:
## コンポーネント自体を有効/無効にするフラグ
## 本番ビルドで TimeScaler を残したまま無効化したい場合に使います。
set(value):
enabled = value
# 無効化されたら time_scale を元に戻す
if not enabled:
Engine.time_scale = 1.0
@export var slow_scale: float = 0.1:
## スローモーション時の時間倍率
## 0.1 なら 1/10 速度。0.5 なら 1/2 速度。
set(value):
slow_scale = max(value, 0.01) # 0 は危険なので最低値を確保
@export var fast_scale: float = 2.0:
## 早送り時の時間倍率
## 2.0 なら 2倍速、3.0 なら 3倍速など。
set(value):
fast_scale = max(value, 0.01)
@export var normal_scale: float = 1.0:
## 通常時の時間倍率。基本は 1.0 で OK。
set(value):
normal_scale = max(value, 0.01)
@export var toggle_slow_action: StringName = &"debug_slow_motion":
## スローモーション用の Input Action 名
## Project Settings > Input Map で定義して使います。
@export var toggle_fast_action: StringName = &"debug_fast_motion":
## 早送り用の Input Action 名
@export var reset_action: StringName = &"debug_time_normal":
## 通常速度に戻すための Input Action 名
@export var show_debug_label: bool = true:
## 画面左上に現在の time_scale を表示するかどうか
## デバッグ中だけ true にしておくと便利です。
@export var auto_disable_in_release: bool = true:
## エクスポートビルド(release)では自動で無効化するかどうか
## 「デバッグ専用なので本番では絶対に動いてほしくない」場合に true 推奨。
## --- 内部用変数 ---
var _label: Label = null
var _last_time_scale: float = 1.0
func _ready() -> void:
# release ビルドでは自動無効化(オプション)
if auto_disable_in_release and OS.has_feature("release"):
enabled = false
return
_last_time_scale = Engine.time_scale
if show_debug_label:
_create_debug_label()
# 初期状態を反映
_update_debug_label()
func _process(delta: float) -> void:
if not enabled:
return
_handle_input()
# time_scale が外部から変えられた場合もラベルを更新
if Engine.time_scale != _last_time_scale:
_last_time_scale = Engine.time_scale
_update_debug_label()
func _handle_input() -> void:
# 入力アクションが定義されていないときに error が出ないようにチェック
if InputMap.has_action(toggle_slow_action) and Input.is_action_just_pressed(toggle_slow_action):
_set_time_scale(slow_scale)
if InputMap.has_action(toggle_fast_action) and Input.is_action_just_pressed(toggle_fast_action):
_set_time_scale(fast_scale)
if InputMap.has_action(reset_action) and Input.is_action_just_pressed(reset_action):
_set_time_scale(normal_scale)
func _set_time_scale(scale: float) -> void:
scale = clamp(scale, 0.01, 100.0)
Engine.time_scale = scale
_last_time_scale = scale
_update_debug_label()
func _create_debug_label() -> void:
# すでに Label がある場合は再利用
if _label and is_instance_valid(_label):
return
_label = Label.new()
_label.name = "TimeScalerLabel"
_label.text = "time_scale: %.2f" % Engine.time_scale
_label.modulate = Color(1, 1, 0) # 黄色で目立たせる
_label.position = Vector2(8, 8)
_label.z_index = 1024
# 画面に出すために、最上位の CanvasLayer を探す
var root := get_tree().root
var canvas_layer: CanvasLayer = null
for child in root.get_children():
if child is CanvasLayer:
canvas_layer = child
break
if canvas_layer == null:
# なければ自分で CanvasLayer を作る
canvas_layer = CanvasLayer.new()
canvas_layer.name = "TimeScalerCanvasLayer"
root.add_child(canvas_layer)
canvas_layer.add_child(_label)
func _update_debug_label() -> void:
if not show_debug_label:
return
if _label == null or not is_instance_valid(_label):
return
_label.text = "time_scale: %.2f" % Engine.time_scale
## --- 便利関数(任意で呼び出し可能) ---
func set_slow() -> void:
## 外部スクリプトから「スローにしたい」とき用のショートカット
_set_time_scale(slow_scale)
func set_fast() -> void:
## 外部スクリプトから「早送りにしたい」とき用のショートカット
_set_time_scale(fast_scale)
func set_normal() -> void:
## 外部スクリプトから「通常速度に戻したい」とき用のショートカット
_set_time_scale(normal_scale)
使い方の手順
今回は、プレイヤーシーンに TimeScaler をアタッチする例で説明しますが、どのシーンに付けてもOKです(ゲーム全体に効きます)。
① スクリプトをプロジェクトに追加
res://addons/debug/TimeScaler.gdなど、好きな場所に上記スクリプトを保存します。- Godot エディタでスクリプトを開き、エラーが出ていないことを確認します。
② Input Map にデバッグ用アクションを追加
Project > Project Settings… > Input Map で、以下のアクションを追加しましょう(名前はコードと合わせてください):
debug_slow_motion… 例: キーQdebug_fast_motion… 例: キーEdebug_time_normal… 例: キーR
もちろん、コンポーネント側の @export var toggle_***_action を編集すれば、別名のアクションも使えます。
③ シーンに TimeScaler ノードを追加
たとえば 2D のプレイヤーシーンがこんな構成だとします:
Player (CharacterBody2D) ├── Sprite2D ├── CollisionShape2D └── TimeScaler (Node)
- シーンツリーで
Playerを開く。 - 右クリック > Add Child Node… から
Nodeを追加。 - 追加した Node を選択し、インスペクタの「スクリプト」欄から
TimeScaler.gdをアタッチ。 - 名前を
TimeScalerに変更(任意ですが分かりやすいです)。
3D ゲームならたとえばこんな感じ:
GameRoot (Node3D) ├── Player (CharacterBody3D) ├── EnemySpawner (Node3D) ├── UI (CanvasLayer) └── TimeScaler (Node)
どのシーンに置いても Engine.time_scale を触るだけなので、ゲーム全体に時間スケールが効きます。
④ 実行してキー入力を試す
- ゲームを実行すると、左上に
time_scale: 1.00と表示されます。 Q(debug_slow_motion)を押すとtime_scale: 0.10になり、全体がスローモーションに。E(debug_fast_motion)を押すとtime_scale: 2.00になり、全体が倍速に。R(debug_time_normal)を押すとtime_scale: 1.00に戻ります。
物理、アニメーション、Tween、Timer など、Engine.time_scale に従うものはまとめて同じ倍率で動くので、個別に速度を管理する必要がありません。
メリットと応用
この TimeScaler コンポーネントを使うと、デバッグやチューニングがかなり楽になります。
- 継承不要・既存コードを汚さない
各キャラクターに「デバッグ用の速度調整コード」を継承で埋め込む必要がありません。
1つのコンポーネントが Engine.time_scale を握るだけなので、プレイヤーや敵のスクリプトは一切触らずに済みます。 - シーン構造がシンプル
深いノード階層や「DebugPlayer」「DebugEnemy」みたいな派生シーンを作らずに、
TimeScalerを 1 ノード追加するだけで完結します。 - レベルデザインの検証がしやすい
ジャンプの高さや敵の攻撃タイミングなどをスローモーションで確認できるので、
「このフレームで当たってるか?」を目視でチェックしやすくなります。 - 本番ビルドで自動無効化も可能
auto_disable_in_releaseをtrueにしておけば、エクスポートビルドでは自動で機能停止します。
デバッグ用のショートカットが誤ってユーザーに露出する心配も減ります。
さらに、「合成(Composition)」の考え方に乗っかると、TimeScaler を別のコンポーネントから操作するのも簡単です。例えば、ポーズメニューを開いたときに自動でスローにするような改造もできます。
シンプルな改造案として、「一時的にスローにして、数秒後に自動で戻す」関数を追加してみましょう:
func temporary_slow(duration: float = 0.5) -> void:
## 一時的にスローモーションにして、duration 秒後に通常速度へ戻す
## 例: ボスの強攻撃の直前だけ世界をスローにする、などの演出に使える
if not enabled:
return
_set_time_scale(slow_scale)
var timer := get_tree().create_timer(duration, false)
timer.timeout.connect(func():
_set_time_scale(normal_scale))
これを使えば、例えばボスのスクリプト側から:
# どこかのボス攻撃処理
var time_scaler := get_tree().get_first_node_in_group("time_scaler")
if time_scaler and time_scaler is TimeScaler:
time_scaler.temporary_slow(0.8)
のように呼び出して、「強攻撃前だけ世界がスローになる」演出も簡単に合成できます。
(TimeScaler を add_to_group("time_scaler") するなど、グループ管理と組み合わせるとさらにきれいに構成できますね。)
継承で「デバッグ用プレイヤー」「デバッグ用敵」を量産するより、TimeScaler みたいな小さなコンポーネントをシーンにアタッチしていく方が、Godot 4 らしいスッキリした設計になります。ぜひ自分のプロジェクト用にカスタマイズしてみてください。
