Godot 4 でカメラを「いい感じに揺らしたい」とき、つい Camera2D を継承したり、シーン階層の中に「揺れ専用ノード」を挟んだりしがちですよね。
でもそれをやり始めると…
- 「プレイヤー用のカメラ継承」「ボス戦用のカメラ継承」みたいにクラスが増える
- カメラの子にさらに
Node2Dを挟んで、そのpositionをいじる…など、ツリーがどんどん深くなる - 「このシーンのカメラだけ揺れない/揺れすぎる」など、シーンごとに調整がバラバラ
といった「ありがちな沼」にハマりがちです。
そこでこの記事では、カメラ本体は素の Camera2D のままにしておき、「揺れ」という機能だけをコンポーネントとして後付けするアプローチを紹介します。
今回作る CameraShaker コンポーネントは、外部からメソッドを呼ぶだけで、親ノードの offset をノイズ関数で激しく揺らすシンプルな仕組みです。
【Godot 4】ノイズでガッツリ揺らす!「CameraShaker」コンポーネント
今回は 2D カメラ(Camera2D)を想定していますが、offset を持っているノードなら基本的に何にでも付けられます。
「揺れ」という機能を 継承ではなく合成(Composition) で実現していきましょう。
フルコード(GDScript / Godot 4)
extends Node
class_name CameraShaker
# 親ノードの "offset" プロパティをノイズで揺らすコンポーネント。
# 典型的には Camera2D の子として配置して使います。
@export_category("Shake Settings")
## 揺れの強さ(ピクセル単位の最大振幅)
@export var amplitude: float = 16.0
## 揺れの長さ(秒)
@export var duration: float = 0.4
## 時間経過による揺れの減衰率(0.0~1.0)
## 1.0 = 減衰なし, 0.0 = 即終了 に近い
@export_range(0.0, 1.0, 0.01) var damping: float = 0.8
## ノイズの細かさ。値が大きいほど「ブルブル」細かく揺れる
@export var noise_frequency: float = 25.0
## 揺れのシード値。複数シェイカーでパターンを変えたいとき用
@export var noise_seed: int = 12345
@export_category("Target")
## offset を操作する対象。未設定なら親ノードを自動で対象にします。
## 通常は Camera2D を指定。
@export var target_node: NodePath
## 揺れ終了後に offset をリセットするかどうか
@export var reset_offset_on_finish: bool = true
# 内部状態
var _time: float = 0.0
var _is_shaking: bool = false
var _initial_offset: Vector2 = Vector2.ZERO
var _noise: FastNoiseLite
var _target: Object
func _ready() -> void:
# 対象ノードの決定。未設定なら親ノードを対象にする。
if target_node.is_empty():
_target = get_parent()
else:
_target = get_node(target_node)
if _target == null:
push_warning("CameraShaker: target_node が見つかりません。親ノードに offset プロパティを持つノードを配置してください。")
return
# offset プロパティを持つか軽くチェック(Camera2D, Node2D など)
if not _target.has_method("get") or not _target.has_method("set"):
push_warning("CameraShaker: 対象ノードが get/set をサポートしていません。offset を持つ Camera2D や Node2D を指定してください。")
return
if not _target.has_property("offset"):
push_warning("CameraShaker: 対象ノードが offset プロパティを持っていません。Camera2D などを対象にしてください。")
return
# 初期 offset を保存
_initial_offset = _target.offset
# ノイズの初期化
_noise = FastNoiseLite.new()
_noise.seed = noise_seed
_noise.frequency = noise_frequency
_noise.noise_type = FastNoiseLite.TYPE_SIMPLEX
func _process(delta: float) -> void:
if not _is_shaking:
return
_time += delta
# 0.0 ~ 1.0 の正規化時間
var t := _time / max(duration, 0.0001)
if t >= 1.0:
# 揺れ終了
_is_shaking = false
if reset_offset_on_finish and _target:
_target.offset = _initial_offset
return
# 減衰係数を計算(時間と damping を使った簡単な指数減衰)
var attenuation := pow(damping, t * 10.0)
# ノイズサンプル用の時間パラメータ
var noise_t := _time
# -1.0 ~ 1.0 のノイズ値を 2D で取得して、X/Y それぞれに適用
var nx := _noise.get_noise_2d(noise_t, 0.0)
var ny := _noise.get_noise_2d(0.0, noise_t + 100.0) # 軸をずらして別パターンにする
var offset_x := nx * amplitude * attenuation
var offset_y := ny * amplitude * attenuation
if _target:
_target.offset = _initial_offset + Vector2(offset_x, offset_y)
# --- Public API -------------------------------------------------------------
## 揺れを開始する。
## 引数を省略すると、export で設定したデフォルト値を使います。
func shake(custom_amplitude: float = -1.0, custom_duration: float = -1.0) -> void:
if _target == null:
push_warning("CameraShaker: 対象ノードが設定されていないため、shake() を実行できません。")
return
# 引数が正なら上書き、負なら既定値を使用
if custom_amplitude >= 0.0:
amplitude = custom_amplitude
if custom_duration >= 0.0:
duration = custom_duration
_time = 0.0
_initial_offset = _target.offset
_is_shaking = true
## 現在の揺れを即座に停止する。
func stop_shake() -> void:
_is_shaking = false
if reset_offset_on_finish and _target:
_target.offset = _initial_offset
使い方の手順
ここからは、実際に CameraShaker をシーンに組み込んでいきましょう。例として「プレイヤーに追従するカメラが、ダメージ時に揺れる」ケースを扱います。
手順①:スクリプトをプロジェクトに追加
- 上記の GDScript をそのままコピーして、
res://components/camera_shaker.gdなどのパスで保存します。 - Godot エディタを開き、スクリプトに
class_name CameraShakerが定義されていることを確認します(上のコードには既に含まれています)。
手順②:Camera2D にコンポーネントをアタッチ
プレイヤーシーンの例:
Player (CharacterBody2D)
├── Sprite2D
├── CollisionShape2D
└── Camera2D
└── CameraShaker (Node)
Playerシーンを開き、プレイヤーに追従するCamera2Dを用意します。Camera2Dの子としてNodeを追加し、名前をCameraShakerに変更します。- その
CameraShakerノードに、上で保存したcamera_shaker.gdをアタッチします。
(class_name CameraShakerが効いていれば、ノード追加時に「CameraShaker」というカスタムノードとしても出てきます) - インスペクタで Target > target_node は空のままで OK です。
空の場合は自動的に「親ノード(= Camera2D)」のoffsetを揺らします。
手順③:イベントから shake() を呼ぶ
例えば、プレイヤーがダメージを受けたときにカメラを揺らしたい場合、プレイヤースクリプトから shake() を呼び出します。
# Player.gd (CharacterBody2D)
extends CharacterBody2D
@onready var camera_shaker: CameraShaker = $Camera2D/CameraShaker
func _on_damage_taken(amount: int) -> void:
# ダメージ量に応じて揺れを少し強くする例
var amp := 8.0 + amount * 2.0
var dur := 0.25
camera_shaker.shake(amp, dur)
これで、_on_damage_taken() が呼ばれるたびにカメラがブルッと揺れます。
ノイズベースなので、同じパラメータでも毎回少し違った揺れになり、演出にランダム感が出ます。
手順④:他のシーンでも「そのまま使い回す」
このコンポーネントのいいところは、カメラの実装を一切いじらなくてよい点です。例えば:
- ボス専用のカメラ
- ステージ全体を映すシネマティック用カメラ
- 動く足場に取り付けた「揺れるカメラ」
などでも、同じ CameraShaker をポン付けして shake() を呼ぶだけで済みます。
例えば「ボスの咆哮で画面全体を揺らす」シーン構成はこんな感じ:
BossStageRoot (Node2D) ├── Boss (CharacterBody2D) ├── Camera2D │ └── CameraShaker (Node) └── LevelTiles (TileMap)
# Boss.gd
extends CharacterBody2D
@onready var camera_shaker: CameraShaker = $"../Camera2D/CameraShaker"
func roar() -> void:
# 咆哮アニメーション再生など…
camera_shaker.shake(24.0, 0.6)
メリットと応用
CameraShaker をコンポーネントとして切り出すことで、いくつか嬉しいポイントがあります。
- シーン構造がシンプルなまま
カメラを継承したり、謎の中間ノードを増やしたりせずに、Camera2D+CameraShakerというフラットな構成で済みます。 - カメラのロジックと揺れのロジックを分離
追従・ズーム・制限領域などのカメラ制御はカメラ側のスクリプトに集中させ、
「揺れ」という演出はCameraShakerに閉じ込められます。保守性がかなり上がります。 - どのシーンでもコピペで使い回せる
プレイヤー、敵専用カメラ、演出カメラなど、Camera2Dを置いたらとりあえずCameraShakerを子に付ける、という運用ができます。 - テストがしやすい
単体で動作確認できるので、「揺れだけオフにしてデバッグ」も簡単です。
改造案:方向付きの揺れを追加する
例えば「横方向だけに揺らしたい」「縦揺れだけにしたい」といったニーズもよくあります。
そんなときは、shake() に「方向ベクトル」を渡せるように改造してみましょう。
# CameraShaker.gd のどこかに追記する改造案
## 特定の方向ベクトルにのみ揺れを加えるバージョン
func shake_directional(direction: Vector2, custom_amplitude: float = -1.0, custom_duration: float = -1.0) -> void:
direction = direction.normalized()
if direction == Vector2.ZERO:
shake(custom_amplitude, custom_duration)
return
# 通常の shake をベースにしつつ、_process 内で方向を考慮するように
if custom_amplitude >= 0.0:
amplitude = custom_amplitude
if custom_duration >= 0.0:
duration = custom_duration
_time = 0.0
_initial_offset = _target.offset
_is_shaking = true
# 方向付きノイズを適用するための一時的なフック(シンプルにするならフラグでもOK)
# 実装をきれいにするには、_process 内を少しリファクタして
# 「揺れベクトルを計算する関数」を分離すると良いですね。
本格的にやるなら、_process() の中で「揺れベクトルを計算する関数」を分離して、shake() / shake_directional() で使い分ける形にするとよりコンポーネント指向になります。
こんな感じで、「継承ベースのカスタムカメラ」を増やす前に、まずはコンポーネントを足せないか?と考えてみると、プロジェクト全体がかなりスッキリしてきます。
ぜひ自分のゲームでも CameraShaker を土台に、演出用コンポーネントをどんどん育てていきましょう。
