Editor-Scripting : event for a serialized property changed

Hi guys,

so, I’m trying to make a script, an Editor, that will allow me to change a value - and see the result of the change immediately while in editor mode, e.g. not running the game.

I manage to this, it’s working fine - but I cannot escape the feeling that it may be there a more elegant or simpler solution than the one I’m using. I searched extensively the net, but couldn’t find a better solution. If I’m missing something, please help.

public override void OnInspectorGUI()
{
    serializedObject.Update();
      
    EditorGUI.BeginChangeCheck();
    EditorGUILayout.PropertyField(cardSuite);
    serializedObject.ApplyModifiedProperties();
    if (EditorGUI.EndChangeCheck())
    {
        _cardScript.ChangeSuite();
    }
      
    EditorGUI.BeginChangeCheck();
    EditorGUILayout.PropertyField(cardNumber);
    serializedObject.ApplyModifiedProperties();
    if (EditorGUI.EndChangeCheck())
    {
       _cardScript.ChangeNumber();
    }
      
    EditorGUI.BeginChangeCheck();
    EditorGUILayout.PropertyField(isFlipped);
    serializedObject.ApplyModifiedProperties();
    if (EditorGUI.EndChangeCheck())
    {
       _cardScript.Flip();
    }
}

cardNumber and cardSuite are enums type, which will produce dropdown control in the Editor. isFliped is a boolean value, which will produce a checkbox control in the Editor.

So, this is working fine - but is there a better solution?

I imagine this will become a huge mess if there are 10+ properties…

1 Like

I’m not sure if you need to call the EditorGUI.BeginChangeCheck() and EditorGUI.EndChangeCheck() multiple times; but instead use one call; but if it is required, you can refactor your code to make it easier to handle new properties in the future.

First, define a struct which will hold a reference to your SerializedProperty and an Action<> delegate to call the method on your _cardScript instance. For the sake of the code below, I’ll assume that the type of _cardScript is a class named CardScript.

public struct PropertyAction
{
    public PropertyAction(SerializedProperty property, Action<CardScript> action)
    {
        this.property = property;
        this.action = action;
    }
  
    public SerializedProperty property;
    public Action<CardScript> action;
}

Create a member in your class which is a list of these PropertyActions, which you will initialize after you’ve initialized the SerializedProperty references to cardSuite, cardNumber and isFlipped, like so:

List<PropertyAction> propertyActions;

At an appropriate place in your script, after you’ve retrieved your SerializedProperty references, initialize the list of PropertyAction structs (this only needs to be done once, and to handle new properties, all you need to do is add a new entry to the initializer list. It will look something like this:

void Awake()
{
    // Get references to SerializedProperties here first...
    
    // Initialize property and method delegates
    this.propertyActions = new List<PropertyAction>
    {
        new PropertyAction(cardSuite, (cs) => cs.ChangeSuite()),
        new PropertyAction(cardNumber, (cs) => cs.ChangeNumber()),
        new PropertyAction(isFlipped, (cs) => cs.Flip())
        // Add new property/action combinations here....
    };
}

Now, at this point, you add code to your OnInspectorGUI() which will iterate through this list and perform the same actions for each property in your list. It will check for the change, apply modified properties and if changed, call the appropriate action on your _cardScript instance. This code only needs to be written once!

public override void OnInspectorGUI()
{
    serializedObject.Update();
  
    this.propertyActions.ForEach(propertyAction =>
    {
        EditorGUI.BeginChangeCheck();
        EditorGUILayout.PropertyField(propertyAction.property);
        base.serializedObject.ApplyModifiedProperties();
  
        if (EditorGUI.EndChangeCheck())
        {
            propertyAction.action(this._cardScript);
        }
    });
}

If all of your properties follow the same pattern, then all you need to do is add one new entry to the list, which is the SerializedProperty reference and a delegate to call an action on your _cardScript instance. Aside from getting the property reference itself, it’s just one new line of code to perform the same operations on that new property.

So I researched this a little more (for some reason, it didn’t give me sleep at night :)) - and I found a better solution. Still, it’s not perfect, but I believe this is what Unity devs use, too.

public SerializedProperty cardSuite;
public SerializedProperty cardNumber;
public SerializedProperty isFlipped;
  
private CardScript _cardScript;
  
void OnEnable()
{
    cardSuite = serializedObject.FindProperty ("CardSuite");
    cardNumber = serializedObject.FindProperty ("CardNumber");
    isFlipped = serializedObject.FindProperty("IsFlipped");
}

public override void OnInspectorGUI()
{
   // Update the serializedProperty - always do this in the beginning of OnInspectorGUI.
   serializedObject.Update();

   EditorGUILayout.PropertyField(cardSuite);
   EditorGUILayout.PropertyField(cardNumber);
   EditorGUILayout.PropertyField(isFlipped);
  
   _cardScript = (CardScript)target;
   string _enumVal = cardSuite.enumNames[cardSuite.enumValueIndex];
   if(_cardScript.CardSuite.ToString() != _enumVal)
   {
       _cardScript.ChangeSuite(_enumVal);
   }
     
   _enumVal = cardNumber.enumNames[cardNumber.enumValueIndex];
   if(_cardScript.CardNumber.ToString() != _enumVal)
   {
      _cardScript.ChangeNumber(_enumVal);
   }
  
   if(_cardScript.IsFlipped != isFlipped.boolValue)
   {
         _cardScript.Flip(isFlipped.boolValue);
    }
  
   serializedObject.ApplyModifiedProperties();
}

Basically, the solution is to compare the values of the serialized property against the value of the current target object.

[176064-снимок-экрана-2021-02-16-в-165715.png|176064]

I was looking into this for updating an array of GameObjects in an EditorWindow, and at least for me I found a much simpler solution for anyone like me googling this in future:

serialObj.ApplyModifiedProperties() now returns a boolean on the frame its content is changed, so insert an If statement where you would normally have this update, and run any update code you want within. (example below)

if( serialObj.ApplyModifiedProperties() )
{
    OnObjectArrayChanged();// If the array contents have changed, update stuff
}

You can use this as of Unity-2021-3-LTS judging by the wiki: