Using Action Resolvers

Action resolvers are the simplest of the two kinds of resolvers; they take a string and "execute" it, without returning or providing a value of any kind. Examples of attributes that use action resolvers would be OnValueChanged, OnCollectionChanged and InlineButton.

They're quite easy to use - let's make a simple attribute that draws a button above a property to invoke an action.

First, we start with the basics - making an attribute and a drawer for it. You can see this tutorial for the basics of creating attribute drawers:

// ActionButtonAttribute.cs
using System;

public class ActionButtonAttribute : Attribute
{
    public string Action;

    public ActionButtonAttribute(string action)
    {
        this.Action = action;
    }
}

// ActionButtonAttributeDrawer.cs
using Sirenix.OdinInspector.Editor;
using UnityEditor;

public class ActionButtonAttributeDrawer : OdinAttributeDrawer<ActionButtonAttribute>
{
    protected override void DrawPropertyLayout(GUIContent label)
    {
        if (GUILayout.Button("Perform Action"))
        {
            // TODO: Perform action
        }
        
        this.CallNextDrawer(label);
    }
}

Now we have an attribute that draws a button above the member you put it on - now we just need to execute the Action string as an action when the button is clicked! For this, we need to add the Sirenix.OdinInspector.Editor.ActionResolvers namespace, create an action resolver in the drawer's Initialize() method, pass in the Action string from the attribute, and then invoke the action resolver when the button is clicked.

// ActionButtonAttributeDrawer.cs
using Sirenix.OdinInspector.Editor;
using Sirenix.OdinInspector.Editor.ActionResolvers;
using UnityEditor;

public class ActionButtonAttributeDrawer : OdinAttributeDrawer<ActionButtonAttribute>
{
    private ActionResolver actionResolver;
    
    protected override void Initialize()
    {
        this.actionResolver = ActionResolver.Get(this.Property, this.Attribute.Action);
    }
    
    protected override void DrawPropertyLayout(GUIContent label)
    {
        if (GUILayout.Button("Perform Action"))
        {
            this.actionResolver.DoActionForAllSelectionIndices();
        }
        
        this.CallNextDrawer(label);
    }
}

This is enough to get it working! We can now make use of the ActionButton attribute, and do things like reference a method or use an expression:

// Example.cs
using UnityEngine;

public class Example : MonoBehaviour
{
    [ActionButton("@UnityEngine.Debug.Log(\"Action invoked with attribute expressions!\")")]
    public string expressionActions;

    [ActionButton("DoAction")]
    public string methodReferenceActions;

    private void DoAction()
    {
        Debug.Log("Action invoked with method reference!");
    }
}

However, there's an issue here! Currently, if we pass in a bad string, we won't get any visual feedback at all - the button will just silently do nothing! We need to tell the resolver to draw errors, if there are any. It will generate a decent error message, and provide the necessary feedback. This is simply done by calling DrawError() on the resolver; if there is an error, it will be drawn.

// ActionButtonAttributeDrawer.cs
using Sirenix.OdinInspector.Editor;
using Sirenix.OdinInspector.Editor.ActionResolvers;
using UnityEditor;

public class ActionButtonAttributeDrawer : OdinAttributeDrawer<ActionButtonAttribute>
{
    private ActionResolver actionResolver;
    
    protected override void Initialize()
    {
        this.actionResolver = ActionResolver.Get(this.Property, this.Attribute.Action);
    }
    
    protected override void DrawPropertyLayout(GUIContent label)
    {
        this.actionResolver.DrawError();
        
        if (GUILayout.Button("Perform Action"))
        {
            this.actionResolver.DoActionForAllSelectionIndices();
        }
        
        this.CallNextDrawer(label);
    }
}

And now if we pass in a bad string to the attribute, we will get a helpful error message in the inspector that explains which kinds of strings can be resolved as an action:

// Example.cs
using UnityEngine;

public class Example : MonoBehaviour
{
    [ActionButton("Bad String!")]
    public string methodReferenceActions;
}

And there we have it. As you can see, using action resolvers practically couldn't be easier!

More to learn

However, we're not done. As you can see in the error message above, it gives a list of something called "named values" - just what are those? Well, they're a huge part of the power and convenience of value and action resolvers.

Let's go take a look at Named Values, or perhaps you want to check out Value Resolvers next?