Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Editor script in Prefab Mode

Discussion in 'Prefabs' started by cAyouMontreal, Nov 12, 2018.

  1. cAyouMontreal

    cAyouMontreal

    Joined:
    Jun 30, 2011
    Posts:
    315
    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 !
     
    toothbrush likes this.
  2. MatthieuPr

    MatthieuPr

    Joined:
    May 4, 2017
    Posts:
    56
    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
     
    cAyouMontreal likes this.
  3. runevision

    runevision

    Joined:
    Nov 28, 2007
    Posts:
    1,892
    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.
     
    cAyouMontreal likes this.
  4. cAyouMontreal

    cAyouMontreal

    Joined:
    Jun 30, 2011
    Posts:
    315
    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:
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. [ExecuteAlways]
    4. public class TestEditPrefabMode : MonoBehaviour
    5. {
    6.     private void OnEnable()
    7.     {
    8.         foreach(Transform child in transform)
    9.         {
    10.             child.name = Random.Range(0, 1000).ToString();
    11.         }
    12.         this.enabled = false;
    13.     }
    14. }
    What do you suggest?
     
  5. cAyouMontreal

    cAyouMontreal

    Joined:
    Jun 30, 2011
    Posts:
    315
    Quick update, I've tried this:
    Code (CSharp):
    1. using UnityEditor;
    2. using UnityEngine;
    3.  
    4. [ExecuteAlways]
    5. public class TestEditPrefabMode : MonoBehaviour
    6. {
    7.     private void OnEnable()
    8.     {
    9.         foreach(Transform child in transform)
    10.         {
    11.             child.name = Random.Range(0, 1000).ToString();
    12.         }
    13.         PrefabUtility.ApplyPrefabInstance(gameObject, InteractionMode.AutomatedAction);
    14.         this.enabled = false;
    15.     }
    16. }
    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?
     
    tetto_green likes this.
  6. SteenLund

    SteenLund

    Unity Technologies

    Joined:
    Jan 20, 2011
    Posts:
    639
    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
     
    cAyouMontreal likes this.
  7. cAyouMontreal

    cAyouMontreal

    Joined:
    Jun 30, 2011
    Posts:
    315
    I was there in person haha :D 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 !
     
  8. mcurtiss

    mcurtiss

    Joined:
    Nov 29, 2012
    Posts:
    26
    Since this thread is the first result when you google this issue, I thought I'd post a solution which is mentioned elsewhere:

    Code (CSharp):
    1. [MenuItem("Test/TestChangeName")]
    2. static void TestChangeName(){
    3.      foreach(Transform transform in Selection.transforms){
    4.        
    5.             transform.name = "Test";
    6.  
    7.             EditorUtility.SetDirty(transform);
    8.             EditorSceneManager.MarkSceneDirty(transform.gameObject.scene);
    9.      }
    10. }
    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.
     
    horeaper, AxeManDan, Ultroman and 2 others like this.
  9. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,294
    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.
     
  10. luisch125

    luisch125

    Joined:
    Jul 10, 2015
    Posts:
    35
    Hi, I have a similar issue. I'm trying to update some prefab values and then apply them with
    Code (CSharp):
    1. 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:

    Code (CSharp):
    1.                
    2. for(int i=0...)
    3. {
    4. .
    5.      activeCar = allCars[i].car.gameObject;
    6. .
    7. }
    8. .
    9. .
    10. public void CarUpgrade()
    11. {
    12.      carUpgrades.BuyUpgrade();
    13.      PrefabUtility.ApplyPrefabInstance(activeCar, InteractionMode.AutomatedAction);
    14. }
    15.  
    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!
     
    Last edited: Mar 11, 2019
  11. TriNityGER

    TriNityGER

    Joined:
    Sep 1, 2017
    Posts:
    29
    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.
     
  12. ELenDiL_Unity

    ELenDiL_Unity

    Joined:
    Sep 29, 2014
    Posts:
    4
    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);
    }
     
    Darren-R and atk_defender like this.
  13. deeprest

    deeprest

    Joined:
    Jun 8, 2016
    Posts:
    17
    Thank you SteenLund for the link to examples.
    I use the second technique, shown below

    Code (CSharp):
    1. string path = "Assets/Prefabs/A.prefab";
    2. GameObject goB = (GameObject)AssetDatabase.LoadMainAssetAtPath("Assets/Prefabs/B.prefab");
    3. GameObject instanceB = (GameObject)PrefabUtility.InstantiatePrefab(goB);
    4. GameObject root = PrefabUtility.LoadPrefabContents(path);
    5. instanceB.transform.parent = root.transform;
    6. PrefabUtility.RecordPrefabInstancePropertyModifications(instanceB.transform);
    7. PrefabUtility.SaveAsPrefabAsset(root, path);
    8. PrefabUtility.UnloadPrefabContents(root);
    9.  
     
    Vedran_M likes this.
  14. atk_defender

    atk_defender

    Joined:
    Jun 26, 2018
    Posts:
    25
    It works SOOOOOOOO well !!
    Thx a lot !
     
  15. rmon222

    rmon222

    Joined:
    Oct 24, 2018
    Posts:
    77
    This works although experimental:
    using UnityEditor.SceneManagement;
    using UnityEditor.Experimental.SceneManagement;
     
  16. tetto_green

    tetto_green

    Joined:
    Dec 22, 2015
    Posts:
    35

    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
    Code (CSharp):
    1. transform.GetChild(0).name = "foo";
    Result: Unity freezes and crashes in some time.
     
    YutakaKubota likes this.
  17. SteenLund

    SteenLund

    Unity Technologies

    Joined:
    Jan 20, 2011
    Posts:
    639
    @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.