一、设计初衷
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
}
}