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?
B is only true of things that exist within the hierarchy I do believe.
For example, Animator State $$anonymous$$achine files wont revert either. They dont live in the hierarchy.
Consider that changes to 'physical' files themselves can not be reverted. If you rewrite a script during playmode that wont revert either, right? Its the same logic for anything that modifies a file in that way.
So is there a way to pass the prefab as a copy so multiple objects can edit their own prefab copy and instantiate a different one?
Unfortunately, no. prefabs are just like ordinary gameobject with a special "state". They simply aren't visible in the scene and don't get any callbacks from the engine. It is not possible to manually create an object with that "state" at runtime. Prefabs are an editor feature. The moment you instantiate the prefab the object get "cloned" but that special state is "removed" so the cloned object will be part of the scene. You can also use Instantiate to clone other gameobjects in the scene as well. Instantiate is just a "CreateClone" function.
As i said in my answer, it's not recommended to change anything inside prefabs as those changes can't be reverted, only by restarting the application.
Answer by Bunny83 · Feb 18, 2016 at 12:06 PM
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.
Unfortunately this is a bad answer. Applying modifications each time a prefab instantiated is very bad approach for both performance and software quality. The modification may be very complex so applying it for hundreds of instances at instantiation time may be extremely costly compared to Unity's automated serialization instantiation.
The problem which harrymuana described is very valid and very common use case.
I still don't have the solution but I will find one. That's why I ended up in this page.
I finally had time to implement a "reset prefab changes which are applied during play mode in editor" system. See my answer below.
I would be happy if you change the accepted answer because of the reasons I posted in my earlier comment above.
Please let me know if you can't make my solution to work. There may be typo in my post.
As long as you Instantiate, and make your changes to the Instance (not the prefab itself), you should be fine.
I think this answer needlessly complicates things by having a cubeSize variable, keeping track of it, resetting it, etc.. which could be confusing for other people reading this.
create your object.
GameObject objInstance = Instantiate(prefab);
Change its scale
objInstance.GetComponent().localScale *= 2;
that's it. (You can also do it with a Transform prefab like Bunny83 did).
Answer by ddas · Jan 22, 2017 at 11:07 PM
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.
Answer by Xtro · Jun 19, 2017 at 02:54 AM
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
}
}
Why bother with all that? Why not just Instantiate your prefab, and make your changes to the instance? Don't ever make changes to a prefab directly in script.
Yes, I am aware of this option but sometimes it doesn't suffice the need.
If you make changes on small number of prefabs in runtime and if those prefabs belongs to your game project, you can choose this option, yes.
If you are making changes to lots of prefabs or if you are a 3rd party asset developer who is dealing with prefabs which don't belong to the customer's game project, things get complicated very fast.
By coincidence, I was just trying to improve my implementation when you commented to it just now. I'll update my answer if I can think of a better and simple way. I'll think about instancing the prefabs even when working on a 3rd party asset project.
If you're worried about performance of modifying hundreds of properties, it would be cleaner to instantiate a prefab to create template GameObjects that you modify and use to instantiate your final instances. Any GameObject can be instantiated, not just prefabs.
Even better would be to create a pool of objects with the correct properties and just reuse them. The performance of instantiating game objects in general is really poor and probably far outweighs the cost of modifying a few properties.