Upon further testing, turns out that there are several problems with this:
- Does not work in you change the value through the inspector (but works if the value is changed by the script)
- Having the property be marked as driven prevents it from being serialized correctly, so the changes are lost when the scene is unloaded.
I’ve managed to get an example working correctly, but the code is… more convoluted than I would like.
using UnityEngine;
[ExecuteInEditMode]
public class Test : MonoBehaviour, ISerializationCallbackReceiver
{
[SerializeField]
public int value;
int value_copy = 0;
void Awake()
{
#if UNITY_EDITOR
UnityEditor.EditorApplication.playModeStateChanged += ModeStateChanged;
UnityEditor.SceneManagement.EditorSceneManager.sceneSaving += BeforeSaveSceneCallback;
UnityEditor.SceneManagement.EditorSceneManager.sceneSaved += AfterSaveSceneCallback;
RegisterDrivenProperty();
if(value == 0)
value = Random.Range(1, 100);
#endif
}
protected virtual void OnDestroy()
{
#if UNITY_EDITOR
UnityEditor.EditorApplication.playModeStateChanged -= ModeStateChanged;
UnityEditor.SceneManagement.EditorSceneManager.sceneSaving -= BeforeSaveSceneCallback;
UnityEditor.SceneManagement.EditorSceneManager.sceneSaved -= AfterSaveSceneCallback;
#endif
}
protected virtual void OnEnable()
{
#if UNITY_EDITOR
if (UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode || isRemovingOverride)
return;
RemoveOverrideState(true);
#endif
}
protected void OnDisable()
{
#if UNITY_EDITOR
if (Application.isPlaying || isRemovingOverride)
return;
UnregisterDrivenProperty();
#endif
}
#if UNITY_EDITOR
void ModeStateChanged (UnityEditor.PlayModeStateChange state)
{
if (state == UnityEditor.PlayModeStateChange.ExitingEditMode)
UnregisterDrivenProperty();
}
void RegisterDrivenProperty()
{
RemoveOverrideState(false);
var assembly = System.Reflection.Assembly.Load("UnityEngine.CoreModule");
var type = assembly.GetType("UnityEngine.DrivenPropertyManager");
var method = type.GetMethod("RegisterProperty");
method.Invoke(null, new object[] { this, this, "value" });
value = value_copy;
}
void UnregisterDrivenProperty()
{
value_copy = value;
var assembly = System.Reflection.Assembly.Load("UnityEngine.CoreModule");
var type = assembly.GetType("UnityEngine.DrivenPropertyManager");
var method = type.GetMethod("UnregisterProperty");
method.Invoke(null, new object[] { this, this, "value" });
value = value_copy;
}
void AfterSaveSceneCallback(UnityEngine.SceneManagement.Scene scene)
{
RegisterDrivenProperty();
}
void BeforeSaveSceneCallback(UnityEngine.SceneManagement.Scene scene, string path)
{
if (scene != gameObject.scene)
return;
UnregisterDrivenProperty();
}
bool isRemovingOverride = false;
void RemoveOverrideState(bool registerProperty)
{
isRemovingOverride = true;
value_copy = value;
if (UnityEditor.PrefabUtility.IsPartOfAnyPrefab(gameObject))
{
UnityEditor.SerializedObject serializedObject = new UnityEditor.SerializedObject(this);
UnityEditor.SerializedProperty serializedPropertyMyInt = serializedObject.FindProperty("value");
UnityEditor.PrefabUtility.RevertPropertyOverride(serializedPropertyMyInt, UnityEditor.InteractionMode.AutomatedAction);
}
if (registerProperty)
RegisterDrivenProperty();
value = value_copy;
isRemovingOverride = false;
public void OnBeforeSerialize()
{
// IMPORTANT: EditorApplication checks must be done first.
// Otherise Unity may report errors like "Objects are trying to be loaded during a domain backup"
if (UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode || UnityEditor.EditorApplication.isUpdating) return;
// Validate the type of your prefab. Useful pre-check.
if (UnityEditor.PrefabUtility.GetPrefabAssetType(this) != UnityEditor.PrefabAssetType.Regular) return;
// Override properties only if this is a prefab asset on disk and not any of its scene instances
if (UnityEditor.PrefabUtility.GetPrefabInstanceHandle(this)) return;
// Finally, re-set any fields to initial or specific values for the shared asset prefab on disk
// This protects these fields when "Apply Override" gets called from any of prefab's scene instances
value = 0;
}
public void OnAfterDeserialize()
{}
}
#endif
}
With this, it at least serializes correctly. However, it does not work for values changed through the inspector (maybe this could be done by catching the change through OnValidate and calling RemoveOverrideState?); and still has a slightly janky interaction with disabling the object: while the object is disabled, the property is considered to be a prefab override, and it is only when you re-enable it that it goes back to being considered a driven property.
Better than nothing, I guess.
EDIT: Added a callback for detecting when we are about to enter play mode in the editor, because for some mysterious reason it resets the value otherwise (but only if the scene is open when the mode change happens). From what I can tell, it works correctly now.
EDIT (again): Turns out there was STILL trouble with serialization (specifically, when applying all other overrides to the prefab if a value for the property was already serialized as part of the scene). I got a really good solution from Protect Unity Prefabs From Changes With the ISerialization Callback Receiver Interface - CGI Сoffee , which honestly could be used to make this whole Driven Property business irrelevant… Almost. Driven properties are still required to prevent the changes from showing up as overrides in the inspector, still.