tldr; In the editor how do you mark a script/scene as dirty so it’s serialized & saved?
I have a serialized ushort ID part of a MonoBehaviour
[SerializeField]
private ushort ID = 0;
The ID is used as a unique identifier for scene objects used for networking. So if there are 3 objects in a scene they would have ID’s 1, 2, and 3. I wrote an OnValidate script that does this and works fine.
private void OnValidate()
{
if(ID == 0) //has not been set yet
{
ID = GetOpenID(); //search through all scripts in the scene and find the first open ID number
}
}
However, when I set the ID value in this way it does not mark the script/scene as dirty. So if I change scenes or click Play then the values are lost. Interestingly if I make the scene dirty through some other means (i.e. moving different object’s transform) then the values for all objects in the scene (even unmoved ones) will be saved.
To attempt to make things dirty I’ve tried…
-Undeo.RecordObject(gameObject, “SetID”);
-EditorUtility.SetDirty(this);
-EditorUtility.SetDirty(gameObject);
-SerializedObject serializedObject = new SerializedObject(this); + serializedObject.ApplyModifiedProperties();
-UnityEditor.SceneManagement.EditorSceneManager.MarkAllScenesDirty();
I also have the same issue with prefab asset that are not part of a scene. If the scripts ushort value is changed, but if you close the Unity Editor and re-open it then the prefab asset ushort value is reverted.
I feel like I’m missing something. There has to be a simple way to mark an object as dirty or force an object to be serialized/saved!
So I guess the ‘resolution’ was that you can’t Dirty stuff in OnValidate because once OnValidate has completed executing, the thing is marked as not dirty?
I am using OnValidate on some scriptable objects to do some ‘ID Management’ similar to you (if ID == 0, ask database for next available ID and set self as dirty), but I am finding that the objects are being marked as dirty correctly. The changes are not showing up on git. If I edit another property of the scriptable object, it does appear in git. I can see the mock change that I made and the ID assignment comes up as a change as well.
So it effectively means that any changes performed in OnValidate should be deterministic in nature, because those changes aren’t saved to disk (unless the asset gets edited some other way after that point, in which case they will get written to disk)?
The problem here is that Unity’s serialization simply does not support the ushort type. So the SerializeField attribute does not have any effect in this case.
The way around the issue is to use an int instead of ushort for the ID, at least when serializing the value. You can always cast the value to ushort from the int after deserialization has finished. Here is one possible solution:
[SerializeField]
private int id;
public ushort ID
{
get
{
return (ushort)id;
}
private set
{
id = value;
}
}
If you want to avoid casting the int to ushort every time ID is accessed, another option is to only cast it once after deserialization has finished. This can be done by implementing the ISerializationCallbackReceiver interface.
using UnityEngine;
public class Example : MonoBehaviour, ISerializationCallbackReceiver
{
private uint ID;
[SerializeField]
private int idSerialized;
public void OnBeforeSerialize()
{
idSerialized = (int)ID;
}
public void OnAfterDeserialize()
{
ID = (ushort)idSerialized;
}
}
private void OnValidate()
{
if(ID == 0) //has not been set yet
{
#if UNITY_EDITOR
UnityEditor.Undo.RecordObject(this, "Assign OpenID");
#endif
ID = GetOpenID(); //search through all scripts in the scene and find the first open ID number
}
}