Unityを触り始めた頃って、つい何でもかんでも Update() に書きがちですよね。カメラの追従、入力処理、UI更新、ダメージ計算、そして「カメラ揺らしたい!」まで全部ひとつのスクリプトに押し込みたくなります。
でもそれを続けていると、
- カメラ処理が肥大化して、どこを直せばいいか分からなくなる
- 別のシーンや別プロジェクトで「カメラ揺れ」だけ再利用したいのに、他の処理とベッタリ結合していて切り出せない
- 「爆発で揺らしたい」「ダメージで揺らしたい」たびに同じようなコードをコピペしてバグの温床になる
といった問題にぶつかります。
そこで今回は、「カメラ揺れ」だけに責務を絞ったコンポーネント CameraShaker を用意して、外部からメソッドを呼ぶだけで親オブジェクトをノイズ関数でガッツリ揺らせるようにしてみましょう。プレイヤーや敵のスクリプトは「揺らして」と命令するだけ。揺らし方の実装は CameraShaker に丸投げ、というコンポーネント指向な設計です。
【Unity】爆発も被ダメも一発演出!「CameraShaker」コンポーネント
フルコード(CameraShaker.cs)
using UnityEngine;
/// <summary>
/// 親オブジェクトのローカルオフセットをノイズで揺らすコンポーネント。
/// 外部から Shake を呼び出して使います。
/// カメラ本体ではなく、「カメラをぶら下げる空オブジェクト」に付ける想定です。
/// </summary>
[DisallowMultipleComponent]
public class CameraShaker : MonoBehaviour
{
[Header("揺れの基本設定")]
[SerializeField]
private float defaultDuration = 0.3f; // デフォルトの揺れ時間(秒)
[SerializeField]
private float defaultAmplitude = 0.5f; // デフォルトの揺れ強さ(振幅)
[SerializeField]
private float defaultFrequency = 20f; // デフォルトの揺れの速さ(周波数)
[Header("軸ごとの揺れ有効フラグ")]
[SerializeField]
private bool shakeX = true;
[SerializeField]
private bool shakeY = true;
[SerializeField]
private bool shakeZ = false; // Z方向の揺れは3Dゲームで酔いやすいのでデフォルトOFF
[Header("減衰設定")]
[SerializeField]
[Tooltip("時間経過で揺れを減衰させるかどうか")]
private bool useDamping = true;
[SerializeField]
[Tooltip("1.0で線形減衰。2.0以上で終盤で急に収束するようなカーブになります。")]
private float dampingPower = 1.5f;
// 現在の揺れ状態
private float shakeTimer = 0f;
private float currentDuration = 0f;
private float currentAmplitude = 0f;
private float currentFrequency = 0f;
// 元のローカル位置を保持
private Vector3 initialLocalPosition;
// 乱数シード(各軸で少しずらして使う)
private float noiseSeedX;
private float noiseSeedY;
private float noiseSeedZ;
private void Awake()
{
// 初期ローカル位置を保持
initialLocalPosition = transform.localPosition;
// シードをランダムに生成(GameObjectごとに違う揺れパターンにするため)
float baseSeed = Random.Range(-10000f, 10000f);
noiseSeedX = baseSeed;
noiseSeedY = baseSeed + 100.123f;
noiseSeedZ = baseSeed + 200.456f;
}
private void OnEnable()
{
// 有効化されたときに位置をリセットしておく
transform.localPosition = initialLocalPosition;
ResetShakeState();
}
private void OnDisable()
{
// 無効化時も位置を元に戻す
transform.localPosition = initialLocalPosition;
ResetShakeState();
}
private void Update()
{
if (shakeTimer <= 0f)
{
// 揺れていないときは元の位置を維持
transform.localPosition = initialLocalPosition;
return;
}
// 経過時間更新
shakeTimer -= Time.deltaTime;
float t = Mathf.Clamp01(1f - (shakeTimer / currentDuration)); // 0 → 1
// 減衰係数(1 → 0)
float damping = useDamping
? Mathf.Pow(1f - t, dampingPower)
: 1f;
// 時間に応じてノイズサンプル位置を進める
float time = Time.time * currentFrequency;
// PerlinNoise は 0~1 の値を返すので、-1~1 に変換
float noiseX = (Mathf.PerlinNoise(noiseSeedX, time) * 2f - 1f);
float noiseY = (Mathf.PerlinNoise(noiseSeedY, time) * 2f - 1f);
float noiseZ = (Mathf.PerlinNoise(noiseSeedZ, time) * 2f - 1f);
Vector3 offset = Vector3.zero;
if (shakeX)
{
offset.x = noiseX * currentAmplitude * damping;
}
if (shakeY)
{
offset.y = noiseY * currentAmplitude * damping;
}
if (shakeZ)
{
offset.z = noiseZ * currentAmplitude * damping;
}
// 元の位置にオフセットを足して適用
transform.localPosition = initialLocalPosition + offset;
// 完全に終わったら位置を戻す
if (shakeTimer <= 0f)
{
transform.localPosition = initialLocalPosition;
ResetShakeState();
}
}
/// <summary>
/// デフォルト設定で揺らす(外部から一番シンプルに呼べるAPI)。
/// </summary>
public void Shake()
{
Shake(defaultDuration, defaultAmplitude, defaultFrequency);
}
/// <summary>
/// 強さだけ指定して揺らす。時間と周波数はデフォルトを使用。
/// </summary>
/// <param name="amplitude">揺れの強さ(振幅)</param>
public void Shake(float amplitude)
{
Shake(defaultDuration, amplitude, defaultFrequency);
}
/// <summary>
/// 完全指定で揺らすメインAPI。
/// </summary>
/// <param name="duration">揺れ時間(秒)</param>
/// <param name="amplitude">揺れの強さ(振幅)</param>
/// <param name="frequency">揺れの速さ(周波数)</param>
public void Shake(float duration, float amplitude, float frequency)
{
// 新しい揺れが来たとき、今より弱い揺れなら無視する等のロジックも考えられますが、
// ここでは素直に上書きします。
currentDuration = Mathf.Max(0.0001f, duration);
currentAmplitude = Mathf.Max(0f, amplitude);
currentFrequency = Mathf.Max(0f, frequency);
shakeTimer = currentDuration;
}
/// <summary>
/// 現在の揺れを即座に停止し、位置を初期状態に戻す。
/// </summary>
public void StopShake()
{
shakeTimer = 0f;
transform.localPosition = initialLocalPosition;
ResetShakeState();
}
/// <summary>
/// スクリプトから初期ローカル位置を更新したい場合に呼ぶ。
/// (カメラのベース位置を動的に変えるようなゲーム向け)
/// </summary>
public void SetCurrentAsInitialPosition()
{
initialLocalPosition = transform.localPosition;
}
/// <summary>
/// 内部状態をリセット。
/// </summary>
private void ResetShakeState()
{
shakeTimer = 0f;
currentDuration = 0f;
currentAmplitude = 0f;
currentFrequency = 0f;
}
}
使い方の手順
-
カメラ用の親オブジェクトを用意する
シーン階層で、例えばこんな構造にします。CameraRig ← このオブジェクトに CameraShaker を付ける └─ Main CameraCameraRigは空の GameObject で OK です。Main CameraをCameraRigの子にドラッグして親子関係を作ります。
-
CameraRig に CameraShaker をアタッチする
CameraShaker.csをプロジェクトの任意のフォルダに保存。- Unity に戻ると自動コンパイルされるので、
CameraRigを選択してCameraShakerコンポーネントを追加します。 - インスペクターで「揺れ時間」「振幅」「周波数」「軸ごとの揺れON/OFF」をお好みで調整しましょう。
-
外部スクリプトから Shake を呼び出す
たとえば「プレイヤーがダメージを受けたときにカメラを揺らす」例です。
プレイヤーのスクリプトに以下のようなコードを追加します。using UnityEngine; public class PlayerDamageTester : MonoBehaviour { [SerializeField] private CameraShaker cameraShaker; // CameraRig の CameraShaker をアサイン [SerializeField] private KeyCode testKey = KeyCode.Space; private void Update() { // スペースキーを押したらテスト的にカメラを揺らす if (Input.GetKeyDown(testKey)) { if (cameraShaker != null) { // デフォルト設定で揺らす cameraShaker.Shake(); } } } // 例えば実際のダメージ処理からも同じように呼べます public void OnDamaged(int damage) { if (cameraShaker == null) return; // ダメージ量に応じて揺れを強くする例 float amplitude = Mathf.Clamp(damage * 0.05f, 0.2f, 1.0f); cameraShaker.Shake(amplitude); } }cameraShakerには、ヒエラルキー上のCameraRigをドラッグ&ドロップしてアサインしてください。- ゲーム再生中にスペースキーを押すと、カメラがガッと揺れます。
-
他の演出にもどんどん再利用する
- 敵の死亡時に揺らす:
爆発エフェクトを出すスクリプトからcameraShaker.Shake(0.4f, 0.8f, 25f);のように呼び出すだけ。 - 動く床に乗ったときにわずかに揺らす:
プレイヤーが特定のトリガーに入ったときにcameraShaker.Shake(0.15f, 0.1f, 10f);を呼ぶと、臨場感が出ます。 - 必殺技発動時のド派手演出:
カットイン中に長め&強めの揺れを設定して、演出を盛り上げましょう。
- 敵の死亡時に揺らす:
メリットと応用
この CameraShaker コンポーネントを導入すると、カメラ揺れの処理をすべてひとつの小さなスクリプトに閉じ込められます。
- プレハブ管理が楽
カメラ周りのプレハブ(例:CameraRigプレハブ)に CameraShaker を仕込んでおけば、どのシーンでも同じ揺れ演出をすぐ使い回せます。
シーンごとに「カメラ揺れコード」を書き直す必要がなくなります。 - ゲームロジックと演出が分離できる
プレイヤーや敵のスクリプトは「ダメージを受けた」「爆発した」といったゲームロジックに集中し、
「どのくらい揺らすか」は CameraShaker に任せられます。
これによりスクリプトが小さく保たれ、保守性も上がります。 - レベルデザイン時にパラメータ調整がしやすい
「このステージは全体的に揺れを強めたいな」と思ったら、CameraRig のインスペクターでデフォルト値を変えるだけ。
スクリプトをいじらずに、レベルデザイナーが直接パラメータを触れるのも大きな利点です。
さらに、CameraShaker をちょっと改造するだけで、いろいろなバリエーションが作れます。
改造案:揺れの完了イベントを追加する
「揺れが終わったら UI を出したい」「ポーズを解除したい」といったケース向けに、
シンプルなコールバックを仕込む例です。
// CameraShaker クラス内に追記する例
public System.Action onShakeCompleted;
// Update の最後あたりの「完全に終わったら位置を戻す」部分を少し変更
if (shakeTimer <= 0f)
{
transform.localPosition = initialLocalPosition;
ResetShakeState();
// 揺れ完了コールバックを呼ぶ
onShakeCompleted?.Invoke();
}
これで、外部スクリプトから
cameraShaker.onShakeCompleted = () =>
{
// 揺れが終わったら実行したい処理
resultPanel.SetActive(true);
};
のように登録すれば、揺れ演出をトリガーにしたシーケンスも簡単に組めます。
カメラ揺れの責務を CameraShaker に閉じ込めておくと、「とりあえず揺らしたい」が一行で書けるようになります。
Update に全部書き殴るスタイルから卒業して、小さなコンポーネントを積み上げる設計にシフトしていきましょう。




