OnValueChanged doesn`t work with ScriptableObject

Issue #287 open
Former user created an issue
[OnValueChanged("Apply", true)]
[InlineEditor(InlineEditorModes.FullEditor)]
public TextableSettingsData Data;

Apply method will called if only change the reference to the object (delete or etc)

I wish to have an event of any changes in the scriptableobject

Comments (5)

  1. Bjarke Elias
    • changed status to open

    IncludeChildren for OnValueChanged, unfortunately, doesn't work with Unity objects and the InlineEditor attribute like that, as we have no way of knowing if any value actually changed inside the Unity object.

    But in the special case of rendering the object with InlineEditor attribute, we could add a simple GUI.changed around drawing the actual inline editor and trigger the OnValueChanged. But we would have no way of knowing whether a value actually changed, if the object was drawn by Odin, then we could potentially do so, but I'm not sure we're going to go that far.

    // Note to self:
    EditorGUI.BeginChangeCheck();
    context.Value.Editor.OnInspectorGUI();
    if (EditorGUI.EndChangeCheck())
    {
        (entry as PropertyValueEntry).TriggerOnChildValueChanged(0);
    }
    

    I'll leave this open for now and come back to it.

  2. Darren Haba

    @Bjarke Elias Hello, I ran into the same issue. Your workaround should work fine, but my a little confused about where to add your code snippet.

    If I have the following MonoBehaviour class, similar to the one in your ScriptableObject tutorials:

    public class EnemyController : MonoBehaviour
    {
        [OnValueChanged("OnDataChanged", true)] 
        [InlineEditor]
        public EnemyData data; // EnemyData is a ScriptableObject
    
        public void OnDataChanged()
        {
            print("On data changed");
        }
    }
    

    Do I have to create a custom editor for EnemyController and add your code snippet inside it, then remove the [InlineEditor] attribute?

  3. Darren Haba

    I found a workaround. Anyone who wants to tell when the data in the child ScriptableObject changed can do the following;

    1. Create a script named EnemyData.cs and add the following code:

    using UnityEngine;
    
    [CreateAssetMenu]
    public class EnemyData : ScriptableObject
    {
        [Range(0.5f, 5f)]
        public float enemyScale;
    
        // Dynamically changed to the currently selected EnemyController.
        [HideInInspector] public EnemyController enemyController;
    
    #if UNITY_EDITOR // Ensure that OnValidate() does not execute in our builds.
        private void OnValidate()
        {
            // If not in play mode then exit.
            if(!Application.isPlaying) return;
    
            if(enemyController)
                enemyController.OnDataChanged();
        }
    #endif
    }
    

    2. Create a script named EnemyController.cs and add the following code:

    using Sirenix.OdinInspector;
    using UnityEngine;
    
    public class EnemyController : MonoBehaviour
    {
        [OnValueChanged("OnDataChanged")] 
        [InlineEditor]
        public EnemyData data; // EnemyData is a ScriptableObject
    
    #if UNITY_EDITOR
        private void OnValidate()
        {
    
            OnDataChanged();
        }
    #endif
    
        private void OnEnable()
        {
            OnDataChanged();
        }
    
        public void OnDataChanged()
        {
            // If enemy data scriptable object is set to None, then exit.
            if(data == null) return;
    
            print("On data changed");
            transform.localScale = new Vector3(data.enemyScale, data.enemyScale, data.enemyScale);
        }
    }
    

    3. Inside the project window create a folder named Editor, inside of it create a script called EnemyDataEditor.cs add the following code:

    using Sirenix.OdinInspector.Editor;
    using UnityEditor;
    using UnityEngine;
    
    [CustomEditor(typeof(EnemyData))]
    public class EnemyDataEditor : OdinEditor
    {
        private EnemyData enemyData;
        private GameObject lastActiveGameObject;
    
        private void Awake()
        {
            enemyData = (EnemyData)target;
        }
    
        public override void OnInspectorGUI()
        {
            base.OnInspectorGUI();
    
            // if no selected game object then exit.
            if(!Selection.activeGameObject) return;
            var enemyController = Selection.activeGameObject.GetComponent<EnemyController>();
            // If not in play mode then exit.
            if(!Application.isPlaying) return;
            // If enemy control or doesn't exist then exit.
            if(enemyController == null) return;
            // If not the first time game the GameObject is selected then exit.
            if(lastActiveGameObject == Selection.activeGameObject) return;
    
            enemyData.enemyController = enemyController;
            lastActiveGameObject = Selection.activeGameObject;
        }
    }
    

    To test this, do the following.

    1. Create an EnemyData scriptable object: Assets->Create->Enemy Data

    2. In the scene create a cube: 3D Object->Cube

    3. Drag the EnemyController script onto the cube.

    4. In the EnemyController set Data to the newly created ScriptableObject named “New Enemy Data” by default.

    5. Enter play mode, then move the enemy scale slider up-and-down. The cube should scale with the slider.

    What I’m doing is using OnValidate() inside EnemyData to see if any data has changed. When the data has changed I call the EnemyController.OnDataChanged(). The problem is if you have two game objects in the scene with an EnemyController script attached the EnemyData ScriptableObject doesn’t know which one to update. So I had to create a custom editor “EnemyDataEditor” that dynamically sets the “enemyController” variable to that of the currently selected GameObject's EnemyController component.

    Note this is an ugly workaround but it is the only way I found to allow my designers to see their changes live in play mode. I find a better solution I will post it here.

  4. Log in to comment