【Unity】使用C#泛型类包装Animator参数——优化Animator使用体验

一、设计初衷

1、优化Aniamtor繁琐的参数获取和设置方法 :

Unity官方的各个参数的Get方法以及Set方法实际用起来很不方便

例如:
浮点类型Get方法:
public float GetFloat (string name);
public float GetFloat (int id);
浮点类型Set方法:
public void SetFloat (string name, float value);
public void SetFloat (string name, float value, float dampTime, float deltaTime);
public void SetFloat (int id, float value);
public void SetFloat (int id, float value, float dampTime, float deltaTime);

不难看出,当你想要获取/设置某个你已知的Animator中存在的参数值时,你的心里需要知道:
1、参数类型,然后调用对应的方法(GetFloat/GetInteger/GetBool...)
2、参数名称/哈希值,这种绑定方式很不稳定,特别是你需要在脚本中多个地方调用Get/Set方法,每次都需要提供参数名称/哈希值

 

2、改善引用难管理的问题

我相信肯定不止作者我一个人对Animator的参数引用管理发过愁,看着不能“查看所有引用”的Animator面板,以及脚本中满屏幕的通过参数名称/哈希值调用的Set方法,心中肯定一股无奈。(这也是我萌生将Animator参数对象化的主要原因)

 

二、使用示例

1、模拟参数中央管理者

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AnimatorController : MonoBehaviour
{
    public Animator animator;

    public AnimatorParameters<float> TestFloat;
    public AnimatorParameters<bool> Testbool;
    public AnimatorParameters<Trigger> TestTrigger;
    public AnimatorParameters<int> TestInt;

    private void Start()
    {
        //变量获取(推荐对象名和状态机参数名相同)
        TestFloat = new AnimatorParameters<float>(nameof(TestFloat), animator);
        Testbool = new AnimatorParameters<bool>(nameof(Testbool),animator);
        TestTrigger = new AnimatorParameters<Trigger>(nameof(TestTrigger),animator);
        TestInt = new AnimatorParameters<int>(nameof(TestInt),animator);

        //直接设值
        TestFloat.SetValue(233.0f);
        Testbool.SetValue(true);
        TestTrigger.SetValue(Trigger.Trigger);
        TestInt.SetValue(233);

        //在5秒时间内这些变量均“归零”
        TestFloat.DOTweenSetValue(0, 5);
        Testbool.DOTweenSetValue(false, 5);
        TestTrigger.DOTweenSetValue(Trigger.ResetTrigger, 5);
        TestInt.DOTweenSetValue(0,5);
    }
}

 

2、模拟运行效果

1、正常运行:


2、名称不匹配错误:

3、类型不匹配错误:

 

三、源码部分

除基本的Animator的Get/Set方法封装之外,博主也用Dotween简单的包装了一下插值设值方法。如果你希望有更多功能封装,那么自行对源码进行增删/扩展即可。


using System;
using UnityEngine;
using UnityEngine.Assertions;

#if DOTWEEN
using DG.Tweening;
#endif

namespace Tools.Common.Anime.Animator
{
    public class AnimatorParameters<T> where T : struct
    {
        private readonly string _parameterName;
        private readonly UnityEngine.Animator _animator;

        public AnimatorParameters(string name, UnityEngine.Animator playerAnimator)
        {
            Assert.IsTrue(typeof(T) == typeof(int) || typeof(T) == typeof(float) || typeof(T) == typeof(bool) || typeof(T) == typeof(Trigger), string.Format("该泛型类型仅支持状态机支持的4种参数类型,显然{0}不是其中任何一种", name));

            Assert.IsTrue(IsParameterValid(name, playerAnimator, out string animatorTypeName), animatorTypeName);

            _parameterName = name;
            _animator = playerAnimator;
        }

        /// <summary>
        /// 向传入的状态机中检测目标名称的参数是否真实存在,同时检测脚本中的泛型实际类型是否填入正确
        /// </summary>
        /// <param name="name">状态机中的参数名</param>
        /// <param name="animator">持有该参数的状态机</param>
        /// <param name="errorMessage">可能的错误信息</param>
        /// <returns>若成功在传入的状态机中找到了指定名称的参数,并且与泛型类型匹配则返回true,否则返回false</returns>
        private static bool IsParameterValid(string name, UnityEngine.Animator animator, out string errorMessage)
        {
            foreach (var parameter in animator.parameters)
            {
                if (parameter.name == name)
                {
                    errorMessage = $"Parameters TypeMismatch!:名为{animator.name}的状态机中找到了名为{name}的参数,但类型不匹配,状态机中类型为:{parameter.type.ToString()},脚本中类型为:{typeof(T).ToString()}";
                    switch (parameter.type)
                    {
                        case AnimatorControllerParameterType.Float:
                            if (typeof(T) == typeof(float)) return true;
                            else return false;
                        case AnimatorControllerParameterType.Int:
                            if (typeof(T) == typeof(int)) return true;
                            else return false;
                        case AnimatorControllerParameterType.Bool:
                            if (typeof(T) == typeof(bool)) return true;
                            else return false;
                        case AnimatorControllerParameterType.Trigger:
                            if (typeof(T) == typeof(Trigger)) return true;
                            else return false;
                        default:
                            return false;
                    }
                }
            }

            errorMessage = $"Parameters NameMismatch!:名为{animator.name}的状态机中并未找到名为{name}的参数";
            return false;
        }

        /// <summary>
        /// 获取该参数在状态机的当前值
        /// </summary>
        /// <returns>返回状态机中该参数当前的值</returns>
        public T GetValue()
        {
            // 判断泛型类型 T,执行相应的方法并返回值
            if (typeof(T) == typeof(float))
            {
                return (T)(object)_animator.GetFloat(_parameterName);
            }
            else if (typeof(T) == typeof(int))
            {
                return (T)(object)_animator.GetInteger(_parameterName);
            }
            else if (typeof(T) == typeof(bool))
            {
                return (T)(object)_animator.GetBool(_parameterName);
            }
            else
            {
                return default;
            }
        }

        /// <summary>
        /// 直接设置状态机中该参数的值
        /// </summary>
        /// <param name="newValue">目标值</param>
        public void SetValue(T newValue)
        {
            Type type = typeof(T);
            // 判断泛型类型 T,执行相应的方法并返回值
            if (type == typeof(float))
            {
                _animator.SetFloat(_parameterName, (float)(object)newValue);
            }
            else if (type == typeof(int))
            {
                _animator.SetInteger(_parameterName, (int)(object)newValue);
            }
            else if (type == typeof(bool))
            {
                _animator.SetBool(_parameterName, (bool)(object)newValue);
            }
            else if (type == typeof(Trigger))
            {
                var trigger = (Trigger)(object)newValue;
                switch (trigger)
                {
                    case Trigger.Trigger:
                        _animator.SetTrigger(_parameterName);
                        break;
                    case Trigger.ResetTrigger:
                        _animator.ResetTrigger(_parameterName);
                        break;
                    default:
#if UNITY_EDITOR
                        Debug.LogError($"{type} Type but Unhandled case:{trigger.ToString()}");
#endif
                        break;
                }
            }
            else
            {
#if UNITY_EDITOR
                Debug.LogError($"Unhandled type:{type}");
#endif
            }
        }

#if DOTWEEN
        private Tween _currentValueTween = null;
        private bool _doTween = false; //正在执行DoTween动画设值
        /// <summary>
        /// 创建DoTween动画平滑设值(如果是bool或trigger则为计时器设目标值),请注意一个参数仅能有一个动画设值,新调用方法会杀掉旧方法设置的动画
        /// </summary>
        /// <param name="targetValue">在指定时间过后,该参数的值到达目标值</param>
        /// <param name="durationTime">过渡时间</param>
        /// <param name="callBack">当动画设值完成后的回调委托</param>
        public void DoTweenSetValue(T targetValue, float durationTime, TweenCallback callBack = null)
        {
            if (_doTween) _currentValueTween.Kill(); //若该参数已经有DoTween动画正在执行,则关闭当前正在执行的动画操作

            _doTween = true; //标注正在转换值

            if (typeof(T) == typeof(bool) || typeof(T) == typeof(Trigger))
            {
                //对于bool或trigger类型的参数,只需要计时器即可,计时完成设置成目标值就行

                float currentValue = 0;
                _currentValueTween = DOTween.To(() => currentValue, x => currentValue = x, durationTime, durationTime)
                    .OnComplete(() =>
                    {
                        SetValue(targetValue);
                        _currentValueTween =
                            null;
                        callBack?.Invoke();
                        _doTween = false;
                    });
            }
            else
            {
                var convertedCurrentValue = typeof(T) == typeof(float) ? (float)(object)GetValue() : (int)(object)GetValue();
                var convertedTargetValue = typeof(T) == typeof(float) ? (float)(object)targetValue : (int)(object)targetValue;

                //将插值计时器每帧的结果通过SetValue实时设置到状态机
                _currentValueTween = DOTween.To(() => convertedCurrentValue, x => convertedCurrentValue = x, convertedTargetValue, durationTime)
                    .OnUpdate(() => { SetValue((T)Convert.ChangeType(convertedCurrentValue, typeof(T))); })
                    .OnComplete(() =>
                    {
                        _currentValueTween = null;
                        callBack?.Invoke();
                        _doTween = false;
                    });
            }
        }
#endif
    }

    /// <summary>
    ///  用来模拟状态机参数Trigger类型的
    /// </summary>
    public enum Trigger
    {
        Trigger,
        ResetTrigger
    }
}
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
下一篇