【Unity】SpriteAnimator (簡易アニメ) コンポーネントの作り方

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

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

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

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

Unity 6 C#スクリプト 100本ノック

新品価格
¥1,230から
(2025/12/29 10:28時点)

Cocos Creator100本ノックTypeScriptで書く!

新品価格
¥1,250から
(2025/12/29 10:32時点)

Unityを触り始めた頃、つい Update() の中に「移動」「入力」「アニメ」「当たり判定」など、全部まとめて書いてしまいがちですよね。最初は動くので満足なのですが、時間が経つとこうなります。

  • どの処理がどの機能なのか分かりづらい
  • ちょっとした変更で別の挙動が壊れる
  • 他のオブジェクトでも使いたいのに、コピペ地獄になる

特に「スプライトアニメーション」まわりは、Update()

// フレーム時間を足して、一定時間ごとに sprite を差し替える…
// つい他の処理と混ざってカオスになる

といったコードを書きがちです。そこでこの記事では、

  • 「スプライトアニメーションだけ」を責務に持つ小さなコンポーネント
  • AnimationPlayer(旧Animator)を使わず、コードだけで簡単に制御

を実現する 「SpriteAnimator」コンポーネント を作っていきます。プレイヤーにも敵にも、動くギミックにもポン付けできる、シンプルなコンポーネント指向アプローチでいきましょう。

【Unity】コードだけでサクッとスプライトアニメ!「SpriteAnimator」コンポーネント

ここでは、AnimationPlayer(Animator)を使わずSpriteRenderer の sprite を一定間隔で切り替えるだけの簡易アニメコンポーネントを実装します。

  • フレーム配列と再生速度をインスペクターから設定
  • ループ再生・一度だけ再生の切り替え
  • 再生 / 一時停止 / 停止(先頭フレームに戻す)をコードから制御

といった最低限だけど汎用性の高い機能に絞ることで、コンポーネントとしての責務を明確にしています。

フルコード


using UnityEngine;

/// <summary>
/// SpriteRenderer の sprite を一定間隔で切り替える簡易アニメーター。
/// AnimationPlayer(Animator)を使わず、コードだけで制御したいとき用。
/// </summary>
[RequireComponent(typeof(SpriteRenderer))]
public class SpriteAnimator : MonoBehaviour
{
    // ==== アニメーション設定 ====

    [Header("アニメーション設定")]

    [Tooltip("アニメーションに使用するスプライトの配列(左から右に並べたフレーム順など)")]
    [SerializeField] private Sprite[] frames = default;

    [Tooltip("1秒間に何フレーム進めるか(FPS)。例えば 8 にすると 1秒で8枚進む。")]
    [SerializeField] private float framesPerSecond = 8f;

    [Tooltip("最後のフレームまで再生したら先頭に戻ってループするかどうか")]
    [SerializeField] private bool loop = true;

    [Tooltip("再生開始時に自動的に再生するかどうか")]
    [SerializeField] private bool playOnAwake = true;

    // ==== 状態管理 ====

    private SpriteRenderer spriteRenderer;

    // 現在のフレームインデックス(0~frames.Length-1)
    private int currentFrameIndex = 0;

    // 経過時間を蓄積して、一定時間ごとにフレームを進める
    private float timer = 0f;

    // 再生中かどうか
    private bool isPlaying = false;

    // 1フレームあたりの時間(秒)
    private float SecondsPerFrame
    {
        get
        {
            // FPSが0以下にならないようにガード
            if (framesPerSecond <= 0f)
            {
                return float.MaxValue; // 実質止まる
            }
            return 1f / framesPerSecond;
        }
    }

    private void Awake()
    {
        // 必須コンポーネントをキャッシュ
        spriteRenderer = GetComponent<SpriteRenderer>();

        // 初期フレームを反映
        ApplyFrame(currentFrameIndex);

        // playOnAwake が true のときは自動再生
        if (playOnAwake)
        {
            Play();
        }
    }

    private void Update()
    {
        // 再生中でない場合は何もしない
        if (!isPlaying)
        {
            return;
        }

        // フレームが設定されていない場合も何もしない
        if (frames == null || frames.Length == 0)
        {
            return;
        }

        // 経過時間を足す
        timer += Time.deltaTime;

        // 1フレーム分の時間を超えたら次のフレームへ
        while (timer >= SecondsPerFrame)
        {
            timer -= SecondsPerFrame;
            AdvanceFrame();
        }
    }

    /// <summary>
    /// 再生を開始(または再開)する。
    /// </summary>
    public void Play()
    {
        // フレームが無い場合は再生しても意味がないので止める
        if (frames == null || frames.Length == 0)
        {
            isPlaying = false;
            return;
        }

        isPlaying = true;
    }

    /// <summary>
    /// 再生を一時停止する(フレーム位置は保持)。
    /// </summary>
    public void Pause()
    {
        isPlaying = false;
    }

    /// <summary>
    /// 再生を停止し、先頭フレームに戻す。
    /// </summary>
    public void Stop()
    {
        isPlaying = false;
        timer = 0f;
        currentFrameIndex = 0;
        ApplyFrame(currentFrameIndex);
    }

    /// <summary>
    /// 任意のフレームインデックスを設定して、そのフレームを表示する。
    /// 範囲外のインデックスが渡された場合はクランプする。
    /// </summary>
    public void SetFrame(int frameIndex)
    {
        if (frames == null || frames.Length == 0)
        {
            return;
        }

        // 0 ~ frames.Length-1 にクランプ
        currentFrameIndex = Mathf.Clamp(frameIndex, 0, frames.Length - 1);
        ApplyFrame(currentFrameIndex);
    }

    /// <summary>
    /// 現在のフレームインデックスを返す。
    /// </summary>
    public int GetCurrentFrameIndex()
    {
        return currentFrameIndex;
    }

    /// <summary>
    /// フレーム配列を差し替える(ランタイムにアニメ切り替えしたい場合など)。
    /// 差し替え後はインデックスをリセットして先頭フレームを表示する。
    /// </summary>
    public void SetFrames(Sprite[] newFrames, bool autoPlay = true)
    {
        frames = newFrames;

        // 何も入っていない場合は止める
        if (frames == null || frames.Length == 0)
        {
            Stop();
            return;
        }

        currentFrameIndex = 0;
        timer = 0f;
        ApplyFrame(currentFrameIndex);

        if (autoPlay)
        {
            Play();
        }
    }

    /// <summary>
    /// ループ再生のオン/オフを切り替える。
    /// </summary>
    public void SetLoop(bool shouldLoop)
    {
        loop = shouldLoop;
    }

    /// <summary>
    /// 内部的に1フレーム進める処理。
    /// ループ設定や最後のフレーム到達時の挙動をここで制御する。
    /// </summary>
    private void AdvanceFrame()
    {
        if (frames == null || frames.Length == 0)
        {
            return;
        }

        currentFrameIndex++;

        // 最後のフレームを超えたかどうか
        if (currentFrameIndex >= frames.Length)
        {
            if (loop)
            {
                // ループの場合は先頭に戻る
                currentFrameIndex = 0;
            }
            else
            {
                // ループしない場合は最後のフレームで止める
                currentFrameIndex = frames.Length - 1;
                isPlaying = false;
            }
        }

        ApplyFrame(currentFrameIndex);
    }

    /// <summary>
    /// 指定したインデックスのスプライトを SpriteRenderer に適用する。
    /// </summary>
    private void ApplyFrame(int frameIndex)
    {
        if (spriteRenderer == null)
        {
            return;
        }

        if (frames == null || frames.Length == 0)
        {
            return;
        }

        // インデックスが範囲外にならないようにクランプ
        frameIndex = Mathf.Clamp(frameIndex, 0, frames.Length - 1);
        spriteRenderer.sprite = frames[frameIndex];
    }
}

使い方の手順

ここからは、実際にこの SpriteAnimator をシーンで使う手順を見ていきましょう。例として「プレイヤーキャラ」「敵キャラ」「動く床」の3パターンを挙げます。

手順①:スプライトシートを分割してフレームを用意する

  1. プロジェクトに2D用のスプライトシート(歩きアニメなど)をインポートします。
  2. インポートしたテクスチャを選択し、Sprite ModeMultiple に変更します。
  3. Sprite Editor を開き、Grid By Cell Size などでコマ割りして Apply します。
  4. Project ウィンドウでテクスチャを展開すると、分割された複数の Sprite アセットが見えるはずです。

これらの Sprite をそのまま SpriteAnimatorframes にドラッグ&ドロップして使います。

手順②:ゲームオブジェクトに SpriteRenderer と SpriteAnimator を付ける

  1. Hierarchy で右クリック → 2D Object > Sprite から新しいオブジェクトを作成(例:Player)。
  2. 自動で SpriteRenderer が付くので、念のため確認します。
  3. Add Component ボタンから SpriteAnimator を追加します。
  4. インスペクターで Frames 配列を展開し、先ほど分割した Sprite を順番に入れていきます(歩きアニメなら、歩行1 → 2 → 3 → 4 のように順番に)。
  5. Frames Per Second を 8〜12 くらいに設定すると、自然な歩きアニメになります。
  6. ループ再生したい場合は Loop にチェックを入れます(デフォルトでオン)。
  7. シーン再生と同時にアニメを始めたい場合は Play On Awake をオンにしておきます。

手順③:プレイヤーに使う(移動スクリプトと分離)

プレイヤーキャラに「移動処理」と「アニメ処理」を分けたい場合は、

  • PlayerMover:入力を読んで Rigidbody2D を動かす
  • SpriteAnimator:見た目のアニメだけを担当

という構成にするとスッキリします。例えば:


using UnityEngine;

/// <summary>
/// 簡単なプレイヤー横移動。アニメ制御は SpriteAnimator に委譲する例。
/// </summary>
[RequireComponent(typeof(Rigidbody2D))]
public class PlayerMover : MonoBehaviour
{
    [SerializeField] private float moveSpeed = 5f;
    [SerializeField] private SpriteAnimator walkAnimator = default;

    private Rigidbody2D rb;
    private float inputX;

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

    private void Update()
    {
        // キーボードの左右入力(-1 ~ 1)
        inputX = Input.GetAxisRaw("Horizontal");

        // 入力があるときだけアニメ再生、止まったら停止して先頭フレームに戻す
        if (walkAnimator != null)
        {
            if (Mathf.Abs(inputX) > 0.01f)
            {
                walkAnimator.Play();
            }
            else
            {
                walkAnimator.Stop();
            }
        }
    }

    private void FixedUpdate()
    {
        // 物理挙動は FixedUpdate で
        Vector2 velocity = rb.velocity;
        velocity.x = inputX * moveSpeed;
        rb.velocity = velocity;
    }
}

このように、移動とアニメを別コンポーネントに分けることで、あとから

  • アニメだけ差し替えたい
  • 移動ロジックだけ別のキャラにも使い回したい

といったときにも柔軟に対応できます。

手順④:敵や動く床にもポン付けで使う

敵キャラの場合:

  • 敵の GameObject に SpriteAnimator を追加
  • 「待機アニメ」「攻撃アニメ」など、別の Sprite[] を用意しておき、AIスクリプトから SetFrames() で切り替える

例えば、敵がプレイヤーを見つけたら攻撃アニメに切り替えるコードはこんな感じです。


using UnityEngine;

/// <summary>
/// 敵の状態に応じて SpriteAnimator のフレームを切り替える簡易例。
/// </summary>
public class EnemyAnimationController : MonoBehaviour
{
    [SerializeField] private SpriteAnimator animator = default;
    [SerializeField] private Sprite[] idleFrames = default;
    [SerializeField] private Sprite[] attackFrames = default;

    // ここでは単純に Inspector から切り替えるだけの例
    [SerializeField] private bool isAttacking = false;

    private void Start()
    {
        // 初期状態は待機アニメ
        if (animator != null)
        {
            animator.SetFrames(idleFrames, autoPlay: true);
        }
    }

    private void Update()
    {
        if (animator == null) return;

        // isAttacking のフラグを見てアニメを切り替える
        if (isAttacking)
        {
            animator.SetFrames(attackFrames, autoPlay: true);
        }
        else
        {
            animator.SetFrames(idleFrames, autoPlay: true);
        }
    }
}

動く床(ギミック)の場合:

  • 「光っている」「消えている」状態をスプライトアニメで表現
  • トリガーに入ったら Play()、出たら Stop() など、イベントと紐付ける

イベントとアニメを結びつける処理は、別コンポーネントにまとめておくとより見通しが良くなります。

メリットと応用

SpriteAnimator を使うことで、次のようなメリットがあります。

  • プレハブがシンプルになる
    アニメーション制御は SpriteAnimator に閉じ込められているので、プレハブ側では「どのフレームを使うか」「何FPSで回すか」だけを設定すればOKです。
  • レベルデザイン時に「見た目の動き」を簡単に付けられる
    動く床・トラップ・背景の看板など、ちょっとした動きをつけたいオブジェクトに SpriteAnimator を追加してスプライトを入れるだけで、すぐに「動いている感」を出せます。
  • 責務が分離されるので、保守しやすい
    アニメーションのロジックがゲームロジックから切り離されているため、「アニメの速さを変えたい」「ループせず一度だけ再生したい」といった変更は SpriteAnimator だけを見れば済みます。
  • アニメーションシステムに縛られない
    AnimationPlayer(Animator)のステートマシンを組むほどではない、でも静止画だけだと寂しい、という2Dプロトタイピングに非常に向いています。

改造案:Ping-Pong(往復)再生を追加する

例えば、フレームを「0 → 1 → 2 → 1 → 0 → 1 → 2 → …」と往復させたいときがあります。
その場合は、SpriteAnimator に「再生方向」を持たせて、AdvanceFrame() を少し改造してみましょう。


    // SpriteAnimator 内に追加するフィールド
    [Tooltip("フレームを 0→1→2→1→0 のように往復させるかどうか")]
    [SerializeField] private bool pingPong = false;

    // 再生方向(1:正方向, -1:逆方向)
    private int direction = 1;

    /// <summary>
    /// Ping-Pong 対応版のフレーム進行処理。
    /// 既存の AdvanceFrame() と置き換えて使う。
    /// </summary>
    private void AdvanceFrame()
    {
        if (frames == null || frames.Length == 0)
        {
            return;
        }

        // Ping-Pong 再生のとき
        if (pingPong && frames.Length > 1)
        {
            currentFrameIndex += direction;

            // 端に到達したら方向を反転
            if (currentFrameIndex >= frames.Length - 1)
            {
                currentFrameIndex = frames.Length - 1;
                direction = -1;
                if (!loop)
                {
                    isPlaying = false;
                }
            }
            else if (currentFrameIndex <= 0)
            {
                currentFrameIndex = 0;
                direction = 1;
                if (!loop)
                {
                    isPlaying = false;
                }
            }
        }
        else
        {
            // 通常の一方向ループ
            currentFrameIndex++;

            if (currentFrameIndex >= frames.Length)
            {
                if (loop)
                {
                    currentFrameIndex = 0;
                }
                else
                {
                    currentFrameIndex = frames.Length - 1;
                    isPlaying = false;
                }
            }
        }

        ApplyFrame(currentFrameIndex);
    }

このように、小さなコンポーネントとして作っておくと、「こう動かしたいな」と思ったときにピンポイントで改造しやすいのが大きな利点です。
ぜひ、自分のプロジェクトに合わせて機能を足したり、InputSystemと組み合わせたりして、コンポーネント指向のアニメ管理を育てていってみてください。

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

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

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

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

Unity 6 C#スクリプト 100本ノック

新品価格
¥1,230から
(2025/12/29 10:28時点)

Cocos Creator100本ノックTypeScriptで書く!

新品価格
¥1,250から
(2025/12/29 10:32時点)

URLをコピーしました!