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
使い方の手順
ここからは、実際に「回転する足場」や「回転トラップ」にこのコンポーネントを組み込む手順を見ていきましょう。
① スクリプトをプロジェクトに追加する
res://components/AutoRotator.gdなど、分かりやすい場所に上記コードを保存します。- Godotエディタでプロジェクトを再読み込みすると、
AutoRotatorがスクリプトクラスとして認識されます。
② 回転させたい親ノードを用意する(例:回転する足場)
2Dゲームで「回転する足場」を作る例です。
RotatingPlatform (StaticBody2D or Node2D) ├── Sprite2D ├── CollisionShape2D └── AutoRotator (Node)
RotatingPlatformは足場本体(StaticBody2DでもNode2DでもOK)Sprite2Dは見た目CollisionShape2Dは当たり判定AutoRotatorは今回のコンポーネント
ポイントは「回転したいのは親(RotatingPlatform)」であり、AutoRotator 自身はただの Node として、その子にぶら下げるだけという構造にしていることです。
③ AutoRotator をアタッチして設定する
- シーンツリーで
RotatingPlatformを右クリック → 「子ノードを追加」 →Nodeを追加 - 追加した
Nodeを選択し、「ノード」タブの「スクリプト」欄からAutoRotator.gdをアタッチ - インスペクタで以下のパラメータを調整
angular_speed_deg: 例)90→ 1秒で90度回転active: 最初から回転させたいならtrueuse_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ライフを楽しんでいきましょう。
