Search Unity

Child Prefab Overrides

Discussion in 'Prefabs' started by Griffo, Mar 5, 2019.

  1. Griffo

    Griffo

    Joined:
    Jul 5, 2011
    Posts:
    700
    Hi,

    Unity 2018.3.0f2

    I'm getting use to using the new prefab setup, but I was wondering can you not just override any changes to a child prefab?

    What I've done is made 4 rectransforms with their own children then saved each rectransform as a prefab and any changes to them I can override, but then when I made a canvas and added each rectransform then after saving the canvas as a prefab the children rectransforms have no override button so any changes to them I have to override the canvas as I saved as a prefab, then looking at the 4 rectransforms prefabs I saved the changes are not saved to those only the parent canvas prefab.

    Thanks.
     
  2. runevision

    runevision

    Joined:
    Nov 28, 2007
    Posts:
    1,892
    Right, if you want to apply an entire Prefab at a time, you can only apply the outermost Prefab to its corresponding asset.

    However, you can apply individual components (such as RectTransform) all the way to the innermost Prefab by using the cog button in the Component, or by clicking on the component in the Overrides dropdown and using the Apply dropdown in the comparison view.
     
    NonPolynomialTim and Griffo like this.
  3. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    978
    It's not possible from the inspector, but I'm curious why that is..
    Since you can do it on individual components (through multiple selections and clicks) why not give the option to override the entire child prefab directly (faster)?

    Maybe it's possible by custom scripting?

    I sometimes make a bunch of changes on a child prefab and then I'm too lazy to go over every element and apply them.

    I'll try the tool scripting in this case. But if you can satisfy my curiosity that would be great :)




    EDIT: Getting tired, I'll finish this script tomorrow. I'm close but it's not quite working yet.

    Code (CSharp):
    1.     [MenuItem("GameObject/Prefab/Apply Nearest Prefab", false, 1)]
    2.     public static void ApplyNearestPrefab()
    3.     {
    4.         Debug.Log("updating");
    5.         var source = Selection.activeObject;
    6.         if (source == null) return;
    7.         var nearestPrefab = PrefabUtility.GetNearestPrefabInstanceRoot(source);
    8.         if (nearestPrefab == null) return;
    9.         Debug.Log($"nearestPrefab: {nearestPrefab.name}");
    10.         var prefabStatus = PrefabUtility.GetPrefabInstanceStatus(source);
    11.         if (prefabStatus != PrefabInstanceStatus.Connected) return;
    12.        
    13.         var obj = nearestPrefab.gameObject;
    14.         // var path1 = PrefabStageUtility.GetPrefabStage(nearestPrefab).assetPath;
    15.         // var path = PrefabStageUtility.GetPrefabStage(obj).assetPath;
    16.         var modifications = PrefabUtility.GetPropertyModifications(obj);
    17.                    
    18.         var path = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(obj);
    19.                    
    20.         foreach (var m in modifications)
    21.         {
    22.             Debug.Log($"modif: {m.propertyPath}");
    23.             SerializedObject serializedObject = new UnityEditor.SerializedObject(obj);
    24.             SerializedProperty sp = serializedObject.FindProperty(m.propertyPath);
    25.                        
    26.             if (sp != null)
    27.             {
    28.                 PrefabUtility.ApplyPropertyOverride(sp, path, InteractionMode.UserAction);
    29.             }
    30.             else
    31.             {
    32.                            
    33.                 Debug.LogError($"can't find: {m.propertyPath} {m.target.name}");
    34.             }
    35.         }
    36.        
    37.         // PrefabUtility.ApplyObjectOverride(obj, path, InteractionMode.UserAction);
    38.         // PrefabUtility.ApplyPrefabInstance(obj, InteractionMode.UserAction);
    39.     }
     
    Last edited: Oct 16, 2020
  4. runevision

    runevision

    Joined:
    Nov 28, 2007
    Posts:
    1,892
    Because the functionality of applying a whole Prefab to an arbitrary apply target is much more complex than it is for a single object.

    For a single object you have a single linear chain of apply targets.

    For a whole Prefab, different objects in the Prefab may each have their own separate chains of possible apply targets. For a given apply target, not all objects are going to be part of that target at all, so even though they're overrides, they wouldn't make sense to display for all apply targets.

    So you would have to first choose an apply target, and then the list of overrides might completely change based on which apply target you've chosen. We did actually implement this, and it was sufficiently confusing that people got scared of using the Prefab functionality at all. When we decided as a result to reduce the complexity by only supporting applying to the nearest Prefab when applying the whole Prefab at once (but still support apply targets for individual objects) the UI got a lot simpler, and people were much more comfortable using it without worrying they were making mistakes.

    Anyway, I hope your script will work out well for you. But our general recommendation is that if you're editing a lot of objects you want to be applied to a nested Prefab anyway, it's probably better to open that Prefab in Prefab Mode and make the edits directly there.
     
  5. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    978
    Very well :)

    @runevision I have another question if you don't mind:
    I realized that the id returned by the PrefabUtility.GetRemovedComponents's assetComponent.gameObject is not the same as the id given by
    PrefabUtility.GetNearestPrefabInstanceRoot()

    So in the code below, I had to check by name that the 2 values returned by those functions are the same. But that's dangerous. Do you have any idea what I can do to check that these 2 gameObjects are the same? Somehow they are the same but have different ID's.

    Here's the script:

    EDIT: I edited the script with runevision's suggestion.

    Code (CSharp):
    1.  
    2. #if UNITY_EDITOR
    3. using UnityEditor;
    4. using UnityEngine;
    5.  
    6.  
    7. // https://forum.unity.com/threads/child-prefab-overrides.639367/#post-6424480
    8. public static class PrefabEditorUtility
    9. {
    10.     private static InteractionMode Action = InteractionMode.AutomatedAction;
    11.  
    12.     private static bool IsPartOfNearest(GameObject _current, GameObject _nearest)
    13.     {
    14.         var currentNearestPrefab = PrefabUtility.GetNearestPrefabInstanceRoot(_current);
    15.         return (_nearest == currentNearestPrefab);
    16.     }
    17.  
    18.     private static bool IsPartOfNearestByName(GameObject _current, GameObject _nearest)
    19.     {
    20.         var currentNearestPrefab = PrefabUtility.GetNearestPrefabInstanceRoot(_current);
    21.         // Debug.Log($"currentNearestPrefab: {currentNearestPrefab.GetInstanceID()}   and nearestPrefab: {_nearest.GetInstanceID()}");
    22.         return (_nearest.name == currentNearestPrefab.name);
    23.     }
    24.    
    25.     [MenuItem("GameObject/Prefab/Apply Overrides To Nearest Prefab", false, 1)]
    26.     public static void ApplyOverridesToNearestPrefab()
    27.     {
    28.         var source = Selection.activeGameObject;
    29.         if (source == null) return;
    30.         var nearestPrefab = PrefabUtility.GetNearestPrefabInstanceRoot(source);
    31.         if (nearestPrefab == null) return;
    32.         Debug.Log($"Applying to prefab: {nearestPrefab.name} ...");
    33.         var prefabStatus = PrefabUtility.GetPrefabInstanceStatus(source);
    34.         if (prefabStatus != PrefabInstanceStatus.Connected) return;
    35.        
    36.         var nearestGo = nearestPrefab;
    37.         var nearestPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(nearestGo);
    38.  
    39.         var components = nearestGo.GetComponents<Component>();
    40.  
    41.         ApplyAddedComponentsToNearest(source);
    42.         ApplyRemovedComponentsToNearest(source);
    43.         ApplyAddedGameObjectsToNearest(source);
    44.        
    45.         PrefabUtility.ApplyObjectOverride(source, nearestPath, Action);
    46.         foreach (var c in components)
    47.             PrefabUtility.ApplyObjectOverride(c, nearestPath, Action);
    48.     }
    49.  
    50.     private static void ApplyRemovedComponentsToNearest(GameObject go)
    51.     {
    52.         var nearestGo = PrefabUtility.GetNearestPrefabInstanceRoot(go);
    53.         var nearestPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(nearestGo);
    54.  
    55.         var removedComponents = PrefabUtility.GetRemovedComponents(nearestGo); // these results are from the root prefab, so we get too much
    56.         foreach (var removedComponent in removedComponents)
    57.         {
    58.             var myGo = removedComponent.assetComponent.gameObject;
    59.             var prefabPath = AssetDatabase.GetAssetPath(myGo);
    60.             var instanceGo = PrefabUtility.GetCorrespondingObjectFromSourceAtPath(nearestGo, prefabPath);
    61.  
    62.             if (instanceGo == myGo)
    63.             {
    64.                 // Debug.Log($"!!!! removing component: {myGo.name}");
    65.                 removedComponent.Apply(nearestPath, Action);
    66.             }
    67.             else
    68.             {
    69.                 Debug.Log($"not removing component from: {myGo.name}. Its nearest prefab is not: {instanceGo.name}");
    70.             }
    71.         }
    72.     }
    73.  
    74.     private static void ApplyAddedComponentsToNearest(GameObject go)
    75.     {
    76.         var nearestPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(go);
    77.         var nearestPrefab = PrefabUtility.GetNearestPrefabInstanceRoot(go);
    78.  
    79.         var components = PrefabUtility.GetAddedComponents(nearestPrefab); // these are for the root asset, so we get too much
    80.         foreach (var c in components)
    81.         {
    82.             if (IsPartOfNearest(c.instanceComponent.gameObject, nearestPrefab))
    83.             {
    84.                 // Debug.Log($"applied added: {c.instanceComponent.gameObject.name}");
    85.                 PrefabUtility.ApplyAddedComponent(c.instanceComponent, nearestPath, Action);
    86.             }
    87.             else
    88.             {
    89.                 // Debug.Log($"not child of prefab: {c.instanceComponent.gameObject.name}");
    90.             }
    91.         }
    92.     }
    93.  
    94.     private static void ApplyAddedGameObjectsToNearest(GameObject go)
    95.     {
    96.         var nearestPrefab = PrefabUtility.GetNearestPrefabInstanceRoot(go);
    97.         var nearestPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(nearestPrefab);
    98.  
    99.         var addedGos = PrefabUtility.GetAddedGameObjects(nearestPrefab); // these results are from the root prefab, so we get too much
    100.         foreach (var addedGo in addedGos)
    101.         {
    102.             var myGo = addedGo.instanceGameObject;
    103.             if (myGo.transform.IsChildOf(nearestPrefab.transform))
    104.             {
    105.                 // Checking that the transform does not have a prefab parent before the nearest
    106.                 var currTransform = myGo.transform;
    107.                 while (currTransform.parent != null)
    108.                 {
    109.                     var closestPrefab = PrefabUtility.GetNearestPrefabInstanceRoot(currTransform.gameObject);
    110.                     // Debug.LogError($"{currTransform.name} check...");
    111.                     if (closestPrefab != null && closestPrefab == nearestPrefab)
    112.                     {
    113.                         Debug.Log($"allowed: {currTransform.name}");
    114.                         PrefabUtility.ApplyAddedGameObject(myGo, nearestPath, Action);
    115.                         break;
    116.                     }
    117.                     else if (closestPrefab != null && closestPrefab != nearestPrefab)
    118.                     {
    119.                         Debug.LogError($"{myGo.name}'s closest prefab is {closestPrefab.name}. It's not {nearestPrefab.name} so it's refused.");
    120.                         break;
    121.                     }
    122.                    
    123.                     currTransform = currTransform.parent;
    124.                 }
    125.             }
    126.             else
    127.             {
    128.                 Debug.Log($"not go adding: {myGo.name}");
    129.             }
    130.         }
    131.     }
    132.  
    133.  
    134. }
    135. #endif
    136.  
    Use it like this:
    upload_2020-10-16_14-24-40.png
     
    Last edited: Oct 16, 2020
  6. runevision

    runevision

    Joined:
    Nov 28, 2007
    Posts:
    1,892
    Hmm, on line 36 you have:
    Code (CSharp):
    1. var nearestGo = nearestPrefab.gameObject;
    But nearestPrefab is already a GameObject and its .gameObject member just returns itself...

    Anyway...
    Well assetComponent.gameObject is a GameObject in the Asset while PrefabUtility.GetNearestPrefabInstanceRoot() returns a GameObject in the instance... Those won't have the same id because they are different objects.

    You can use this to get the asset object that corresponds to your instance object:
    https://docs.unity3d.com/ScriptReference/PrefabUtility.GetCorrespondingObjectFromSourceAtPath.html
     
    FeastSC2 likes this.