Prevent Reset() from clearing out serialized fields

I have a component which stores a unique id, once it gets created. After it was once set, the id should never change.

However, when a designer selects the cog and “Reset” function on the component, all serialized values are cleared. I know I can implement my own Reset() call, but that doesn’t help, because it gets called, after the fields were already cleared.

Is there any way I can prevent this or get a callback so that I can save and restore my unique id? I’m trying to prevent my level designes from accidentally corrupting references to said ids.

Old question, but it is certainly possible! All you need is a NonSerialized value that you use as a dump for your serialized value. Like so:

public class MyClass : MonoBehaviour, ISerializationCallbackReceiver
{
    // [HideInspector] in your case I'd suggest this as well.
    [SerializeField] private int uniqueID;
    private int uniqueIDDump;

    public void OnAfterDeserialize()
    {
        if(uniqueIDDump != default) // or use a Nullable
        {
            uniqueID = uniqueIDDump;
        }
    }

    public void OnBeforeSerialize()
    {
        uniqueIDDump = uniqueID;
    }
}

Even if you call your uniqueID in Reset, it will have its old value.

Note that the “default” check is important! Since the dump is not serialized, in some cases (e.g. Editor Restart) it will reset the dump. In these cases we may assume the uniqueID has been serialized and will restore the dump on its soonest OnBeforeSerialize call, which will always be before any editing takes place.

196230-ezgifcom-gif-maker-1.gif

It is possible, and in a very intuitive way. First, copy this wrapper:

    [Serializable]
    public struct NonResetable<T> : ISerializationCallbackReceiver
    {
        public T value;
        private T _dump;

        [SerializeField] [HideInInspector] private bool valid;

        public static implicit operator T(NonResetable<T> nonResetable) => nonResetable.value;
        public static implicit operator NonResetable<T>(T value) => new NonResetable<T> { value = value };

        public void OnBeforeSerialize()
        {
            _dump = value;
        }

        public void OnAfterDeserialize()
        {
            if (!valid)
            {
                value = _dump;
                valid = true;
            }
        }
    }

You don’t need to know how it works in order to use it. It automatically converts to and from the type it’s wrapping, so you can use a NonResetable ‘mostly’ like you would a float.

public NonResetable<float> f = 5f;
float SomeMethod() => 10 * f; // Returns 50.

For styling, I would also suggest you copy this script inside an Editor folder:

    [CustomPropertyDrawer(typeof(NonResetable<>))]
    public class NonResetableDrawer : PropertyDrawer
    {
        public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
        {
            EditorGUI.BeginProperty(position, label, property);
            
            var valueProperty = property.FindPropertyRelative(nameof(NonResetable<bool>.value));
            EditorGUI.PropertyField(position, valueProperty, label);

            EditorGUI.EndProperty();
        }
    }

Hi, this page comes up when searching the web for ways to stop Reset from doing stuff, so I want to share a very simple solution. I don’t use it for the same reasons than the OP, but there are many reasons for wanting to disable reset and this should work for most of them:

public abstract class MyScript : MonoBehaviour
{
        [UnityEditor.MenuItem("CONTEXT/" + nameof(MyScript) + "/Reset", true)]
        private static bool OnValidateReset() => false;

        [UnityEditor.MenuItem("CONTEXT/" + nameof(MyScript) + "/Reset")]
        private static void OnReset() => Debug.LogWarning("MyScript doesn't support Reset."); 
}

This works for classes that inherit from MyScript. MyScript could be a ScriptableObject too.

It will completely disable the Reset menu item, so if you or people that use your code use it frequently, this solution might not be for you. Most people I know very rarely click Reset in the menu if at all, so they would probably not notice this anyway. Also, it’s usually not hard to reset things manually; one can usually just create another instance in the worst case. You can still use a Reset() method to set initial values in your scripts.

If you remove the OnValidateReset method, the menu item won’t be disabled, but it will print a warning letting users know that Reset is not supported instead of resetting. And, if you really need a working Reset, you can do your own Reset implementation by doing something like this:

public abstract class MyScript : MonoBehaviour
{    
        [UnityEditor.MenuItem("CONTEXT/" + nameof(MyScript) + "/Reset")]
        private static void OnReset(MenuCommand command)
        {
             var instanceToReset = command.context as MyScript;
             // Do your own Resetting implementation with instanceToReset.
        }
}

EDIT :

I haven’t tested it, but this should work with other menu items, like “Copy Component” or “Paste Component Values”. Just replace Reset with the relevant menu item in the MenuItem attributes.

Also, I feel like I should say this won’t prevent issues with duplicate ids when a whole Object is duplicated. A good solution is outside of the scope of this question, but some ideas involve storing the Object’s own GlobalObjectId when validating, or using Addressables.

No, you can’t prevent that fundamental editor feature. Just like you can’t prevent the designer from removing the component and re-adding a new one which would result in the same problem.

Yout only options would be setting the hideflags and prevent interaction completely. Either set the “NotEditable” flag which would display the whole component “inactive” (just like the components on an imported fbx model). Or if the component doesn’t even need to be seen you can completely hide it from the inspector by setting the “HideInInspector” flag as well.

However keep in mind that there are still ways to at least mess up your IDs. If the object gets duplicated or stored as prefab it would still have the same ID. So you would have multiple objects with the same ID.

However it depends on the actual usage of the component.