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. Dismiss Notice

Problem with Prefab API upgrade

Discussion in 'Prefabs' started by Jiraiyah, Jun 20, 2018.

  1. Jiraiyah

    Jiraiyah

    Joined:
    Mar 4, 2013
    Posts:
    175
    hi, take a look at this code snippet :

    Code (CSharp):
    1.     var go = PrefabUtility.InstantiatePrefab (gameObject) as GameObject;
    2.     if (go != null)
    3.     {
    4.         var components = go.GetComponentsInChildren <SpriteRenderer> (true);
    5.         foreach (var renderer in components)
    6.             renderer.material = material;
    7.             var prefabParent = PrefabUtility.GetCorrespondingObjectFromSource (go);
    8.             if (prefabParent != null)
    9.             {
    10.                 var obj = PrefabUtility.GetOutermostPrefabInstanceRoot (go);
    11.                 if (obj != null)
    12.                     PrefabUtility.ReplacePrefab (obj, prefabParent, ReplacePrefabOptions.ConnectToPrefab);
    13.             }
    14.             DestroyImmediate (go);
    15.     }
    16.  
    Tried to import it into 2018.2.0.x-Prefab version (The one with nested prefabs) and got a message that ReplacePrefab is now obsolete. How should i change that line of code?

    Code (CSharp):
    1. PrefabUtility.ReplacePrefab (obj, prefabParent, ReplacePrefabOptions.ConnectToPrefab);


    The params are totally different and there is no documentation on this.

    I know that Nested Prefab is not scheduled for 2018.2 but maybe 2018.3 but i want to adopt early because i am sitting right on the start of a production pipe line and this can make a huge difference for me during the production time. so, someone please help?
     
    Deeeds likes this.
  2. SteenLund

    SteenLund

    Unity Technologies

    Joined:
    Jan 20, 2011
    Posts:
    639
    So let me see if I got this right, you basically want to change the material of all the renders in a Prefab

    Try this:
    Code (CSharp):
    1.  
    2. var rootGameObject = PrefabUtility.LoadPrefabContent(pathToPrefab);
    3.  
    4. var components = rootGameObject.GetComponentsInChildren <SpriteRenderer> (true);
    5. foreach (var renderer in components)
    6.     renderer.material = material;
    7.  
    8. PrefabUtility.SaveAsPrefabAsset(rootGameObject, pathToPrefab);
    9. PrefabUtility.UnloadPrefabContent(rootGameObject);
    10.  
    Basically what happens here is that we load the prefab source directly, modify the object, write back to assets folder and unload the content in memory.
     
    Last edited: Jun 20, 2018
    tosiabunio and rakkarage like this.
  3. Jiraiyah

    Jiraiyah

    Joined:
    Mar 4, 2013
    Posts:
    175
    exactly
     
  4. Jiraiyah

    Jiraiyah

    Joined:
    Mar 4, 2013
    Posts:
    175
    in case it helps, this is the whole code :

    Code (CSharp):
    1. if (material != null)
    2.     foreach (var gameObject in selection)
    3.     {
    4.         var ispref = PrefabUtility.GetCorrespondingObjectFromSource (gameObject) == null &&
    5.         PrefabUtility.GetPrefabObject (gameObject) != null;
    6.         if (! ispref)
    7.         {
    8.             var components = gameObject.GetComponentsInChildren <SpriteRenderer> (true);
    9.             foreach (var renderer in components)
    10.                 renderer.material = material;
    11.             if (prefabifyMaterial)
    12.             {
    13.                 var prefabParent = PrefabUtility.GetCorrespondingObjectFromSource (gameObject);
    14.                 if (prefabParent != null)
    15.                 {
    16.                     var obj = PrefabUtility.GetOutermostPrefabInstanceRoot (gameObject);
    17.                     if (obj != null)
    18.                         PrefabUtility.ReplacePrefab (obj, prefabParent, ReplacePrefabOptions.ConnectToPrefab);
    19.                  }
    20.              }
    21.          }
    22.          else
    23.          {
    24.              var go = PrefabUtility.InstantiatePrefab (gameObject) as GameObject;
    25.              if (go != null)
    26.              {
    27.                  var components = go.GetComponentsInChildren <SpriteRenderer> (true);
    28.                  foreach (var renderer in components)
    29.                      renderer.material = material;
    30.                  var prefabParent = PrefabUtility.GetCorrespondingObjectFromSource (go);
    31.                  if (prefabParent != null)
    32.                  {
    33.                      var obj = PrefabUtility.GetOutermostPrefabInstanceRoot (go);
    34.                      if (obj != null)
    35.                          PrefabUtility.ReplacePrefab (obj, prefabParent, ReplacePrefabOptions.ConnectToPrefab);
    36.                  }
    37.                  DestroyImmediate (go);
    38.              }
    39.          }
    40.     }
    let me explain what is going on, I want to parse through the selected objects and replace their materials to something else, now, i need to identify if the selection is the game objects in the scene hierarchy or prefabs inside project window.

    if they are project prefabs, instantiate, replace, apply, delete the instance (couldn't find anyway to directly tweak the prefab material)
    if they are scene view objects, change the material and check if i need to make a prefab (a toggle inside an editor window) if so, make a prefab out of them in pre cached folder address.

    I think even the above code is wrong to some extend but not sure.
     
    Last edited: Jun 21, 2018
  5. SteenLund

    SteenLund

    Unity Technologies

    Joined:
    Jan 20, 2011
    Posts:
    639
    My little snippet can easily be fit into that. You just need to find the path to the asset you want to modify using AssetDatabase API.

    This should do the same

    Code (CSharp):
    1. if (material != null)
    2. {
    3.     var prefabAssets = new HashSet<string>();
    4.  
    5.     // Get set of prefab assets so we only change them once
    6.     foreach (var gameObject in selection)
    7.     {
    8.         if (!PrefabUtility.IsPartOfAnyPrefab(gameObject))  // Skip regular GameObjects
    9.             continue;
    10.  
    11.         bool isImmutablePrefab = PrefabUtility.IsPartOfImmutablePrefab(gameObject);
    12.         bool isVariantPrefab = PrefabUtility.IsPartOfVariantPrefab(gameObject);
    13.  
    14.         if (isImmutablePrefab || isVariantPrefab)  // Skip Models, read only prefabs and Variants
    15.             continue;
    16.  
    17.         bool isInstance = PrefabUtility.IsPartOfPrefabInstance(gameObject);
    18.         bool isAsset = PrefabUtility.IsPartOfPrefabAsset(gameObject);
    19.         bool isSceneInstance = isInstance && !isAsset;
    20.  
    21.         if (isSceneInstance)
    22.             gameObject = PrefabUtility.GetCorrespondingObjectFromSource(gameObject);
    23.  
    24.         prefabAssets.Add(AssetDatabase.GetAssetPath(gameObject));
    25.     }
    26.  
    27.  
    28.     foreach(string prefabAssetPath in prefabAssets)
    29.     {
    30.         var rootGameObject = PrefabUtility.LoadPrefabContent(prefabAssetPath);
    31.        
    32.         var components = rootGameObject.GetComponentsInChildren <SpriteRenderer> (true);
    33.         foreach (var renderer in components)
    34.             renderer.material = material;
    35.        
    36.         PrefabUtility.SaveAsPrefabAsset(rootGameObject, prefabAssetPath);
    37.         PrefabUtility.UnloadPrefabContent(rootGameObject);
    38.     }
    39. }
    40.  
     
    Jiraiyah likes this.
  6. Jiraiyah

    Jiraiyah

    Joined:
    Mar 4, 2013
    Posts:
    175
    No such method under prefab utility :
    PrefabUtility.IsPartOfAnyPrefab(gameObject) ???
    PrefabUtility.IsPartOfImmutablePrefab ???

    Edit : sorry, was refrencing wrong dll files. solved
     
  7. Jiraiyah

    Jiraiyah

    Joined:
    Mar 4, 2013
    Posts:
    175
    Ok, for future reference and those people who are interested, this is what i came up with, this method will remove any Collider2D from any object, being a normal scene object without prefabs, being an instance of prefab in the game or being the prefab sitting in project, each one you send into this method, will get it's collider deleted. tweaking this for any other component manipulation should work easily
    Code (CSharp):
    1. public static void RemoveColliders (IEnumerable <GameObject> selection)//
    2.         {
    3.             var prefabAssets = new HashSet <string> ();
    4.             GameObject go;
    5.             foreach (var gameObject in selection)
    6.             {
    7.                 if (!PrefabUtility.IsPartOfAnyPrefab (gameObject))
    8.                 {
    9.                     var components = gameObject.GetComponentsInChildren <Collider2D> (true);
    10.                     foreach (var component in components)
    11.                         Object.DestroyImmediate (component);
    12.                     continue;
    13.                 }
    14.  
    15.                 if (IsSceneInstance (gameObject))
    16.                 {
    17.                     var isImmutablePrefab = PrefabUtility.IsPartOfImmutablePrefab (gameObject);
    18.                     var isVariantPrefab = PrefabUtility.IsPartOfVariantPrefab (gameObject);
    19.                     if (isImmutablePrefab || isVariantPrefab)
    20.                         continue;
    21.                     go = PrefabUtility.GetCorrespondingObjectFromSource (gameObject);
    22.                     if (go != null)
    23.                         prefabAssets.Add (AssetDatabase.GetAssetPath (go));
    24.                     foreach (var asset in prefabAssets)
    25.                     {
    26.                         var rootGameObject = PrefabUtility.LoadPrefabContents (asset);
    27.                         var components = rootGameObject.GetComponentsInChildren <Collider2D> (true);
    28.                         foreach (var component in components)
    29.                             Object.DestroyImmediate (component);
    30.                         PrefabUtility.SaveAsPrefabAsset (rootGameObject, asset);
    31.                         PrefabUtility.UnloadPrefabContents (rootGameObject);
    32.                     }
    33.                 }
    34.                 else
    35.                 {
    36.                     var isImmutablePrefab = PrefabUtility.IsPartOfImmutablePrefab (gameObject);
    37.                     var isVariantPrefab = PrefabUtility.IsPartOfVariantPrefab (gameObject);
    38.                     if (isImmutablePrefab || isVariantPrefab)
    39.                         continue;
    40.                     prefabAssets.Add (AssetDatabase.GetAssetPath (gameObject));
    41.                     foreach (var asset in prefabAssets)
    42.                     {
    43.                         var rootGameObject = PrefabUtility.LoadPrefabContents (asset);
    44.                         var components = rootGameObject.GetComponentsInChildren <Collider2D> (true);
    45.                         foreach (var component in components)
    46.                             Object.DestroyImmediate (component);
    47.                         PrefabUtility.SaveAsPrefabAsset (rootGameObject, asset);
    48.                         PrefabUtility.UnloadPrefabContents (rootGameObject);
    49.                     }
    50.                 }
    51.             }
    52.         }
    and here is the IsSceneInstance method :
    Code (CSharp):
    1.         private static bool IsSceneInstance (GameObject gameObject)
    2.         {
    3.             var isInstance = PrefabUtility.IsPartOfPrefabInstance (gameObject);
    4.             var isAsset = PrefabUtility.IsPartOfPrefabAsset (gameObject);
    5.             var isSceneInstance = isInstance && !isAsset;
    6.             return isSceneInstance;
    7.         }
    8.  
     
    PowerhoofDave and Deeeds like this.
  8. Deeeds

    Deeeds

    Joined:
    Mar 15, 2018
    Posts:
    739

    THANK YOU!!!
     
  9. Jiraiyah

    Jiraiyah

    Joined:
    Mar 4, 2013
    Posts:
    175
    was thinking of making a generic method out of it, but no matter how i looked at it, it won't work with Func or Action because the body of the component manipulation will accept unknown amount of params, the only possible "sane" way for that would be a delegate and i don't know if it's worth it or not, if anyone can make a good generic class out of the whole method, it would be nice because each time we would want to tweak params on a component on something via code in editor we would need the whole thing except few lines.

    Also, there is room for a bit of code clean up and optimization there for example, do the scene instance check after the immutable stuff and close it before foreach loop, so that you won't have duplicated code but i kept it this way for better readability and understanding of what is going on.
     
    Deeeds likes this.
  10. Deeeds

    Deeeds

    Joined:
    Mar 15, 2018
    Posts:
    739
    I'm not in the same universe as that guy. Not a coder... I'm a visual thinker. And dreamer. So very sorry. I cannot help take this further. Can only be extremely grateful that it's exactly pertinent to something I'm doing, in a most amazingly lucky coincident way, as I need to remove and change 2D colliders.
     
  11. Deeeds

    Deeeds

    Joined:
    Mar 15, 2018
    Posts:
    739
    On generics, I got to a point of understanding them and using them ( a little ) in Swift, for a while. But I'm yet to get into them in C#. They intimidate me because using and testing them is a bit involved, and I'm yet to find situations where I think they'd benefit how I normally do and see things. I get their usefulness, but they're overkill for me, most of the time. It seems that strict type languages should ultimately be generically used, but what an overload of the mind to work that way! Theoretically good, practically a bit of a time drain for anyone but tool level coders, I think.
     
  12. Jiraiyah

    Jiraiyah

    Joined:
    Mar 4, 2013
    Posts:
    175
    Well, honestly, the thing is that, tweaking parameters on components, is a very common thing to happen when it comes to custom editor designing and programming. with previous api, you didn't have to pull your hair when it would come to prefabs, you would simply check if it was an instance or not, if yes, replace, if not instantiate, tweak replace, remove. that would be only 4 or 5 lines of code and that would be it. but with the new api, each time you are doing something new on the prefabs, there is more and more over head stuff to care about by code.
    I can just hope that in future, the API would become more programmer friendly, like just add a bool flag on a method call and the API would handle the rest of stuff behind the scene. But a man can only wish !
     
    PowerhoofDave likes this.
  13. ImperialDynamics

    ImperialDynamics

    Joined:
    Jun 21, 2018
    Posts:
    21
    hello. When i use your code i always get a "prefab was changed on disk" dialog. How can i make it not show that dialog? :(
     
  14. winxalex

    winxalex

    Joined:
    Jun 29, 2014
    Posts:
    166
    I want to add component to particular nested game object. game object is nested in the prefab. On stage I've prefab instance. I drop component on particular nested game object. I could find it original source in prefab by
    Code (CSharp):
    1. gameObject = PrefabUtility.GetCorrespondingObjectFromOriginalSource(gameObject);
    2.                             prefabPath = AssetDatabase.GetAssetPath(gameObject); //get prefab.asset path, no matter if you supply and root or nested gameObject
    3.                             gameObject = PrefabUtility.LoadPrefabContents(prefabPath);//open root prefab asset into prefab content
    I can open prefab as above. But how to find now GameObject inside opened prefab so I can add the component.Of course latter I'll close the prefab and save after changes. I couldn't find the way to find which game object in prefab instance is associated with game object in prefab content. In the past you can use GetPrefabParent and can add component to it. Now opening prefab ....?????
     
    Last edited: Feb 4, 2020
  15. nasos_333

    nasos_333

    Joined:
    Feb 13, 2013
    Posts:
    12,898
    Hi, is there a reason why this path finding process is not handled internally ?

    Thanks