📗

【Unity】AnimationManagerについて

2024/08/29に公開

はじめに

初めての方も、そうでない方もこんにちは!
現役ゲームプログラマーのたむぼーです。
自己紹介を載せているので、気になる方は見ていただければ嬉しいです!

今回は
 AnimationManager
について紹介します

https://zenn.dev/tmb/articles/1072f8ea010299

アニメーション統合クリップ

前回の記事のアニメーション統合クリップを使用した、アニメーションマネージャーを実装しました。

https://zenn.dev/tmb/articles/4d4fb5a48e2982

UniRx/UniTask

Task管理などでUniRx/UniTaskを利用しています。

CancellationTokenHelperについては、こちらの記事の最後にあります。

https://zenn.dev/tmb/articles/e4fb3fe350852f

使い方

// 対象のオブジェクト(target)のShowアニメーションを実行
AnimationManager.Instance.AddAnimationObject(target, "Show");

// 対象のオブジェクト(target)のHideアニメーションを実行
AnimationManager.Instance.AddAnimationObject(target, "Hide");
変数 説明
GameObject obj Animatorをアタッチしたオブジェクト
string stateName どのステートのアニメーションをさせるか
Animator animator アニメーター
Action beforeAction アニメーション開始前に実行するアクション
Action afterAction アニメーション終了後に実行するアクション

スクリプト

アニメーションマネージャー
AnimationManager.cs
using System;
using System.Collections.Generic;
using UnityEngine;
using UniRx;
using Cysharp.Threading.Tasks;
using System.Threading;
using System.Linq;

namespace Utils
{
    sealed public class AnimationManager
    {
        private static AnimationManager _instance = null;
        public static AnimationManager Instance
        {
            get
            {
                _instance ??= new AnimationManager();
                return _instance;
            }
        }

        private List<AnimationObject> _animationObjects = new List<AnimationObject>();

        /// <summary>
        /// アニメーションする対象を追加
        /// </summary>
        public AnimationObject AddAnimationObject(GameObject obj, string stateName, Animator animator = null, Action beforeAction = null, Action afterAction = null)
        {
            if (string.IsNullOrEmpty(stateName))
            {
                return null;
            }

            if (animator == null)
            {
                animator = obj.GetComponent<Animator>();
                if (animator == null)
                {
                    return null;
                }
            }

            AnimationObject existingAnimationObject = _animationObjects.FirstOrDefault(a => a.obj == obj);
            if (existingAnimationObject != null)
            {
                CancelAnimation(existingAnimationObject);
                _animationObjects.Remove(existingAnimationObject);
            }

            AnimationObject animationObject = new AnimationObject()
            {
                obj = obj,
                animator = animator,
                stateName = stateName,
                beforeAction = beforeAction,
                afterAction = afterAction,
                ctsHelper = new CancellationTokenHelper()
            };
            _animationObjects.Add(animationObject);

            ApplyAndPlayAnimation(animationObject).Forget();

            return animationObject;
        }

        /// <summary>
        /// アニメーションする対象を事前読み込み
        /// </summary>
        public AnimationObject PreloadAddAnimationObject(GameObject obj, string stateName, Animator animator = null, Action beforeAction = null, Action afterAction = null)
        {
            if (string.IsNullOrEmpty(stateName))
            {
                return null;
            }

            if (animator == null)
            {
                animator = obj.GetComponent<Animator>();
                if (animator == null)
                {
                    return null;
                }
            }

            AnimationObject existingAnimationObject = _animationObjects.FirstOrDefault(a => a.obj == obj);
            if (existingAnimationObject != null)
            {
                CancelAnimation(existingAnimationObject);
                _animationObjects.Remove(existingAnimationObject);
            }

            AnimationObject animationObject = new AnimationObject()
            {
                obj = obj,
                animator = animator,
                stateName = stateName,
                beforeAction = beforeAction,
                afterAction = afterAction,
                ctsHelper = new CancellationTokenHelper()
            };
            _animationObjects.Add(animationObject);

            return animationObject;
        }

        /// <summary>
        /// 事前に読み込んだアニメーションオブジェクトを実行
        /// </summary>
        public void ActivatePreloadedAnimation(AnimationObject animationObject)
        {
            ApplyAndPlayAnimation(animationObject).Forget();
        }

        /// <summary>
        /// アニメーションプロパティの適用
        /// </summary>
        public void ApplyAnimationProperties(Animator animator, GameObject target, string stateName)
        {
            AnimationController.ApplyProperties(animator, target, stateName);
        }

        /// <summary>
        /// アニメーションを再生
        /// </summary>
        public async UniTask PlayAnimation(Animator animator, string stateName, Action beforeAction, Action afterAction, CancellationToken token)
        {
            await AnimationController.Play(animator, stateName, beforeAction, afterAction, token);
        }

        /// <summary>
        /// アニメーションの時間を取得
        /// </summary>
        public float GetAnimationTime(AnimationObject animationObject)
        {
            return AnimationController.GetAnimationTime(animationObject.animator, animationObject.stateName);
        }

        /// <summary>
        /// アニメーションをキャンセル
        /// </summary>
        public void CancelAnimation()
        {
            foreach (var animationObject in _animationObjects)
            {
                CancelAnimation(animationObject);
            }
        }

        /// <summary>
        /// 特定のアニメーションをキャンセル
        /// </summary>
        public void CancelAnimation(AnimationObject animationObject)
        {
            animationObject.ctsHelper.Dispose();
            _animationObjects.Remove(animationObject);
        }

        /// <summary>
        /// アニメーションを適用し再生する処理
        /// </summary>
        private async UniTaskVoid ApplyAndPlayAnimation(AnimationObject animationObject)
        {
            ApplyAnimationProperties(animationObject.animator, animationObject.obj, animationObject.stateName);
            try
            {
                await PlayAnimation(animationObject.animator, animationObject.stateName, animationObject.beforeAction, animationObject.afterAction, animationObject.ctsHelper.Token);
            }
            catch (OperationCanceledException)
            {
                // キャンセル時の処理
            }
            _animationObjects.Remove(animationObject);
        }
    }
}
アニメーションコントローラー
AnimationController.cs
using System;
using UnityEngine;
using Cysharp.Threading.Tasks;
using System.Threading;
using System.Linq;

namespace Utils
{
    [Serializable]
    sealed public class AnimationController
    {
        /// <summary>
        /// 最初のフレームのプロパティを適用
        /// </summary>
        static public void ApplyProperties(Animator animator, GameObject target, string stateName)
        {
            // アニメーションクリップを取得
            AnimationClip animationClip = animator.runtimeAnimatorController.animationClips.FirstOrDefault(clip => clip.name == stateName);
            if (animationClip != null)
            {
                // アニメーションの初期フレームをターゲットに適用
                animator.enabled = false;
                animationClip.SampleAnimation(target, 0);
            }
        }

        /// <summary>
        /// 再生処理
        /// </summary>
        static public async UniTask Play(Animator animator, string stateName, Action beforeAction, Action afterAction, CancellationToken token)
        {
            beforeAction?.Invoke();

            animator.enabled = true;

            // アニメーションの再生
            await PlayAnimation(animator, stateName, token);

            afterAction?.Invoke();
        }

        /// <summary>
        /// アニメーションを実行し、完了を待つ
        /// </summary>
        static private async UniTask PlayAnimation(Animator animator, string stateName, CancellationToken token)
        {
            if (animator == null)
            {
                return;
            }

            animator.Play(stateName);

            await UniTask.WaitUntil(() =>
            {
                if (animator == null || animator.gameObject == null)
                {
                    return true;
                }

                return animator.GetCurrentAnimatorStateInfo(0).IsName(stateName) &&
                       animator.GetCurrentAnimatorStateInfo(0).normalizedTime >= 1.0f;
            }, cancellationToken: token);
        }

        /// <summary>
        /// 指定されたステート名のアニメーションの時間を取得
        /// </summary>
        static public float GetAnimationTime(Animator animator, string stateName)
        {
            if (animator == null)
            {
                return 0.0f;
            }

            AnimationClip animationClip = animator.runtimeAnimatorController.animationClips.FirstOrDefault(clip => clip.name == stateName);
            return animationClip != null ? animationClip.length : 0f;
        }
    }
}
アニメーション対象のオブジェクト
AnimationObject.cs
using System;
using UnityEngine;

namespace Utils
{
    sealed public class AnimationObject
    {
        public GameObject obj;
        public Animator animator;
        public string stateName;
        public Action beforeAction;
        public Action afterAction;
        public CancellationTokenHelper ctsHelper;
    }
}

Discussion