Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

How do I edit prefabs from scripts?

Discussion in 'Prefabs' started by Baste, May 28, 2019.

  1. IARI

    IARI

    Joined:
    May 8, 2014
    Posts:
    70
    I'm not sure about a Prefab Type being that much of an improvement - but then again, so far I really don't understand the prefabUtility api at all and don't feel like i hav.

    Is there any reference to use cases that edit existing prefabs?

    For me It would help a lot for now, if there was just at least some clear documentation, and maybe a tutorial and some more example use-cases that do more than just the create-prefabs case (which only involves simple creation/writing)

    * Something that involves editing existing prefabs
    * editing existing prefabs with an unknown path, given a reference to a gameobject within the opened scene (like the Selection)
    * in the latter case a decision needs to be made, at which level editing occurs in case of nested prefabs or variants

    For example:
    Given a Gameobject reference (from selection, or target from within a custom inspector):
    How do I add/remove a component and store the changes made to the nearest prefab instance?

    Here is some example code that i have tried which does not work as I hoped:

    Code (CSharp):
    1.  
    2.     public static void EditPrefab(this GameObject go, Action<GameObject> action)
    3.     {
    4.         var prefabRoot = PrefabUtility.GetCorrespondingObjectFromSource(go);
    5.         var assetPath = AssetDatabase.GetAssetPath(prefabRoot);
    6.         Debug.Log($"assetRoot: {prefabRoot}, assetPath: {assetPath}");
    7.         var loadedPrefabRoot = PrefabUtility.LoadPrefabContents(assetPath);
    8.         action.Invoke(loadedPrefabRoot);
    9.         PrefabUtility.SaveAsPrefabAsset(loadedPrefabRoot, assetPath);
    10.         PrefabUtility.UnloadPrefabContents(loadedPrefabRoot);
    11.     }
    12.  
    I just hope that im missing something, and the whole thing is a lot easier
     
    Last edited: Oct 13, 2020
    MarekLg likes this.
  2. acmoles

    acmoles

    Joined:
    Jun 28, 2016
    Posts:
    4
    This worked perfectly - many thanks for sharing
     
  3. calpolican

    calpolican

    Joined:
    Feb 2, 2015
    Posts:
    400
    Can anyone explain how this syntax in Baste's code works? I've never encounter that use of the keyword "using" before, and I'm interesting in understanding it.

    Code (CSharp):
    1.     using (var editScope = new EditPrefabAssetScope(assetPath))
    2.     {
    3.         editScope.prefabRoot.AddComponent<BoxCollider>();
    4.     }
     
  4. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,878
    Google.com: “c# using keyword” = 183,000 results.

    its a core part of C#. It creates a temp object and immediately autodestroys it at end of braces. But really ... for C# questions, google is your friend.

    (a common trick is to use the constructor and destructor from ‘using’ as a place to copy/paste tour setup/teardown code, knowing that the compiler will ensure they are called, in the right order, and no future person editing your code can accidentally delete one, or forget to inckude one when coy pasting this code for use elsewhere)
     
    Walter_Hulsebos likes this.
  5. calpolican

    calpolican

    Joined:
    Feb 2, 2015
    Posts:
    400
    Ok, thanks for the tip
     
  6. michealcaj

    michealcaj

    Joined:
    Aug 18, 2017
    Posts:
    191
    The prefab utility don't seem to be working in build mode
     
  7. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,878
    By definition it cannot. There is no such thing as a "prefab" outside the editor. Whatever you're trying to do won't work :).
     
  8. paulatwarp

    paulatwarp

    Joined:
    May 17, 2018
    Posts:
    134
    I've read through this thread and I'm more confused than when I started. I need to edit nested prefabs in a batch process. Basically I need to copy text alignment settings from an earlier version that got corrupted due to a bug when changing assets from binary mode to text mode. There are thousands of them.

    I've tried using many of the methods listed above but none of them seem to be working for me.
     
  9. paulatwarp

    paulatwarp

    Joined:
    May 17, 2018
    Posts:
    134
    I finally got it working. There were two issues. The first was that the original prefabs must have been created before TextMeshPro made the change to not serialise its SubMeshUI objects. Deleting these and forcing it to re-create them before attempting to serialise fixed one issue.

    The second problem was with a prefab that was a RectTransform and had a Canvas object attached. Attempting to re-serialise this using LoadPrefabContents wiped the scale and anchor information from the object.

    Here is a 2019.4.26f1 project showing the second problem: https://github.com/paulatwarp/SerialiseRectTransform

    I have also included a variation which appears to work by creating a preview canvas using LoadPrefabContents and then manually instantiating the prefab object onto that.

    I'm not sure if this is a bug or undefined behaviour.
     
  10. jovg

    jovg

    Joined:
    Oct 14, 2016
    Posts:
    2
    This is not working me, I need to edit some values for a component that exists within the prefab root children. When saved the values aren't reflected on the prefab.
     
  11. SI-Shawn

    SI-Shawn

    Joined:
    Jun 30, 2021
    Posts:
    12
    Thanks for sharing this. This helped me understand the difference between working with prefab stage and asset database contexts.
     
  12. crybirb

    crybirb

    Joined:
    Jun 13, 2022
    Posts:
    5
    Can someone give a GIST of this? I read the entire post and none of the snippets seen to be working, I'm trying something as:

    Code (CSharp):
    1. var prefab = PrefabUtility.LoadPrefabContents(prefabPath);
    2. var childComponents = prefabLoaded.GetComponentsInChildren<ChildComponent>();
    3.  
    4. foreach (var comp in components)
    5. {
    6.     comp.ListInsideChildComponent.Clear();
    7. }
    8.  
    9. PrefabUtility.SavePrefabAsAsset(prefab, prefabPath);
    10. PrefabUtility.UnloadPrefabContents(prefab)
    After that, opening the Prefab, instantiating or whatever doesn't seem to have the changes applied. The vectors are still populated.
     
    Last edited: Jul 19, 2022
  13. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,878
    Like ... that's the whole point of this thread. Use the code provided - that has "using" in the first line.

    If you don't: it will never work.

    If you do: it will always work.
     
    Walter_Hulsebos likes this.
  14. crybirb

    crybirb

    Joined:
    Jun 13, 2022
    Posts:
    5
    I used the code provided that has "using" in the first line. I tried also the Macro that was added into the PrefabUtility API. I tried everything in this thread, and the changes doesn't apply to the prefab.

    It didn't work. That's why I'm here.
     
    Last edited: Jul 19, 2022
  15. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,878
    So the API https://docs.unity3d.com/2020.1/Doc...ce/PrefabUtility.EditPrefabContentsScope.html is broken for you?

    If so ... I think you should create a minimal test case and submit a bug report.

    FYI: I've been using this for a couple of years now and its worked in a wide variety of complex cases on multiple projects, on everything from 2018 (using Baste's workaround) to 2021 (using the official replacement). It's pretty good! But it could still have new bugs :(
     
  16. crybirb

    crybirb

    Joined:
    Jun 13, 2022
    Posts:
    5
    Yes it is not working for me, so the case I have that isn't working can be described as:

    I have a Prefab which contains other Prefabs inside, what I want to do is update a Custom Component inside the hierarchy, for that the code below is the attempt of achieving that

    Code (CSharp):
    1. using (var editingScope = new PrefabUtility.EditPrefabContentsScope(prefabPath))
    2.                 {
    3.                     var prefabLoadedContents = editingScope.prefabContentsRoot;
    4.                     var components = prefabLoadedContents.GetComponentsInChildren<CustomComponent>();
    5.  
    6.                     foreach (var comp in components)
    7.                     {
    8.                         Debug.Log($"{comp.name} {comp.ArrayInComponent.Count}");
    9.                         comp.ArrayInComponent.Clear();
    10.                         Debug.Log($"{comp.name} {comp.ArrayInComponent.Count}");
    11.                     }
    12.                 }
    The array Count is printed correctly, it shows going from X amount to 0. However when I open the prefab the change didn't apply to it. I want to know how can I consolidate said changes. I tried numerous different methods, my last resort being manually getting the serialized property in the field and changing it but that would require a very long and troublesome. I would love if someone can help me identify what I'm doing wrong or missing with this.

    Unity Ver: 2020.3.25f1
     
    Last edited: Jul 19, 2022
  17. crybirb

    crybirb

    Joined:
    Jun 13, 2022
    Posts:
    5
    It seems in this way I can do almost everything besides change the value of a Serialized Field. I can change the name of the object, create a new one, enable or disable it, remove a component, add a new one. Everything but changing the value. Tried also going through serialized objects and serialized properties to edit, no avail.

    EDIT: Discovered the problem, nothing to do with the Prefab API, it was a custom Inspector / Type override that was blocking the values from changing.
     
    Last edited: Jul 19, 2022
    a436t4ataf likes this.
  18. a_ermolinsky

    a_ermolinsky

    Joined:
    Sep 29, 2022
    Posts:
    1
    Thanks for a thread. I was trying to delete component from prefab and this thread clearify what functions needed to be called.
     
  19. denevraut

    denevraut

    Joined:
    Jan 23, 2017
    Posts:
    6
  20. baktubak

    baktubak

    Joined:
    Sep 17, 2018
    Posts:
    1
    Just wanted to share a success, here's a script that allows me to add a component to a particular child of a prefab and save it.

    The critical things seem to be as follows:
    • Create an instance of the prefab you want to change
    • Apply the change you want to make to the prefab instance
    • Save the change using a function from the PrefabUtility class, in my case, "ApplyAddedComponent" was most appropriate.
    My use case was that I needed to add a component to the children with a Renderer to about 65 prefabs, so this just lets me select the prefabs I want and click a button to do it.

    Code (CSharp):
    1.  private void AddStealthPropertiesToPrefabs()
    2.     {
    3.         renderers = new List<Renderer>();
    4.         GameObject[] selectedObjects = Selection.gameObjects;
    5.  
    6.         foreach (GameObject prefab in selectedObjects)
    7.         {
    8.             GameObject prefabInstance = PrefabUtility.InstantiatePrefab(prefab) as GameObject;
    9.             if (prefabInstance == null)
    10.             {
    11.                 Debug.LogError("Failed to instantiate prefab: " + prefab.name);
    12.                 continue;
    13.             }
    14.  
    15.             prefabInstance.GetComponentsInChildren(renderers);
    16.             foreach (Renderer renderer in renderers)
    17.             {
    18.                 if (renderer != null)
    19.                 {
    20.                     StealthMaterialProperties stealthProps = renderer.GetComponent<StealthMaterialProperties>();
    21.                     Debug.Log(stealthProps + "<-- stealthProps ; prefab " + prefab.name);
    22.                     if (stealthProps == null)
    23.                     {
    24.                         stealthProps = renderer.gameObject.AddComponent<StealthMaterialProperties>();
    25.                         Debug.Log("StealthMaterialProperties added to " + prefab.name);
    26.  
    27.                         // Apply the added component change to the prefab asset
    28.                         string prefabPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(prefabInstance);
    29.                         PrefabUtility.ApplyAddedComponent(stealthProps, prefabPath, InteractionMode.UserAction);
    30.  
    31.                         // Destroy the instantiated prefab instance
    32.                         DestroyImmediate(prefabInstance);
    33.                     }
    34.                     else
    35.                     {
    36.                         Debug.Log("StealthMaterialProperties already exist on " + prefab.name);
    37.                         // Destroy the instantiated prefab instance
    38.                         DestroyImmediate(prefabInstance);
    39.                     }
    40.                 }
    41.             }
    42.         }
    43.     }
     

    Attached Files: