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です(ゲーム全体に効きます)。

① スクリプトをプロジェクトに追加

  1. res://addons/debug/TimeScaler.gd など、好きな場所に上記スクリプトを保存します。
  2. Godot エディタでスクリプトを開き、エラーが出ていないことを確認します。

② Input Map にデバッグ用アクションを追加

Project > Project Settings… > Input Map で、以下のアクションを追加しましょう(名前はコードと合わせてください):

  • debug_slow_motion … 例: キー Q
  • debug_fast_motion … 例: キー E
  • debug_time_normal … 例: キー R

もちろん、コンポーネント側の @export var toggle_***_action を編集すれば、別名のアクションも使えます。

③ シーンに TimeScaler ノードを追加

たとえば 2D のプレイヤーシーンがこんな構成だとします:

Player (CharacterBody2D)
 ├── Sprite2D
 ├── CollisionShape2D
 └── TimeScaler (Node)
  1. シーンツリーで Player を開く。
  2. 右クリック > Add Child Node… から Node を追加。
  3. 追加した Node を選択し、インスペクタの「スクリプト」欄から TimeScaler.gd をアタッチ。
  4. 名前を TimeScaler に変更(任意ですが分かりやすいです)。

3D ゲームならたとえばこんな感じ:

GameRoot (Node3D)
 ├── Player (CharacterBody3D)
 ├── EnemySpawner (Node3D)
 ├── UI (CanvasLayer)
 └── TimeScaler (Node)

どのシーンに置いても Engine.time_scale を触るだけなので、ゲーム全体に時間スケールが効きます

④ 実行してキー入力を試す

  • ゲームを実行すると、左上に time_scale: 1.00 と表示されます。
  • Qdebug_slow_motion)を押すと time_scale: 0.10 になり、全体がスローモーションに。
  • Edebug_fast_motion)を押すと time_scale: 2.00 になり、全体が倍速に。
  • Rdebug_time_normal)を押すと time_scale: 1.00 に戻ります。

物理、アニメーション、Tween、Timer など、Engine.time_scale に従うものはまとめて同じ倍率で動くので、個別に速度を管理する必要がありません。

メリットと応用

この TimeScaler コンポーネントを使うと、デバッグやチューニングがかなり楽になります。

  • 継承不要・既存コードを汚さない
    各キャラクターに「デバッグ用の速度調整コード」を継承で埋め込む必要がありません。
    1つのコンポーネントが Engine.time_scale を握るだけなので、プレイヤーや敵のスクリプトは一切触らずに済みます。
  • シーン構造がシンプル
    深いノード階層や「DebugPlayer」「DebugEnemy」みたいな派生シーンを作らずに、
    TimeScaler を 1 ノード追加するだけで完結します。
  • レベルデザインの検証がしやすい
    ジャンプの高さや敵の攻撃タイミングなどをスローモーションで確認できるので、
    「このフレームで当たってるか?」を目視でチェックしやすくなります。
  • 本番ビルドで自動無効化も可能
    auto_disable_in_releasetrue にしておけば、エクスポートビルドでは自動で機能停止します。
    デバッグ用のショートカットが誤ってユーザーに露出する心配も減ります。

さらに、「合成(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 らしいスッキリした設計になります。ぜひ自分のプロジェクト用にカスタマイズしてみてください。