Editor script in Prefab Mode

Hi there !
I’m currently making an in-editor tool that is creating, parenting, deleting children inside a prefab. But no matter what method I call before or after editing my object, changes are lost when I quit the prefab mode. To test faster, I’ve unchecked the auto save option, so I guess it will work when the save button will be activated.

I’ve tried before edition:
Undo.RecordObject(transform, "update branches");
and then after:

PrefabUtility.RecordPrefabInstancePropertyModifications(transform);
EditorUtility.SetDirty(transform);
EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());

Any advice ? It’s quite hard to find clear indications about this new workflow anywhere on the web, I’m kinda lost searching for clues…

Thanks !

1 Like

I would suggest to look into PrefabUtility on the options there.

“PrefabUtility.SavePrefabAsset” could be useful to make sure it is saved, there are other options as well
PrefabUtility.ApplyPrefabInstance

I haven’t been looking into that part of nested prefab system

1 Like

It’s unclear why this doesn’t work for you. Is ‘transform’ the changed object that’s part of the Prefab contents in Prefab Mode? Posting the entire script might help.

1 Like

I’m basically making changes on children, destroying some, creating others, renaming, etc…
Here’s an example, when enabling the script it will rename randomly children:

using UnityEngine;

[ExecuteAlways]
public class TestEditPrefabMode : MonoBehaviour
{
    private void OnEnable()
    {
        foreach(Transform child in transform)
        {
            child.name = Random.Range(0, 1000).ToString();
        }
        this.enabled = false;
    }
}

What do you suggest?

Quick update, I’ve tried this:

using UnityEditor;
using UnityEngine;

[ExecuteAlways]
public class TestEditPrefabMode : MonoBehaviour
{
    private void OnEnable()
    {
        foreach(Transform child in transform)
        {
            child.name = Random.Range(0, 1000).ToString();
        }
        PrefabUtility.ApplyPrefabInstance(gameObject, InteractionMode.AutomatedAction);
        this.enabled = false;
    }
}

Unity freezes for a long time and then it’s quite broken, even if my prefab is an empty root with 6 children. I’m using the latest beta (b9).

Edit: it looks like this code is working, but as I said it freezes the editor for a solid minute, and then the UI is blocked (apart the upper part) so I have to relaunch the project. And after relaunching I can confirm the edition worked. Do you have the same behavior?

1 Like

Hi,

Any chance you can file a bug report with an attached project ans step to reproduce? Makes it easier for us to understand what you are doing and verify the issue. Any attached project is only available to Unity employees for testing.

If your script is intended to run in Prefab mode it only makes sense to call Apply if your object is a nested prefab or a variant.

If you haven’t seen it already here is my presentation from Unite LA, giving some insights into how Prefabs works now and some scripting examples

1 Like

I was there in person haha :smile: But pretty sure I missed a lot of stuff.
But yes, I’m trying to apply changes inside the prefab mode, but not to a nested prefab, only regular child objects.
I will watch it again, there was a lot of useful informations.
Thanks again !

Since this thread is the first result when you google this issue, I thought I’d post a solution which is mentioned elsewhere:

[MenuItem("Test/TestChangeName")]
static void TestChangeName(){
     foreach(Transform transform in Selection.transforms){
        
            transform.name = "Test";

            EditorUtility.SetDirty(transform);
            EditorSceneManager.MarkSceneDirty(transform.gameObject.scene);
     }
}

The key is not to use SceneManager.GetActiveScene() as the argument in EditorSceneManager.MarkSceneDirty()

I believe this is because SceneManager.GetActiveScene() does not actually return the prefab editing scene, whereas the ‘scene’ property of the gameobject does.

5 Likes

You should in general not use SceneManager.GetActiveScene for that - even if you’re not in the prefab scene, you could be in a multi-scene setup, and the object you’re editing doesn’t need to be in the active scene.

Hi, I have a similar issue. I’m trying to update some prefab values and then apply them with

PrefabUtility.ApplyPrefabInstance(gameObject, InteractionMode.AutomatedAction);

This is the first time I use the prefab utilities, so I guess I’m missing something.

In short, I have several cars, when I cycle through them, inside a for loop I assign the actual car as a GameObject called “activeCar”. Then, when I buy an upgrade, the “buy” button calls the method “CarUpgrade”. Inside this method I try to update the active car prefab. As I said, this is the first time I use this, so I guess I’m missing something.
This is what I’m doing:

for(int i=0...)
{
.
     activeCar = allCars[i].car.gameObject;
.
}
.
.
public void CarUpgrade()
{
     carUpgrades.BuyUpgrade();
     PrefabUtility.ApplyPrefabInstance(activeCar, InteractionMode.AutomatedAction);
}

I get the error

ArgumentNullException: Value cannot be null. Parameter name: obj

It points me to the line where I call the PrefabUtility, but activeCar is an actual GameObject… What am I missing?

One more thing, this code is outside the activeCar, before writing this code I called the PrefabUtility from within the BuyUpgrade() method, located on one activeCar script. So, what is the best way to do this?

Thanks!

I had the same exception. I actually fixed it by by using InstantiatePrefab instead of LoadPrefabContents. I think its because the latter doesn’t return a normal instance but rather an “isolated instance”.
When using LoadPrefabContents, one has to use SaveAsPrefabAsset.

Hi I find the way. Very painful, But I did it…

Use this api below

var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
if (prefabStage != null)
{
EditorSceneManager.MarkSceneDirty(prefabStage.scene);
}

2 Likes

Thank you SteenLund for the link to examples.
I use the second technique, shown below

string path = "Assets/Prefabs/A.prefab";
GameObject goB = (GameObject)AssetDatabase.LoadMainAssetAtPath("Assets/Prefabs/B.prefab");
GameObject instanceB = (GameObject)PrefabUtility.InstantiatePrefab(goB);
GameObject root = PrefabUtility.LoadPrefabContents(path);
instanceB.transform.parent = root.transform;
PrefabUtility.RecordPrefabInstancePropertyModifications(instanceB.transform);
PrefabUtility.SaveAsPrefabAsset(root, path);
PrefabUtility.UnloadPrefabContents(root);
1 Like

It works SOOOOOOOO well !!
Thx a lot !

This works although experimental:
using UnityEditor.SceneManagement;
using UnityEditor.Experimental.SceneManagement;

Hello,
The bug is still there. STR:

  1. Add an instance of a prefab with multiple children to scene
  2. Try to rename any prefab’s child with something like
    transform.GetChild(0).name = "foo";

Result: Unity freezes and crashes in some time.

1 Like

@tetto_green

Please file a bug report. We can’t track bugs in the forum and they quickly disappear in the noise on the forum.