【Unity】WobbleEffect (ぷるぷる) コンポーネントの作り方

Godot 4ゲーム制作 実践ドリル 100本ノック

新品価格
¥1,250から
(2025/12/13 21:27時点)

脱・初心者!Godot 4 ゲーム開発の「2歩目」

新品価格
¥831から
(2025/12/13 21:39時点)

Godot4ローグライク入門 ~ダンジョン自動生成~

新品価格
¥831から
(2025/12/13 21:44時点)

Godot4& GDScriptではじめる 2Dゲーム開発レシピ

新品価格
¥590から
(2025/12/13 21:46時点)

Unityを触り始めたころは、ついなんでもかんでも Update() に書いてしまいがちですよね。移動、入力、エフェクト、アニメーション、UI更新…全部1つのスクリプトに押し込むと、動きはするけど「あとから直せない」「プレハブごとに挙動を変えづらい」「コピペ地獄になる」といった問題が一気に噴き出します。

見た目のちょっとした演出(ぷるぷる揺れる、少しだけ拡大縮小する、点滅する など)も、つい巨大なプレイヤースクリプトに書いてしまいがちですが、それは避けたいところです。演出は演出用の小さなコンポーネントに切り出しておくと、他のオブジェクトにも簡単に再利用できて、ゲーム全体の設計もずっとシンプルになります。

この記事では、「待機中にスプライトをノイズ関数でぷるぷる揺らして、ゼリーのような質感を出す」ための専用コンポーネント 「WobbleEffect」 を作ってみます。プレイヤー、敵、動く床、アイテムなど、どんなスプライトにもアタッチするだけでゼリー感を足せる、シンプルで再利用性の高いコンポーネントにしていきましょう。

【Unity】ノイズでぷるぷる!「WobbleEffect」コンポーネント

今回の WobbleEffect は、

  • オブジェクトのローカル座標を Perlin Noise で少しだけ揺らす
  • オプションでスケールもぷるぷるさせる
  • 元の位置・スケールから必ず戻せる(再生中にON/OFFしても安全)

という方針で作ります。
スプライト自体を変形するわけではなく、「見た目上の揺れ」を Transform の位置・スケールで表現する形です。2Dゲームでの「ゼリー感」を出すには十分な表現力がありますし、3Dオブジェクトにもそのまま使えます。

フルコード:WobbleEffect.cs


using UnityEngine;

/// <summary>
/// 待機中にオブジェクトをぷるぷる揺らして、ゼリーのような質感を出すコンポーネント。
/// Transform のローカル位置・スケールを Perlin Noise でゆらすだけなので、
/// どんな 2D/3D オブジェクトにもアタッチして使えます。
/// </summary>
[DisallowMultipleComponent]
public class WobbleEffect : MonoBehaviour
{
    // --- 基本設定 ---

    [Header("基本設定")]
    [Tooltip("コンポーネント有効時に自動でぷるぷるを開始するかどうか")]
    [SerializeField] private bool playOnEnable = true;

    [Tooltip("ぷるぷるの強さを全体的にスケーリングする係数")]
    [SerializeField, Range(0f, 2f)] private float intensity = 1f;

    [Tooltip("時間経過に対するノイズの速さ(値が大きいほど素早く揺れる)")]
    [SerializeField, Range(0.1f, 10f)] private float speed = 2f;

    [Tooltip("ランダムシード。複数オブジェクトで同じ動きをさせたくないときに変える")]
    [SerializeField] private int randomSeed = 0;

    // --- 位置(オフセット)設定 ---

    [Header("位置オフセット設定")]
    [Tooltip("位置をぷるぷるさせるかどうか")]
    [SerializeField] private bool usePositionWobble = true;

    [Tooltip("X 方向の最大オフセット幅(ローカル座標)")]
    [SerializeField] private float positionAmplitudeX = 0.05f;

    [Tooltip("Y 方向の最大オフセット幅(ローカル座標)")]
    [SerializeField] private float positionAmplitudeY = 0.05f;

    [Tooltip("Z 方向の最大オフセット幅(ローカル座標)")]
    [SerializeField] private float positionAmplitudeZ = 0f;

    // --- スケール設定 ---

    [Header("スケール設定")]
    [Tooltip("スケールをぷるぷるさせるかどうか")]
    [SerializeField] private bool useScaleWobble = true;

    [Tooltip("X スケールの変化量(1 を基準とした加算値)")]
    [SerializeField] private float scaleAmplitudeX = 0.05f;

    [Tooltip("Y スケールの変化量(1 を基準とした加算値)")]
    [SerializeField] private float scaleAmplitudeY = 0.05f;

    [Tooltip("Z スケールの変化量(1 を基準とした加算値)")]
    [SerializeField] private float scaleAmplitudeZ = 0f;

    [Tooltip("スケール変化に対する時間係数(位置とは別に揺れの速さを調整したい場合に使用)")]
    [SerializeField, Range(0.1f, 10f)] private float scaleSpeedMultiplier = 1.2f;

    // --- 内部状態 ---

    /// <summary>元のローカル位置(オフセットの基準)</summary>
    private Vector3 _baseLocalPosition;

    /// <summary>元のローカルスケール(スケール変化の基準)</summary>
    private Vector3 _baseLocalScale;

    /// <summary>現在ぷるぷる中かどうか</summary>
    private bool _isPlaying = false;

    /// <summary>ノイズ用の時間オフセット(複数オブジェクトで動きをずらす)</summary>
    private float _timeOffset;

    private void Awake()
    {
        // 起動時に現在の Transform を基準値として保持しておく
        _baseLocalPosition = transform.localPosition;
        _baseLocalScale = transform.localScale;

        // シードを元に時間オフセットを決める
        // 同じシードなら同じ動き、違うシードなら違う動きになります。
        if (randomSeed == 0)
        {
            // シードが 0 の場合はランダムに決める
            _timeOffset = Random.Range(0f, 1000f);
        }
        else
        {
            // 固定シードから決まった時間オフセットを生成
            _timeOffset = randomSeed * 13.37f; // 適当な係数でばらけさせる
        }
    }

    private void OnEnable()
    {
        // 有効化時に元の Transform を再キャッシュしておくと、
        // プレイ中に位置やスケールを変えても、その状態を基準にぷるぷるできます。
        _baseLocalPosition = transform.localPosition;
        _baseLocalScale = transform.localScale;

        if (playOnEnable)
        {
            Play();
        }
    }

    private void OnDisable()
    {
        // 無効化されたときは Transform を元に戻しておくと安全
        ResetTransform();
    }

    private void Update()
    {
        if (!_isPlaying)
        {
            return;
        }

        // 経過時間にオフセットを足して、ノイズのスタート位置をずらす
        float time = Time.time * speed + _timeOffset;

        // 位置のぷるぷる
        Vector3 wobblePos = _baseLocalPosition;
        if (usePositionWobble && intensity > 0f)
        {
            // Mathf.PerlinNoise は 0〜1 の値なので、-1〜1 に変換してから振幅を掛ける
            float noiseX = (Mathf.PerlinNoise(time, 0.123f) * 2f - 1f) * positionAmplitudeX * intensity;
            float noiseY = (Mathf.PerlinNoise(0.456f, time) * 2f - 1f) * positionAmplitudeY * intensity;
            float noiseZ = (Mathf.PerlinNoise(time * 0.5f, time * 0.75f) * 2f - 1f) * positionAmplitudeZ * intensity;

            wobblePos += new Vector3(noiseX, noiseY, noiseZ);
        }

        // スケールのぷるぷる
        Vector3 wobbleScale = _baseLocalScale;
        if (useScaleWobble && intensity > 0f)
        {
            float scaleTime = time * scaleSpeedMultiplier;

            float noiseX = (Mathf.PerlinNoise(scaleTime, 1.234f) * 2f - 1f) * scaleAmplitudeX * intensity;
            float noiseY = (Mathf.PerlinNoise(2.468f, scaleTime) * 2f - 1f) * scaleAmplitudeY * intensity;
            float noiseZ = (Mathf.PerlinNoise(scaleTime * 0.7f, scaleTime * 1.1f) * 2f - 1f) * scaleAmplitudeZ * intensity;

            // 基準スケールに対して加算する形でゼリー感を出す
            wobbleScale += new Vector3(noiseX, noiseY, noiseZ);
        }

        // 実際に Transform に反映
        transform.localPosition = wobblePos;
        transform.localScale = wobbleScale;
    }

    /// <summary>
    /// ぷるぷるを開始します。
    /// 外部スクリプトから制御したいとき用の API です。
    /// </summary>
    public void Play()
    {
        // 再生開始時点の Transform を新たな基準として記録
        _baseLocalPosition = transform.localPosition;
        _baseLocalScale = transform.localScale;
        _isPlaying = true;
    }

    /// <summary>
    /// ぷるぷるを停止し、Transform を基準値にリセットします。
    /// </summary>
    public void StopAndReset()
    {
        _isPlaying = false;
        ResetTransform();
    }

    /// <summary>
    /// ぷるぷるを一時停止しますが、現在の変形状態は維持します。
    /// 再度 Play() すると、その時点を基準に再開します。
    /// </summary>
    public void Pause()
    {
        _isPlaying = false;
    }

    /// <summary>
    /// 内部的に使う Transform リセット処理。
    /// </summary>
    private void ResetTransform()
    {
        transform.localPosition = _baseLocalPosition;
        transform.localScale = _baseLocalScale;
    }
}

使い方の手順

ここでは Unity6 の 2D プロジェクトを想定して、具体的な使用例をいくつか挙げながら手順を説明します。

手順①:スクリプトを用意する

  1. プロジェクトウィンドウで Scripts フォルダ(なければ作成)を右クリックし、
    Create > C# Script を選択して名前を WobbleEffect にします。
  2. 生成された WobbleEffect.cs を開き、この記事のコードを丸ごとコピペして保存します。

手順②:ぷるぷるさせたいオブジェクトにアタッチする

  • プレイヤーキャラクター(例:Player オブジェクト)
    • Hierarchy で Player を選択
    • Inspector の Add Component ボタンから WobbleEffect を追加
    • デフォルトのままでも軽くぷるぷるしますが、プレイヤーは控えめにしたいなら
      • intensity を 0.5 くらいに下げる
      • positionAmplitudeX/Y を 0.02 くらいにする
  • 敵キャラクター(スライムなど)
    • Slime プレハブに WobbleEffect をアタッチ
    • ゼリー感を強めたいので
      • intensity:1.2〜1.5
      • positionAmplitudeY:0.08〜0.12
      • useScaleWobble:ON、scaleAmplitudeY:0.1 くらい
  • 動く床(ゼリー状の足場)
    • MovingPlatform プレハブに WobbleEffect をアタッチ
    • 横方向にゆらゆらさせたい場合は
      • positionAmplitudeX:0.05〜0.1
      • positionAmplitudeY:0.02 くらい
      • useScaleWobble は OFF(足場の大きさを変えたくない場合)

手順③:パラメータを調整してゼリー感を作る

再生ボタンを押してゲームを実行し、WobbleEffect のパラメータをいじりながら好みの揺れを探しましょう。

  • intensity:全体の強さ。まずは 0.5〜1.0 の範囲で調整。
  • speed:時間に対するノイズの進み具合。1〜3 くらいが扱いやすいです。
  • positionAmplitudeX/Y/Z:どの方向にどのくらい揺れるか。
  • useScaleWobblescaleAmplitudeX/Y/Z
    • ON にすると「むにゅっ」としたゼリー感が強くなります。
    • Y だけ少し大きめにすると、上下の伸び縮みが強調されてスライム感が出ます。

手順④:状態に応じて ON/OFF したい場合(例:プレイヤーが止まっている時だけぷるぷる)

プレイヤーが動いているときはピタっとさせて、止まっているときだけぷるぷるさせたい場合は、プレイヤー側のスクリプトから Play() / Pause() / StopAndReset() を呼び出します。


using UnityEngine;

public class PlayerIdleWobbleController : MonoBehaviour
{
    [SerializeField] private WobbleEffect wobbleEffect;
    [SerializeField] private float idleSpeedThreshold = 0.01f;

    private Rigidbody2D _rb;

    private void Awake()
    {
        _rb = GetComponent<Rigidbody2D>();
    }

    private void Update()
    {
        // 一定速度以下なら「待機中」とみなす
        bool isIdle = _rb.velocity.sqrMagnitude < idleSpeedThreshold * idleSpeedThreshold;

        if (isIdle)
        {
            // ぷるぷる開始(すでに再生中なら特に変化なし)
            wobbleEffect.Play();
        }
        else
        {
            // 動いている間は一時停止(形はそのまま)
            wobbleEffect.Pause();
        }
    }
}

wobbleEffect には、同じ GameObject か子オブジェクトについている WobbleEffect をドラッグ&ドロップで割り当ててください。

メリットと応用

WobbleEffect をコンポーネントとして分離しておくことで、次のようなメリットがあります。

  • プレハブ管理が楽になる
    • スライム用、ゼリー床用、ぷるぷるアイテム用など、プレハブごとにパラメータだけ変えれば OK。
    • 揺れのロジック自体は 1 つのスクリプトにまとまっているので、仕様変更も一箇所で済みます。
  • レベルデザインのときに「雰囲気づくり」が簡単
    • 「この足場、ちょっと柔らかそうに見せたいな」と思ったら WobbleEffect をポン付け。
    • UI のアイコンや拾得アイテムにも付ければ、「触りたくなる」見た目をすぐ追加できます。
  • Godクラス化を防げる
    • プレイヤーのスクリプトから「演出コード」を追い出せるので、入力・移動・攻撃などの本質ロジックに集中できます。
    • 敵 AI やギミックのスクリプトとも完全に独立しているため、バグの切り分けがしやすいです。

さらに、WobbleEffect を少し改造するだけで、いろいろな表現に応用できます。

  • ダメージを受けた瞬間だけ、一時的に intensity を上げて大きく揺らす
  • ステージの「危険エリア」だけ、揺れをだんだん強くしてプレイヤーに危険を伝える
  • UI ボタンに付けて、ホバー中だけぷるぷるさせる

例えば「一瞬だけ大きく揺らす」ための簡易トリガー関数を追加する改造案はこんな感じです。


    /// <summary>
    /// 一定時間だけ intensity をブーストして、強めのぷるぷるを一瞬だけ発生させる。
    /// コルーチンを使わず、Update 内で減衰させる簡易実装。
    /// </summary>
    public void TriggerBurst(float extraIntensity, float duration)
    {
        // もともとの intensity を一時的に上書きする例。
        // 実装としては、別途「_burstTimer」「_baseIntensity」などのフィールドを追加し、
        // Update() 内で時間経過に応じて intensity を元に戻すようなロジックを足すと良いです。
    }

このように、小さなコンポーネントとして「ぷるぷる」という役割を切り出しておくと、ゲーム中のあらゆるオブジェクトにゼリー感を簡単に付与できます。Update に全部書かず、機能ごとにスクリプトを分けていく習慣をつけていきましょう。

Godot 4ゲーム制作 実践ドリル 100本ノック

新品価格
¥1,250から
(2025/12/13 21:27時点)

脱・初心者!Godot 4 ゲーム開発の「2歩目」

新品価格
¥831から
(2025/12/13 21:39時点)

Godot4ローグライク入門 ~ダンジョン自動生成~

新品価格
¥831から
(2025/12/13 21:44時点)

Godot4& GDScriptではじめる 2Dゲーム開発レシピ

新品価格
¥590から
(2025/12/13 21:46時点)

URLをコピーしました!