I created a custom PropertyDrawer that for a certain field decorated with my attribute, shows the parameters from the animator object referenced in the same script.
That way, I can make sure the field's value is a valid parameter in the referenced animator.
This is my code:
public class AnimationParamAttribute : PropertyAttribute
{
public string AnimatorName { get; }
public AnimatorControllerParameterType[] AllowedParameters { get; }
public AnimationParamAttribute(string animatorName, params AnimatorControllerParameterType[] allowedParameters)
{
AnimatorName = animatorName;
this.AllowedParameters = allowedParameters;
}
}
public class AnimationController : MonoBehaviour
{
[SerializeField] private Animator animator;
[SerializeField, AnimationParam(nameof(animator), AnimatorControllerParameterType.Trigger)]
private string trigger;
[ContextMenu("Print value")]
private void PrintValue()
{
Debug.Log(trigger);
}
public void StartAnimation()
{
animator.SetTrigger(trigger);
}
}
And my PropertyDrawer
looks like this:
[CustomPropertyDrawer(typeof(AnimationParamAttribute))]
public class AnimationParamDrawer : PropertyDrawer
{
// Draw the property inside the given rect
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
AnimationParamAttribute a = (AnimationParamAttribute)attribute;
if (property.propertyType == SerializedPropertyType.String)
{
var targetObject = property.serializedObject.targetObject;
var field = targetObject.GetType().GetField(a.AnimatorName,
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
Animator animator = field?.GetValue(targetObject) as Animator;
if (animator == null)
{
EditorGUI.LabelField(position, label.text, "Select an animator.");
return;
}
var options = animator.parameters
.Where(parameter => a.AllowedParameters.Contains(parameter.type))
.Select(parameter => parameter.name)
.ToArray();
var s = string.Join(", ", options);
Debug.Log($"Options: {s}");
int selection = Array.IndexOf(options, property.stringValue);
Debug.Log($"{property.stringValue} is option {selection}");
if (selection < 0) selection = 0;
position = EditorGUI.PrefixLabel(position, label);
EditorGUI.BeginProperty(position, label, property);
EditorGUI.BeginChangeCheck();
selection = EditorGUI.Popup(position, selection, options);
if (EditorGUI.EndChangeCheck())
{
Debug.Log($"New selection: {selection}");
property.stringValue = options[selection];
}
EditorGUI.EndProperty();
}
else
EditorGUI.LabelField(position, label.text, "Use with string fields.");
}
}
The code works fine, but for some reason when I change the parameters in the animator object, animator.parameters
returns an empty array.
When I modify the code to force Unity to recompile, I get the correct values and the code works again.
What is the reason for this, and how can I fix it?
CodePudding user response:
In your AnimationPropertyDrawer
file, you want to "rebind" all of the properties for us to see the most up-to-date values. This is an Editor PropertyDrawer, so we come to expect some of the magic comes at a price. Fortunately, this won't carry across to the build.
Try this:
Animator animator = field?.GetValue(targetObject) as Animator;
if ( animator == null )
{
EditorGUI.LabelField ( position, label.text, "Select an animator." );
return;
}
animator.Rebind ( );
var options = animator.parameters
.Where(parameter => a.AllowedParameters.Contains(parameter.type))
.Select(parameter => parameter.name)
.ToArray();
Take note of the extra line animator.Rebind ( );
.