Prefab in scene disconnected from file, and saves changes from play mode?!

I have a main menu as a prefab. I wanted to change a button text depending on game context. When I change it (code below) nothing happens. But then when I quit back to edit mode from play mode, the button now shows the text set in play mode (neat trick! whish I could choose to do that for other kinds of game objects…). Meanwhile the instantiated prefab doesn’t show any evidence of an override, though it no longer matches the prefab when edited in isolation. What’s going on here? AND/OR more importantly, how can I fix this without deleting the prefab from the scene and reimporting it? I do have multiple overrides for other prefab gameobjects so it would be a pain. Plus I want to understand what’s gone wrong.

Code:

print(button2.GetComponentInChildren<TMP_Text>().text); // checking I have the right one
button2.GetComponentInChildren<TMP_Text>().text = “Unlocks after level 5”;
print(button2.GetComponentInChildren<TMP_Text>().text); // checking it actually changed it

console prints:

image

but main menu doesn’t show any change (in play mode):

image

now I exit from play mode:

and text mesh pro component shows the new text, but doesn’t think there’s an override(!)

but if viewed in isolation it’s back to normal:

Sounds like your code is editing the prefab and not the in-scene instance. Ideally your runtime code doesn’t modify instances on disk.

Remember that the prototype-pattern that prefab overrides provide are an edit-time only concept.

The other weird thing is when I instantiate the prefab dynamically (so that any scene can view the main menu), the exact same code works fine, changing what’s displayed and leaving alone what’s on disk.

So why, then, does that same code modify the scene contents destructively when the prefab is added by (drag and drop) to scene before pushing play?

Again, probably because you’re probably erroneously modifying the prefab asset on disk and not a scene instance in one case, while the other case is modifying the scene instance.

So double check what your code is modifying. Any code that modifies assets will persist between play mode sessions.

How would I check if an asset reference is on disk instead of a scene instance?

The gameobject’s being modified are just standard UI objects (Button), no backing prefabs as far as I know; they were filled in to my script’s member variables by dragging and dropping from the editor’s hierarchy, for the prefab. Is that where I went wrong? The only other option to find the UI objects I know of is the rather easily broken Find “by name” function.

eg I dragged from here:

to here:

Just, check what object is at the end of an inspector reference. You can double click a unity object reference and it will select said object. This will tell you if its something in a scene, or something in your project assets.

Namely, it sounds like you have something in a scene that is pointing to a prefab in your project assets and modifying that. It should be pointing to something the scene itself, or be instantiating the prefab and operating on the new instance living in the scene.

Do you have a script anywhere on this prefab with attributes ExecuteInEditMode or ExecuteAlways? Because these scripts will also run event methods while editing the prefab (or maybe even the scene). Thus the changes made at that time are edit-time changes that apply to either the prefab itself or any prefab instance placed in the scene.

Also Unity 6 now warns you of entering playmode while being in prefab editing mode for the same reasons. Not sure if this warning exists in 2022. Be sure to exit out of prefab editing mode before entering playmode if your prefab does have one of these attributes.

double clicking on the inspector’s view of my script member variable, named Button 2, it just takes me over the scene hierarchy where that prefab has been placed, see photo.

So if this magically means that it’s pointing at the copy on disk and not the instance in the scene, that’s a big surprise. None of the other prefabs in my game pickup permanent changes from play mode and seem to behave exactly the same way.

Nope, none of that in my project at all (could be some of the editor plugins do, but they aren’t attached to any game object, as least as I barely understand how editor plugins work).

Also, I’ve tried both having the prefab open and not open when I enter play mode, doesn’t make a difference.

What does seem to work, and suggests that somehow “public Button button2;” is pointing at the prefab:

GameObject.Find(“hubworld”).GetComponentInChildren<TMP_Text>().text = “Unlocks after level 5”;

instead of

button2.GetComponentInChildren<TMP_Text>().text = “Unlocks after level 5”;

I’m going with that for now, but it’s an ugly hack, relying on the name of a gameobject. That was the whole point of using public member variables that could be assigned using the inspector.

To be sure, is this what happens?

  • You are in the scene
  • You select MainMenuController - an instance of the prefab dragged into the scene
  • On the Inspector of the MainMenuController script, you double clicked the Button 2 reference
  • This selects hubworld as shown in the photo

If so, that would be expected behaviour.

However, if the first item is actually “you are in prefab edit mode OR you have selected the prefab asset in the Project view” and then you view the Inspector of the prefab asset and double-click the Button 2 reference - if this opens up the reference in the scene then you made a do-hickey that will actually lead to a missing reference if you close and re-open the project.

The reason being that prefab assets cannot reference scene objects. However, there are ways to accomplish this scene object references in prefabs. This will initially seem to work in the editor but fail to work if you make a build or after closing the project due to missing references.

Yes, that’s the exact sequence. So you are saying that means it’s supposed to edit the prefab on disk if that’s how it’s behaving? From the rest of your post it seems perhaps you mean the opposite of that, but at this point I’m very unclear on what the expected behavior is.

PS I can make builds fine. I haven’t tested for my bug in a standalone build, though, but the menu in question is in my 0th scene, so It definitely loads without causing any error.

Assigning text to the label at runtime should not modify the prefab unless the change is somehow applied back to the prefab asset.

Try doing this at runtime:
print(button2.GetInstanceID);
print(button2.GetComponentInChildren<TMP_Text>().GetInstanceID);

Then enable “Debug” mode in the Inspector through the burger menu (three dots, upper right corner). You will notice each object and component have an InstanceID. Verify that the instance IDs printed at runtime are the same as the ones for Button2 and the respective component in the scene.

If either ID does not match, check if it matches the ID of the same Button2 and component in the prefab. If that’s the case you’re likely running GetComponent on a prefab reference of Button2 rather than the instanced version.

You know what? I got the same issue in 6000.0.29f1! :anguished:

I have a prefab instance where I reset a child’s transform.localPosition to Vector3.zero every time it spawns at runtime. When I opened the prefab, I was astonished to find the child’s transform got reset to (0,0,0) in the prefab itself!

BUT … this was my mistake.

A MonoBehaviour on the prefab root contains an OnValidate. This calls the method that resets the position of child elements. Thus the prefab child’s transform gets modified when editing the prefab.