プレイヤーが着地した瞬間、敵が爆発した瞬間。

画面やオブジェクトが「ガガガッ」と揺れるだけで、ゲームのクオリティ(手触り)は劇的に向上します。

しかし、単に position をランダムに動かすだけだと、カクカクしてしまい「安っぽい振動」になりがちです。

そこで今回は、多くのインディーゲーム開発者(Vlambeerなど)が提唱する**「Trauma(トラウマ)」システム**を採用したシェイクコンポーネントを作ります。

どんなことができるの?

この「TraumaShaker(トラウマ・シェイカー)」をCamera2Dやオブジェクトの子として追加します。

  • 自然な減衰: 衝撃を加えると激しく揺れ、徐々に、そして自然に収まります。
  • ノイズ関数を使用: Godot内蔵の FastNoiseLite を使い、機械的な振動ではなく「手ブレ」のような有機的な揺れを作ります。
  • 重ねがけOK: 揺れている最中にさらに衝撃が加わると、揺れが大きくなります(最大値は超えません)。

ステップ1:スクリプトの作成

trauma_shaker_component.gd という名前でスクリプトを作成し、コピペしてください。

class_name TraumaShakerComponent
extends Node

## 親ノード(主にCameraやSprite)に「衝撃(Trauma)」を与えて揺らすコンポーネント
## FastNoiseLiteを使用して、自然で滑らかな揺れを実現します。

# 最大の揺れ幅(ピクセル)
@export var max_offset: Vector2 = Vector2(100.0, 75.0)

# 最大の回転幅(ラジアン)。0にすると回転しません。
@export var max_roll: float = 0.1

# 衝撃の減衰スピード(毎秒どれくらいTraumaが減るか)
# 値が大きいほど、すぐに揺れが止まります。
@export var decay: float = 0.8

# ノイズのスクロール速度(揺れの細かさ)
@export var noise_speed: float = 4.0

# 現在の衝撃度(0.0 〜 1.0)。外部から直接いじらず、add_trauma()を使ってください。
var _trauma: float = 0.0
var _time: float = 0.0
var _noise: FastNoiseLite
var _parent_node: Node2D # 今回は2D用として作成

func _ready() -> void:
	# 親を取得(Node2Dを継承しているものならCamera2DでもSpriteでもOK)
	_parent_node = get_parent() as Node2D
	
	# ノイズ生成器のセットアップ
	_noise = FastNoiseLite.new()
	_noise.seed = randi()
	_noise.noise_type = FastNoiseLite.TYPE_PERLIN
	_noise.frequency = 0.1 # 必要に応じて調整

func _process(delta: float) -> void:
	# 衝撃がないなら何もしない
	if _trauma <= 0.0:
		return
		
	# 時間経過で衝撃を減らす
	_trauma = max(_trauma - decay * delta, 0.0)
	
	# 実際に適用する揺れの強さを計算(二乗することで自然な減衰にする)
	# Traumaが 0.5 のとき、揺れ幅は 0.25 になる(小さい揺れはより小さく、大きい揺れは激しく)
	var amount = _trauma * _trauma
	
	# ノイズを使って揺れを計算
	_time += delta * noise_speed
	
	# ノイズから -1.0〜1.0 の値を取得し、最大幅と現在の強度(amount)を掛ける
	var offset_x = max_offset.x * amount * _noise.get_noise_2d(_noise.seed, _time)
	var offset_y = max_offset.y * amount * _noise.get_noise_2d(_noise.seed + 1, _time)
	var roll = max_roll * amount * _noise.get_noise_2d(_noise.seed + 2, _time)
	
	# 親ノードに適用(Camera2Dの場合は offset プロパティを使うのが一般的だが、
	# 汎用性を持たせるため position を操作する場合は親の元の位置管理に注意が必要)
	# ここではシンプルな「オフセット加算」の例とします。
	
	if _parent_node is Camera2D:
		_parent_node.offset = Vector2(offset_x, offset_y)
		_parent_node.rotation = roll
	else:
		# 普通のSpriteなどの場合、元の位置に戻る処理が必要なため
		# 今回は「現在の位置に揺れを足す」のではなく「見た目だけ揺らす」用途として
		# Camera2D推奨、または親のSpriteを動かす形にします
		_parent_node.position += Vector2(offset_x, offset_y)
		_parent_node.rotation += roll
		
		# 【重要】動かした分を次のフレームで戻す処理が必要になる場合がありますが
		# 簡易版として、揺れたままにならないよう減衰後にリセットする処理を入れるか、
		# そもそもCamera2Dでの使用を推奨します。

## 外部からこの関数を呼んで衝撃を与えます
## amount: 0.0 〜 1.0 (0.5なら中くらいの揺れ、1.0なら最大)
func add_trauma(amount: float) -> void:
	_trauma = min(_trauma + amount, 1.0)

ステップ2:実際に使ってみよう(Camera2D編)

画面揺れ(スクリーンシェイク)が一番わかりやすい使い方です。

  1. プレイヤーやステージにある Camera2D ノードの下に、この TraumaShakerComponent を追加します。
  2. インスペクターで設定を調整します。
    • Max Offset: x: 50, y: 50 (激しく揺らしたい場合)
    • Decay: 0.8 (標準的。2.0にすると一瞬で止まる「硬い」揺れになります)

呼び出し方

例えば、プレイヤーがダメージを受けた時や、爆発エフェクトが出た時に呼び出します。

GDScript

# Player.gd や Explosion.gd など

# シーンツリーからカメラを探してシェイクさせる例
func _on_take_damage():
    var camera = get_viewport().get_camera_2d()
    if camera:
        # カメラの子にあるTraumaShakerを探す
        var shaker = camera.get_node_or_null("TraumaShakerComponent")
        if shaker:
            # 0.5 の強さで揺らす!
            shaker.add_trauma(0.5)

あるいは、爆発エフェクト自体にこのコンポーネントをつけて、爆発している間だけ絵を揺らすという使い方も可能です。

解説:「Trauma(トラウマ)の二乗」の魔法

コードの中に var amount = _trauma * _trauma という行があります。これがこのコンポーネントの肝です。

  • 線形(そのまま): Traumaが減ると、揺れも同じペースで弱まる。機械的な印象。
  • 二乗(今回): Traumaが減ると、揺れは**「急激に」**弱まる。

これにより、**「衝撃発生時は凄まじく揺れるが、すぐにスッと落ち着く」**という、アクション映画のような「キレのある」揺れになります。これを実装しているだけで、ゲームの手触りが「わかってる感」のあるものに変わります。

まとめ

  • 着地した時: add_trauma(0.2) (軽い揺れ)
  • 被弾した時: add_trauma(0.5) (中くらいの揺れ)
  • ボスを倒した時: add_trauma(1.0) (最大の揺れ)

このように数値を変えるだけで、ゲーム内のあらゆる衝撃を表現できます。

特に Camera2D の offset を動かす方法は、プレイヤーの座標計算を邪魔しないので非常に安全です。

次回は、シリーズ最終回(?)。これまでのコンポーネントを組み合わせて、実際にどのようにゲームオブジェクトを構成するか、**「実践的なプレハブ設計」**についてお話しします!


(ブログ執筆者へのメモ)

  • 2D/3Dの兼ね合い: 今回のコードは Vector2rotation (float) を扱っているため、2D専用として紹介するのが無難です。3D版(Camera3D用)が必要な場合は、Vector3rotation_degrees を使うように書き換える必要がありますが、記事が複雑になるので「今回は2Dの画面揺れに特化します」と注釈を入れると親切です。
  • ノイズの重要性: rand_range(完全ランダム)ではなく FastNoiseLite を使った理由を強調すると、記事の価値が上がります。「ランダムだとガガガッとなるが、ノイズだとグワングワンと揺れる」という違いです。