プレイヤーが着地した瞬間、敵が爆発した瞬間。
画面やオブジェクトが「ガガガッ」と揺れるだけで、ゲームのクオリティ(手触り)は劇的に向上します。
しかし、単に 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編)
画面揺れ(スクリーンシェイク)が一番わかりやすい使い方です。
- プレイヤーやステージにある
Camera2Dノードの下に、このTraumaShakerComponentを追加します。 - インスペクターで設定を調整します。
- Max Offset:
x: 50, y: 50(激しく揺らしたい場合) - Decay:
0.8(標準的。2.0にすると一瞬で止まる「硬い」揺れになります)
- Max Offset:
呼び出し方
例えば、プレイヤーがダメージを受けた時や、爆発エフェクトが出た時に呼び出します。
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の兼ね合い: 今回のコードは
Vector2やrotation(float) を扱っているため、2D専用として紹介するのが無難です。3D版(Camera3D用)が必要な場合は、Vector3とrotation_degreesを使うように書き換える必要がありますが、記事が複雑になるので「今回は2Dの画面揺れに特化します」と注釈を入れると親切です。 - ノイズの重要性:
rand_range(完全ランダム)ではなくFastNoiseLiteを使った理由を強調すると、記事の価値が上がります。「ランダムだとガガガッとなるが、ノイズだとグワングワンと揺れる」という違いです。
