5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Unity】アニメーション遷移行列

Posted at

はじめに:

本記事は QualiArts Advent Calendar 2025 8日目の記事です。

Unityでアニメーション遷移を管理する方法として主に以下の2つがあるかと思います。

  1. アニメーターコントローラー + スクリプト
  2. スクリプトのみで管理する


遷移管理の観点から、それぞれ以下のようなメリット・デメリットがあるように感じます。


simpe_anim_01.jpg※遷移先が多い場合、GUIによるノード管理は困難になり属人化しがち


手法 メリット デメリット
アニメーターコントローラー + スクリプト ノードにより非エンジニアでも扱いが簡単 大量の遷移がある場合、視認性が悪くノードであることが反って辛い
スクリプトのみ ノードに比べて大量の遷移でも管理し易い 非エンジニアでは扱えない可能性が高い

上記のメリットを残し、デメリットを軽減する手法として
アニメーション遷移行列 を簡単に紹介します。


アニメーション遷移行列とは:


image.png

アニメーション遷移行列は上記のような表です。
表形式で扱うことにより非エンジニアでも扱いやすく、
アニメーターコントローラーに比べて見た目がすっきりしており、
必要に応じてフィルタリング機能等を追加することで、大量の遷移も管理が容易です。


実装例:


image.png

アニメーション遷移行列の実装は表計算アプリケーションを利用するなど、
様々なやり方が考えられますが今回は上記画像のようなツールとして
Unity上で実装してみました。



 ▼ 遷移条件の作成・指定

condition_00.jpg

   ツール上で名前付きの条件式を作成できるようにしました。


/// <summary>
/// 遷移条件の種類
/// </summary>
public enum ActionTransitionConditionType
{
    ToMove,
    ToJump,
    ToIdle
}

/// <summary>
/// 遷移条件
/// </summary>
public delegate bool ActionTransitionCondition(in ActionContext actionContext);

public static class ActionTransitionConditions
{
    public static readonly ActionTransitionCondition[] ActionTransitionConditionList = new ActionTransitionCondition[]
    {
        Transition_5848d9c0e4074865b83437b34d72c266,
        Transition_52d4a2930c59491eaa0d703b2224f541,
        Transition_ab95663282c245429e881bf9954f99da
    };

    public static ActionTransitionCondition Get(ActionTransitionConditionType type)
    {
        return ActionTransitionConditionList[(int)type];
    }

    public static bool Transition_5848d9c0e4074865b83437b34d72c266(in ActionContext actionContext)
    {
        return actionContext.MoveSpeed > 0.0f;
    }

    public static bool Transition_52d4a2930c59491eaa0d703b2224f541(in ActionContext actionContext)
    {
        return !actionContext.IsGrounded;
    }

    public static bool Transition_ab95663282c245429e881bf9954f99da(in ActionContext actionContext)
    {
        return actionContext.IsGrounded;
    }
}

   Buildボタンから条件式をまとめた上記スクリプトを自動生成します。
   これはランタイムの負荷を下げる目的で導入した機能です。
   ツールにて文字列で記述された条件をランタイムでリフレクションするのではなく、
   事前にスクリプト化しておくことで実行時の負荷を下げます。



 ▼ 遷移元/先のアクション定義

/// <summary>
/// アクションの種類
/// </summary>
public enum ActionType
{
    Idle,
    Move,
    Jump,
    Max,
}

/// <summary>
/// アクションに関連したパラメータ
/// </summary>
public class ActionContext
{
    public bool IsGrounded = false;
    public float MoveSpeed = 0.0f;
};

/// <summary>
/// アクション
/// </summary>
public delegate void ActionHandler(ActionContext actionContext);

public static class ActionHandlers
{
    public static ActionHandler[] List = new ActionHandler[]
    {
        ActionHandlers.Idle,
        ActionHandlers.Move,
        ActionHandlers.Jump,
    };

    public static void Idle(ActionContext actionContext)
    {
        // アイドル処理
    }

    public static void Move(ActionContext actionContext)
    {
        // 移動処理
    }

    public static void Jump(ActionContext actionContext)
    {
        // ジャンプ処理
    }
};

   動作確認あたり関数ベースのASM( Action State Machine )を用意していたため、
   遷移元/先のアクションはそれぞれクラス化せず、関数として実装しました。



 ▼ アクションとアニメーションの関連付け

image.png

   前述で定義したアクションの列挙型と任意のアニメーションクリップを
   ツール上で紐づけました。



 ▼ アニメーション遷移行列の作成

   ここまでの実装で最低限アニメーション遷移行列に必要な要素が揃いました。

image.png

   アニメーション遷移行列の作成はツールをポチポチするだけです。


[Serializable]
public sealed class ActionTransitionEntry
{
    /// <summary>
    /// 遷移先アクション
    /// </summary>
    public ActionType To = default(ActionType);

    /// <summary>
    /// 遷移条件
    /// </summary>
    public ActionTransitionConditionType Condition = default(ActionTransitionConditionType);

    /// <summary>
    /// フェード時間
    /// </summary>
    public float Duration = 0.0f;
}

[HideInInspector] [SerializeField]
private ActionTransitionEntry[][] _runtimeTransitionTable = Array.Empty<ActionTransitionEntry[]>();

   遷移行列は ScriptableOjbect にて、上記のような
   2次元配列 _runtimeTransitionTable に保存されます。



 ▼ アニメーション遷移行列の使用例

void ExecuteASM()
{
    // アクションの遷移確認:
    // 遷移行列から現在のアクションの遷移先一覧を取得
    ActionTransitionEntry[] actionTransitionEntries = actionTransitionTable.Get(_currentActionType);

    foreach (ActionTransitionEntry entry in actionTransitionEntries)
    {
        // 自動生成した遷移条件を取得
        ActionTransitionCondition condition = ActionTransitionConditions.Get(entry.ConditionType);

        // 遷移条件を満たすアクションがあれば遷移・アニメーション再生
        if (condition.Invoke(_actionContext))
        {
            _currentActionType = entry.To;
            int currentActionIndex = (int)_currentActionType;
            _currentActionHandler = ActionHandlers.List[currentActionIndex];
            
            // ツールで紐づけたアニメーションを再生
            AnimationClipPlayable animClip = _animationClips[currentActionIndex];
            _animationPlayableOutput.SetSourcePlayable(animClip);

            break;
        }
    }

    // アクションの実行:
    _currentActionHandler.Invoke(_actionContext);
}

おわりに:

 記事では触れられなかったアニメーション遷移時のフェード処理、
 ブレンドツリーなどアニメーターコントローラーから脱却したことで
 使えなくなった機能については自ら実装する課題が残っています。
 これらについては独自の PlayableGraph 等で解決する必要があるかと思います。

 アニメーション遷移行列の紹介は以上となります。
 アニメーション遷移の管理についてはこれといった正解がなく、
 紹介した手法以外にも例えば モーションマッチング など、プロジェクトごとに
 最適な実装を採用しているかと思われます。

 この記事がゲーム開発の参考になれば幸いです。

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?