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 をシーンに組み込んでいきましょう。例として「プレイヤーに追従するカメラが、ダメージ時に揺れる」ケースを扱います。

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

  1. 上記の GDScript をそのままコピーして、
    res://components/camera_shaker.gd などのパスで保存します。
  2. Godot エディタを開き、スクリプトに class_name CameraShaker が定義されていることを確認します(上のコードには既に含まれています)。

手順②:Camera2D にコンポーネントをアタッチ

プレイヤーシーンの例:

Player (CharacterBody2D)
 ├── Sprite2D
 ├── CollisionShape2D
 └── Camera2D
      └── CameraShaker (Node)
  1. Player シーンを開き、プレイヤーに追従する Camera2D を用意します。
  2. Camera2D の子として Node を追加し、名前を CameraShaker に変更します。
  3. その CameraShaker ノードに、上で保存した camera_shaker.gd をアタッチします。
    class_name CameraShaker が効いていれば、ノード追加時に「CameraShaker」というカスタムノードとしても出てきます)
  4. インスペクタで 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 を土台に、演出用コンポーネントをどんどん育てていきましょう。