How to discard changes to prefabs at runtime?

So say I have a cube prefab. GameObject A Instantiates this prefab, and later modifies it’s properties at runtime, eg double it’s local scale. This means that every further cube spawned will be bigger, which is great.

However, when I stop running my scene, the prefab has been overwritten! I thought that

a) the prefab was passed as a copy so if I had another gameobject B that made the prefab smaller, he could spawn his smaller copies without affecting the copies that gameobject A spawns and

b) changes during runtime do not get saved, so you can play with values during runtime as much as you want without them being overwritten.

So now both of these seem to be false… How would you get the desired results?

As meat5000 said, this is only an issue when testing in the editor. Direct changes to assets will persist. If you build the game and run the standalone version, you can still change the prefab, but the changes aren’t permanent as the compiled game files can’t be changed. When you restart the build game (literally quite the game and restart it) everything is back to what it was originally when you build the game.

In general changing assets at runtime is not a good solution. It’s always better to use some kind of variables to control those changes and only appliy those changes to actual gameobject and not prefabs.

Example:

float cubeSize = 1.0f;
Transform cubePrefab;

Transform CreateCube()
{
    Transform cube = (Transform)Instantiate(cubePrefab);
    cube.localScale *= cubeSize;
    cubeSize *= 2;
    return cube;
}

Here everytime you call “CreateCube” a new instance of your cubePrefab is created. We scale that instantiated object by the current cubeSize variable after it’s instantiated and in addition we double the cubeSize variable each time.

That way you can simply set cubeSize back to “1.0f” and new cubes will restart at size 1.

Unity Editor should not change the prefab object properties at runtime. It is very annoying to manually revert the prefab properties that changes at runtime during testing.

Here is how I did it:

How to use

// Implement IExitPlaymodeHandler interface in your script (the one you will add to your prefab).
public abstract class Widget : MonoBehaviour, IExitPlaymodeHandler
{
    // Implement your class here as you wish.
    // ...

    // Implement ExitPlaymode method of IExitPlaymodeHandler interface
    public virtual void ExitPlaymode()
    {
        //-------->     Do your reset work here.  <-------
        // For example: if you changed the scale of prefab in play mode, reset it here.
        transform.localScale = Vector3.one;
    }
}
  • You must register your prefab to the system in play mode so call this anywhere you want (Probably in your game’s initialization)

          if (Application.isEditor) ExitPlaymodeSender.Register(MyWidgetPrefab);
    
  • When you stop play mode in editor, ExitPlaymode method on the prefab you registered earlier will be called and you will be able to reset your changes.

The system I implemented for it to work:

    public interface IExitPlaymodeHandler
    {
        void ExitPlaymode();
    }

    // This static class is responsible for storing the registered ExitPlaymode handlers and notify them when exited the play mode.
    public static class ExitPlaymodeSender
    {
        static readonly List<IExitPlaymodeHandler> Handlers = new List<IExitPlaymodeHandler>();

        public static void Register(IExitPlaymodeHandler Handler)
        {
            if (!Application.isEditor) throw new System.NotSupportedException(Strings.ExitPlaymodeSender);

            if (!Handlers.Contains(Handler)) Handlers.Add(Handler);
        }

        internal static void Send()
        {
            foreach (var Handler in Handlers)
            {
                Handler.ExitPlaymode();
            }

            Handlers.Clear();
        }
        static bool RestoreScheduled;

   #if UNITY_EDITOR
        internal static void EditorApplication_PlaymodeStateChanged()
        {
            // If user pressed stop button.
            if (EditorApplication.isPlaying && !EditorApplication.isPlayingOrWillChangePlaymode) RestoreScheduled = true;

            if (!EditorApplication.isPlaying && !EditorApplication.isPlayingOrWillChangePlaymode && RestoreScheduled)
            {
                RestoreScheduled = false;

                Send();
            }
        }
    #endif
    }

    // This static class is responsible for listening to Unity's EditorApplication.playmodeStateChanged event.
    [InitializeOnLoad]
    static class Initializer
    {
        static Initializer()
        {
   #if UNITY_EDITOR
            EditorApplication.playmodeStateChanged += ExitPlaymodeSender.EditorApplication_PlaymodeStateChanged;
    #endif
        }
    }