Godotで「回転する足場」や「クルクル回るトラップ」を作るとき、つい親ノードのスクリプトに直接ロジックを書いてしまいがちですよね。
最初はそれでも動きますが、

  • プレイヤー、敵、ギミックごとに毎回「回転処理」をコピペ
  • 一部だけ回転速度を変えたいときに、各スクリプトを開いて書き換え
  • 回転処理と当たり判定、アニメーション処理などが1ファイルに混ざってカオス

……と、だんだんスクリプトが肥大化していきます。
Godot標準のやり方だと「回転する足場クラス」を継承して、さらにそれを別のクラスが継承して……と、継承のツリーもどんどん深くなりがちです。

そこで今回は、「回転」という振る舞いだけを独立させたコンポーネント AutoRotator を用意して、
足場でも敵でもオブジェクトでも「必要なノードにアタッチするだけ」で自動回転させるスタイルを目指してみましょう。

【Godot 4】置くだけでクルクル回転!「AutoRotator」コンポーネント

この AutoRotator コンポーネントは、

  • 自分の親ノードを、指定した角速度で回転させ続ける
  • 回転方向(時計回り / 反時計回り)を簡単に切り替え
  • ゲーム中に一時停止・再開ができる

といった、シンプルだけど汎用性の高い機能を提供します。
「継承ベースで RotatingPlatform クラスを増やす」のではなく、
「どんなノードにも AutoRotator をポン付けして回転させる」という、合成(Composition)スタイルの実装ですね。


フルコード:AutoRotator.gd


extends Node
class_name AutoRotator
## 親ノードを自動で回転させるコンポーネント。
## どんな2D/3Dノードにもアタッチして使えます。
##
## 想定用途:
## - 回転する足場 (StaticBody2D / Node2D / Node3D)
## - ぐるぐる回るトラップ (Spikes, Saw, etc.)
## - 回転するコインやアイテム
## - メニュー画面の飾りオブジェクト など

@export_group("Rotation Settings")
@export var angular_speed_deg: float = 90.0:
	set(value):
		angular_speed_deg = value
		_angular_speed_rad = deg_to_rad(value)
## 1秒あたりの回転速度(度)。正で反時計回り、負で時計回りに回転します。

@export var active: bool = true
## true のときだけ回転します。ゲーム中にオン/オフ可能。

@export var use_local_rotation: bool = true
## true: ローカル回転(親ノード自身の rotation/rotation_degrees を変更)
## false: グローバル回転(global_rotation / global_rotation_degrees を変更)
## ※3Dノードの場合は Y軸回転を行います。

@export_group("Debug")
@export var print_warning_if_no_parent: bool = true
## 親が存在しないときに警告を出すかどうか。

var _parent_node: Node = null
var _angular_speed_rad: float

func _ready() -> void:
	# 内部用のラジアン値を初期化
	_angular_speed_rad = deg_to_rad(angular_speed_deg)

	# 親ノードをキャッシュ
	_parent_node = get_parent()
	if _parent_node == null:
		if print_warning_if_no_parent:
			push_warning("AutoRotator: 親ノードが存在しません。このコンポーネントは親を回転させる前提です。")
		return

	# 親ノードの型によって、2D/3D どちらの回転APIを使うかを判断します。
	if not _supports_rotation(_parent_node):
		push_warning("AutoRotator: 親ノード '%s' は rotation プロパティをサポートしていません。" % _parent_node.name)
		_parent_node = null


func _process(delta: float) -> void:
	if not active:
		return

	if _parent_node == null:
		return

	# 1フレームあたりの回転量(ラジアン)を計算
	var delta_angle: float = _angular_speed_rad * delta

	# 親ノードが 2D か 3D かで処理を分ける
	if _parent_node is Node2D:
		_rotate_2d(_parent_node as Node2D, delta_angle)
	elif _parent_node is Node3D:
		_rotate_3d(_parent_node as Node3D, delta_angle)
	else:
		# rotation を持たないノードは何もしない
		pass


func _supports_rotation(node: Node) -> bool:
	# Node2D / Node3D なら rotation 系プロパティを持っているのでOK
	return node is Node2D or node is Node3D


func _rotate_2d(node: Node2D, delta_angle: float) -> void:
	if use_local_rotation:
		# ローカル回転(親自身の rotation)
		node.rotation += delta_angle
	else:
		# グローバル回転
		node.global_rotation += delta_angle


func _rotate_3d(node: Node3D, delta_angle: float) -> void:
	# 3Dでは、Y軸まわりに回転させるのが一般的なので Y軸固定で回転させます。
	if use_local_rotation:
		node.rotate_y(delta_angle)
	else:
		# グローバル回転をシンプルに扱うため、Basis を直接操作します。
		var basis := node.global_transform.basis
		basis = Basis(Vector3.UP, delta_angle) * basis
		var t := node.global_transform
		t.basis = basis.orthonormalized()
		node.global_transform = t


## --- 公開API: スクリプトから制御したいときに使うメソッド群 ---

func set_active(enabled: bool) -> void:
	## 回転のオン/オフを切り替えます。
	active = enabled


func toggle_active() -> void:
	## 回転のオン/オフをトグルします。
	active = not active


func set_speed_deg_per_sec(deg_per_sec: float) -> void:
	## 回転速度を(度/秒)で設定します。
	angular_speed_deg = deg_per_sec


func reverse_direction() -> void:
	## 回転方向を反転させます。
	angular_speed_deg = -angular_speed_deg

使い方の手順

ここからは、実際に「回転する足場」や「回転トラップ」にこのコンポーネントを組み込む手順を見ていきましょう。

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

  1. res://components/AutoRotator.gd など、分かりやすい場所に上記コードを保存します。
  2. Godotエディタでプロジェクトを再読み込みすると、AutoRotator がスクリプトクラスとして認識されます。

② 回転させたい親ノードを用意する(例:回転する足場)

2Dゲームで「回転する足場」を作る例です。

RotatingPlatform (StaticBody2D or Node2D)
 ├── Sprite2D
 ├── CollisionShape2D
 └── AutoRotator (Node)
  • RotatingPlatform は足場本体(StaticBody2D でも Node2D でもOK)
  • Sprite2D は見た目
  • CollisionShape2D は当たり判定
  • AutoRotator は今回のコンポーネント

ポイントは「回転したいのは親(RotatingPlatform)」であり、
AutoRotator 自身はただの Node として、その子にぶら下げるだけという構造にしていることです。

③ AutoRotator をアタッチして設定する

  1. シーンツリーで RotatingPlatform を右クリック → 「子ノードを追加」 → Node を追加
  2. 追加した Node を選択し、「ノード」タブの「スクリプト」欄から AutoRotator.gd をアタッチ
  3. インスペクタで以下のパラメータを調整
    • angular_speed_deg: 例) 90 → 1秒で90度回転
    • active: 最初から回転させたいなら true
    • use_local_rotation: 通常は true のままでOK

これだけで、ゲームを再生すると足場がクルクル回転し始めます。

④ 他のオブジェクトにも使い回す(敵・トラップ・アイテムなど)

コンポーネント指向の美味しいところは「とにかく再利用が楽」という点です。
同じ AutoRotator を、例えば以下のようなシーンにもそのまま使い回せます。

回転するトゲトラップ:

SpikeTrap (Area2D)
 ├── Sprite2D
 ├── CollisionShape2D
 └── AutoRotator (Node)

回転するコイン:

Coin (Area2D)
 ├── Sprite2D
 ├── CollisionShape2D
 └── AutoRotator (Node)

3Dゲームの回転する足場:

RotatingPlatform3D (StaticBody3D or Node3D)
 ├── MeshInstance3D
 ├── CollisionShape3D
 └── AutoRotator (Node)

いずれも「親ノードを回転させたい」というニーズは同じなので、
共通の AutoRotator コンポーネントをアタッチするだけでOKです。


メリットと応用

AutoRotator コンポーネントを使うと、次のようなメリットがあります。

  • シーン構造がシンプル
    「回転する足場クラス」「回転するトゲクラス」などを継承で増やさずに済みます。
    ベースはただの StaticBody2D / Area2D で、回転だけをコンポーネントに任せられます。
  • レベルデザインが楽
    ステージエディタで足場やトラップをポンポン配置し、
    回転させたいものだけに AutoRotator を追加&パラメータをちょっと変えるだけでバリエーションが増やせます。
  • スクリプトの責務が分離される
    足場のスクリプトは「プレイヤーとの接触判定」などに集中でき、
    「回転ロジック」は AutoRotator 側に閉じ込められます。
  • テストしやすい
    回転の挙動にバグがあれば AutoRotator だけを見ればよく、
    他のロジックとごちゃ混ぜにならないのでデバッグが楽です。

また、応用としては、

  • ゲーム中にスイッチを押したら回転開始 / 停止
  • 一定時間ごとに回転方向を切り替える
  • 回転速度を徐々に上げていくトラップ

といったギミックも簡単に作れます。

改造案:一定間隔で回転方向を反転させる

例えば、「3秒ごとに回転方向が逆になる足場」を作りたい場合、
AutoRotator に次のようなメソッドを追加しても面白いです。


@export_group("Auto Reverse")
@export var auto_reverse: bool = false
## true のとき、一定間隔で自動的に回転方向を反転させます。

@export var reverse_interval_sec: float = 3.0
## 方向を反転させる間隔(秒)

var _reverse_timer: float = 0.0

func _process(delta: float) -> void:
	if not active:
		return

	if _parent_node == null:
		return

	# 方向反転のタイマー処理
	if auto_reverse:
		_reverse_timer += delta
		if _reverse_timer >= reverse_interval_sec:
			_reverse_timer = 0.0
			reverse_direction()

	var delta_angle: float = _angular_speed_rad * delta

	if _parent_node is Node2D:
		_rotate_2d(_parent_node as Node2D, delta_angle)
	elif _parent_node is Node3D:
		_rotate_3d(_parent_node as Node3D, delta_angle)

こうして少しずつコンポーネントを育てていくと、
プロジェクト全体が「再利用可能な小さな部品の集合」になっていきます。
継承地獄から抜け出して、合成指向のGodotライフを楽しんでいきましょう。