[Serializable]
public class AnimatorParameter
{
[SerializeField]
private RuntimeAnimatorController animator;
[SerializeField]
private AnimatorControllerParameterType type;
[SerializeField]
private string name;
private int? hash;
public AnimatorControllerParameterType Type => type;
public string Name => name;
public RuntimeAnimatorController RuntimeAnimatorController;
public int Hash
{
get
{
if (hash == null)
{
hash = Animator.StringToHash(name);
}
return hash.Value;
}
}
#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(AnimatorParameter))]
private class AnimatorParameterDrawer : PropertyDrawer
{
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return EditorGUIUtility.singleLineHeight * 2;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
using (new EditorGUI.PropertyScope(position, label, property))
{
position = EditorGUI.PrefixLabel(position, label);
var controllerRect = position;
controllerRect.height = EditorGUIUtility.singleLineHeight;
position.y += controllerRect.height;
var controllerProperty = property.FindPropertyRelative(nameof(animator));
using (var change = new EditorGUI.ChangeCheckScope())
{
EditorGUI.PropertyField(controllerRect, controllerProperty, GUIContent.none);
if (!controllerProperty.objectReferenceValue)
{
var infoRect = position;
infoRect.height = EditorGUIUtility.singleLineHeight;
EditorGUI.HelpBox(infoRect, "No animator referenced!", MessageType.Error);
}
else
{
var typeRect = position;
typeRect.width = 80;
typeRect.height = EditorGUIUtility.singleLineHeight;
position.x += typeRect.width;
var nameRect = position;
nameRect.height = EditorGUIUtility.singleLineHeight;
nameRect.width -= typeRect.width;
var typeProperty = property.FindPropertyRelative(nameof(type));
EditorGUI.PropertyField(typeRect, typeProperty, GUIContent.none);
var options = ((AnimatorController)controllerProperty.objectReferenceValue).parameters
.Where(p => p.type == typeProperty.GetEnumValue<AnimatorControllerParameterType>())
.Select(p => p.name)
.ToList();
var nameProperty = property.FindPropertyRelative(nameof(name));
var currentIndex = options.IndexOf(nameProperty.stringValue);
var newIndex = EditorGUI.Popup(nameRect, currentIndex, options.ToArray());
nameProperty.stringValue = newIndex >= 0 && newIndex < options.Count ? options[newIndex] : "";
}
if (change.changed)
{
var target = (AnimatorParameter)fieldInfo.GetValue(property.serializedObject.targetObject);
target.hash = null;
fieldInfo.SetValue(property.serializedObject.targetObject, target);
}
}
}
}
}
#endif
}
为了让事情变得简单一点,你甚至可以添加一些扩展方法,比如:
public static class AnimatorExtensions
{
public static void SetFloat(this Animator animator, AnimatorParameter animatorParameter, float value)
{
if (animatorParameter.Type != AnimatorControllerParameterType.Float)
{
throw new ArgumentException("Given parameter is not of type Float!");
}
if (animator.runtimeAnimatorController != animatorParameter.RuntimeAnimatorController)
{
Debug.LogWarning("AnimatorControllers do not match!");
}
animator.SetFloat(animatorParameter.Hash, value);
}
public static void SetInteger(this Animator animator, AnimatorParameter animatorParameter, int value)
{
if (animatorParameter.Type != AnimatorControllerParameterType.Int)
{
throw new ArgumentException("Given parameter is not of type Int!");
}
if (animator.runtimeAnimatorController != animatorParameter.RuntimeAnimatorController)
{
Debug.LogWarning("AnimatorControllers do not match!");
}
animator.SetInteger(animatorParameter.Hash, value);
}
public static void SetBool(this Animator animator, AnimatorParameter animatorParameter, bool value)
{
if (animatorParameter.Type != AnimatorControllerParameterType.Bool)
{
throw new ArgumentException("Given parameter is not of type Bool!");
}
if (animator.runtimeAnimatorController != animatorParameter.RuntimeAnimatorController)
{
Debug.LogWarning("AnimatorControllers do not match!");
}
animator.SetBool(animatorParameter.Hash, value);
}
public static void SetTrigger(this Animator animator, AnimatorParameter animatorParameter)
{
if (animatorParameter.Type != AnimatorControllerParameterType.Trigger)
{
throw new ArgumentException("Given parameter is not of type Trigger!");
}
if (animator.runtimeAnimatorController != animatorParameter.RuntimeAnimatorController)
{
Debug.LogWarning("AnimatorControllers do not match!");
}
animator.SetTrigger(animatorParameter.Hash);
}
public static void ResetTrigger(this Animator animator, AnimatorParameter animatorParameter)
{
if (animatorParameter.Type != AnimatorControllerParameterType.Trigger)
{
throw new ArgumentException("Given parameter is not of type Trigger!");
}
if (animator.runtimeAnimatorController != animatorParameter.RuntimeAnimatorController)
{
Debug.LogWarning("AnimatorControllers do not match!");
}
animator.ResetTrigger(animatorParameter.Hash);
}
public static float GetFloat(this Animator animator, AnimatorParameter animatorParameter)
{
if (animatorParameter.Type != AnimatorControllerParameterType.Float)
{
throw new ArgumentException("Given parameter is not of type Float!");
}
if (animator.runtimeAnimatorController != animatorParameter.RuntimeAnimatorController)
{
Debug.LogWarning("AnimatorControllers do not match!");
}
return animator.GetFloat(animatorParameter.Hash);
}
public static int GetInteger(this Animator animator, AnimatorParameter animatorParameter)
{
if (animatorParameter.Type != AnimatorControllerParameterType.Int)
{
throw new ArgumentException("Given parameter is not of type Integer!");
}
if (animator.runtimeAnimatorController != animatorParameter.RuntimeAnimatorController)
{
Debug.LogWarning("AnimatorControllers do not match!");
}
return animator.GetInteger(animatorParameter.Hash);
}
public static bool GetBool(this Animator animator, AnimatorParameter animatorParameter)
{
if (animatorParameter.Type != AnimatorControllerParameterType.Bool)
{
throw new ArgumentException("Given parameter is not of type Bool!");
}
if (animator.runtimeAnimatorController != animatorParameter.RuntimeAnimatorController)
{
Debug.LogWarning("AnimatorControllers do not match!");
}
return animator.GetBool(animatorParameter.Hash);
}
}
这里有一个小演示,它现在看起来会是什么样子(可能会改进)
public class Example : MonoBehaviour
{
public Animator animator;
public AnimatorParameter animatorParameter;
[ContextMenu(nameof(Apply))]
public void Apply()
{
switch (animatorParameter.Type)
{
case AnimatorControllerParameterType.Float:
animator.SetFloat(animatorParameter, Random.value);
break;
case AnimatorControllerParameterType.Int:
animator.SetInteger(animatorParameter, Random.Range(0, 5));
break;
case AnimatorControllerParameterType.Bool:
animator.SetBool(animatorParameter, !animator.GetBool(animatorParameter));
break;
case AnimatorControllerParameterType.Trigger:
animator.SetTrigger(animatorParameter);
break;
}
}
}
这样做的问题是,触发器的名称可能会更改,或者在某个时候被完全删除,然后我只能在运行时发现问题,甚至更糟,在生产中。 当然,同样的问题仍然存在。不幸的是,动画中的很多东西(和其他)系统是基于字符串的(即使是renaming/re-arranging of objects within the hierarchy也是一个问题)。你 * 可以 * 可能在它周围实现一些 Package 器,而不是直接删除/重命名触发器等,只需要通过你的自定义代码,基本上通知/验证AnimatorParameter的每个示例,更新它们的值并重新序列化它们的资产。这将变得相当复杂,虽然可能不值得努力。 例如,您可以在测试中进行简单的验证,如
public static class AnimatorParameterExtensions
{
public static void Validate(this AnimatorParameter animatorParameter)
{
Assert.IsNotNull(animatorParameter);
Assert.AreEqual(true, (bool)animatorParameter.Animator);
Assert.AreEqual(false, string.IsNullOrWhiteSpace(animatorParameter.Name));
Assert.AreEqual(true, animatorParameter.Animator.parameters.Where(p => p.type == animatorParameter.Type).Any(p => p.name == animatorParameter.Name));
}
}
3条答案
按热度按时间mf98qq941#
不幸的是,没有。它们不是可以通过正常的
var_of_type.name_of_prop_or_var
方式访问的变量。有一个抽象层将字符串转换为Map属性,即实际触发器。如果您使用AudioMixer,则暴露音频属性(例如音量)的变量以相同的方式工作。虽然你可以建立一个集中的关系,比如Dictionary将你自己的属性名Map到一个内部的animator属性,但是结果仍然是一个字符串。不同的是它是集中的,当填充字典时的一个改变不会影响使用键的代码,而不是值。问题仍然存在,虽然代码的风险会更小,Animator仍将使用其自己的参数列表,因此,您也需要在那里更改它。
不过,我不想在没有解决您的顾虑的情况下用一个基本的“不”来结束这个答案,您提到过您担心在运行时或生产中会发生错误。
受控的运行时会话,如单元测试或全面测试,正是为了达到这个目的。(端到端测试、A/B测试和最后但并非最不重要的回归测试),他们应该能够发现代码中可能发生此类问题的任何不一致之处,在我看来,这是值得鼓励的,因为这是测试的重点之一。如果测试做得正确,它发生在最终产品发布的机会是渺茫的。
所以简而言之:虽然没有办法在构建时自动检查,但只要测试遵循正确的指导原则,我不会担心测试过程中的错误。然而,如果您觉得需要过于频繁地更改参数名称,则可能存在不同的问题,无论是在一致性、设计还是影响您的开发的任何其他方面,我会首先解决这个问题。
ffvjumwh2#
这是你在测试时不能忽略的,因为它会破坏你游戏的动画行为,如果你使用了错误的字符串,你会看到错误。只是为了让事情更容易,从一个地方处理,为了优化的目的,我会使用Animator.StringToHash函数。
如果你愿意,你也可以在运行时使用Animator.parameters来运行一些手动检查,但我认为这不值得。如果这些参数在构建时也准备好了,那么你可以明确地使用它们。
olmpazwi3#
如前所述,没有内置的东西,但您可以使用自定义编辑器脚本来实现类似的功能。
例如,您可以有一个自定义属性类型,它带有一个自定义编辑器,该编辑器可以提取
Animator.parameters
,过滤特定类型,并显示一个包含名称的下拉列表。然后在运行时缓存
Aniamtor.StringToHash
。为了让事情变得简单一点,你甚至可以添加一些扩展方法,比如:
这里有一个小演示,它现在看起来会是什么样子(可能会改进)
这样做的问题是,触发器的名称可能会更改,或者在某个时候被完全删除,然后我只能在运行时发现问题,甚至更糟,在生产中。
当然,同样的问题仍然存在。不幸的是,动画中的很多东西(和其他)系统是基于字符串的(即使是renaming/re-arranging of objects within the hierarchy也是一个问题)。你 * 可以 * 可能在它周围实现一些 Package 器,而不是直接删除/重命名触发器等,只需要通过你的自定义代码,基本上通知/验证
AnimatorParameter
的每个示例,更新它们的值并重新序列化它们的资产。这将变得相当复杂,虽然可能不值得努力。例如,您可以在测试中进行简单的验证,如
我实际上希望看到他们实现一些东西,比如在新的输入系统中,你的整个自定义输入操作都会生成到c#类中,所以如果你重命名它们,你也会立即得到一堆错误。
实际上,您可以尝试实现类似的东西-我不是代码生成Maven,但这可能会给您一个很好的起点
然后将所有控制器生成到一个类中(对我来说,重新格式化后看起来像e.g.)
(of课程名称冲突目前可能是一个问题-正如所说,远非完美),您可以稍后(例如
这样,如果参数被重命名/删除/重新键入等,您将得到实际的编译器错误