Search Unity

Nested prefab overwrite issue.

Discussion in 'Prefabs' started by Vince-Chimp, Oct 29, 2019.

  1. Vince-Chimp

    Vince-Chimp

    Joined:
    May 28, 2018
    Posts:
    43
    Currently, our artists are running into an annoying problem with nested prefabs in their UI's.

    The situation is like this:
    Scene > UI Controller > Main Menu > Coin Counter

    They are fixing something that did not look right about the coin counter. It looks all nice now. And then they go to the scene view, and it suddenly looks all broken, and they do not know why.

    What happened is that someone somehow somewhere by accident overwrote some of the transform values in one of the parents (scene, UI controller, or main menu) and they have no way to find that out easily either.

    Current workflow forces you to go up levels 1 by 1, and see which thing is overwriting these specific values, if you even know where to look in the first place.

    If i were to to recreate this example from a scene view perspective it would look like this:
    Normal view in scene:
    ss+(2019-10-29+at+10.54.00).png
    Overwriting the value in scene:
    ss+(2019-10-29+at+10.54.39).png
    Right click after altering the value, allows me to overwrite to any of the 3 prefabs or revert the value:
    ss+(2019-10-29+at+10.55.01).png
    I applied it to the main menu prefab for the test:
    ss+(2019-10-29+at+10.55.58).png

    Co-workers are now in a state where they have to go on a fetch quest to see who messed with their values, and right click and this UI now shows us nothing to tell us that something even changed as opposed to the first screenshot.

    My suggestion would be one of two to fix this:
    A) Right clicking this value from any level (scene or prefab/nested) would show "Component defined in CoinPrefab", "Component overwritten in MainMenu" for their lower levels, to inform the user that changes happened to this prefab on separate levels and where to look for them. If this was the right click menu that showed it, make the menu clickable so we can directly move to said overwritten version inside whatever prefab it may be.

    B) Make some sort of information window we can open to have an overview listing these overwrites in more detail for whatever selected prefab and it's nested children. But i imagine this being even harder and not adding all that much more information to the user.

    Let me know what you think, and if this is intended design by unity or not. I personally find this a really frustrating and confusing workflow as it is currently set up. Mainly because all the hints disappear the moment that a value is applied.
     
  2. runevision

    runevision

    Joined:
    Nov 28, 2007
    Posts:
    1,892
    Here's the easiest way to check where a value is overridden [edit: corrected steps]:
    1. In your scene, select the GameObject where you want to find an overridden value.
    2. Press the shortcut key (P) to go one layer in and check if the value is overridden there. Click the right arrow on the outermost Prefab instance the GameObject is part of to go one layer in and check if the value is overridden there. Keep repeating this.


    Since the same corresponding object is automatically selected at each step, you shouldn't need to do anything else than clicking those arrows repeatedly while looking at the property in the Inspector.
     
    Last edited: Oct 29, 2019
  3. Vince-Chimp

    Vince-Chimp

    Joined:
    May 28, 2018
    Posts:
    43
    @runevision Is this behaviour the same for all unity versions? I am sitting in 2018.3.7f1 at this moment, and if i select that prefab to watch it in the inspector, and press P, i go directly to the deepest prefab (CoinCounter) instead of what you describe.

    And even if it does, you would have to agree that it is not all that well communicated if a value is overwritten in some layer unless you are at that specific layer, right?
    I understand it wouldn't be easy to add something to signify a change, and in a lot of use cases it would just be useless to even show it. But still, there has to be a better way than to show the inspector with a value "as if nothing ever happened" like in my screenshots.
     
  4. runevision

    runevision

    Joined:
    Nov 28, 2007
    Posts:
    1,892
    Sorry, that's indeed true (for all versions). I mis-remembered how the shortcut works.

    Yeah, you'd have to instead press the right arrow for the outermost Prefab instance the object in question is part of. This is a bit more involved than simply pressing a key repeatedly. I agree there would be value in having something that makes it simpler to find out where overrides are one way or the other.

    It's not. There's different opinions about how much information from inner nested Prefabs should be accessible from the outside. Some people believe that Prefabs should work as "black boxes" where what's inside them should be an implementation detail, visible when you edit that Prefab Asset, but not visible form the outside. Others want more information about how the Prefab is structured inside available from the outside.

    Right now, we're having a lot of requests that Prefabs should work more like black boxes, so there's more control over which properties may be overridden - or even displayed at all - on instances of a Prefab. If we end up going this route, where many properties from a Prefab won't be displayed on the instance at all, we won't be able to display information about overrides on those properties inside the Prefab either. This approach would lend itself to digging into Prefabs (opening them) to see how they're structured, what nested Prefabs they might contain, and what overrides might be on those nested Prefabs, if you want to know those things, rather than being able to see everything without opening anything up.
     
  5. Vince-Chimp

    Vince-Chimp

    Joined:
    May 28, 2018
    Posts:
    43
    I can see reasons for going either way. But having them be extreme black boxes wouldn't help me building scenes over keeping overview in any kind of way. So i wonder where that would take the workflow.

    Think about this as one of the many usecases i suppose.

    I am currently trying to write some very convoluted editor script to work from the context menu in the hopes that i can print all changes per level, and hope that is can aid us in quickly pinpointing at least in text, what about a component changed on what level of this 3x nested prefab scenario.
     
  6. Vince-Chimp

    Vince-Chimp

    Joined:
    May 28, 2018
    Posts:
    43
    @runevision This has me kind of stumped. What i want to do, is i have a specific component on a specific gameobject selected in the scene. I then want to walk through all the nested prefab stages (i managed that part) and then somehow target "this object/component, but as seen from within this prefab" when all i have is this selected object/component in the scene view.

    So i use PrefabUtility.LoadPrefabContents() to get the prefab opened, but i have no idea how to find the correct object/component within that returned gameobject that is loaded. I could do some dirty searches, but it would break the moment something had duplicate naming or other silly things like that.

    And even if i managed that part properly, i would most likely have to use reflection to check each and every property on the component to see how they changed and build a report from that.

    Do you have any advice on how to make this workflow better. Are there methods that can aid me with this? Am i going about this the wrong way?
     
  7. runevision

    runevision

    Joined:
    Nov 28, 2007
    Posts:
    1,892
    If you just want to find via code which object has an override on it, it's far easier to just look at the Prefab Assets than it is to load the content.

    So you have your component in the scene. You can use GetCorrespondingObjectFromSource on this object and you'll get the corresponding object in the Prefab Asset back.You can keep using GetCorrespondingObjectFromSource to go further in.

    There's various ways you can extract overrides on these corresponding objects. You can get the SerializedObject and iterate over the SerializedProperties, checking which are overridden, or you can use GetPropertyModifications.

    If in the end you want to open the Prefab you found in Prefab Mode, you can call OpenAsset using the instanceID of the GameObject in the scene as parameter.
     
  8. Vince-Chimp

    Vince-Chimp

    Joined:
    May 28, 2018
    Posts:
    43
    @runevision Ok, so i managed to use that to find the information i needed. And now i have for examples two levels at which my variables are altered. So i would like to provide context to that by telling someone what prefab we are on about. But all i have is my component, as seen inside X prefab. And you can get a handle for that prefab through GetPrefabInstanceHandle, but that returns a non described object, and i can't ever find out in which exact prefab we are currently in name wise.

    I am sorry to say, but in general i can't make out anything from the documentation just because it assumes you know how the underlying system works. When that is exactly what i am trying to figure out. Most of the methods inside PrefabUtility have very little contextual clues as to when you would want to use them and what exactly you gain. It also speaks about outermost and nearest, like that makes sense. I would rather expect them to dig a prefab deeper or get parent prefab in nested prefab scenarios. (which used to be the naming but got removed in favor of this? ) I am just trying all the methods that sounds like they do what i want and hope for the best, which is a painful process because the naming is really vague on most of them.

    Code (CSharp):
    1.     static void GetOverrides(MenuCommand command)
    2.     {
    3.         if (UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage() != null)
    4.         {
    5.             Debug.LogError("You should only use this in Scene Mode. Not prefab edit mode.");
    6.             return;
    7.         }
    8.         //get the component we are interested in.
    9.         Component originalComponent = (Component)command.context;
    10.         //the top level of the prefab set first
    11.         Component targetComponent = originalComponent;
    12.         //iterate over all levels of the prefab that exist.
    13.         while (targetComponent != null)
    14.         {
    15.             targetComponent = PrefabUtility.GetCorrespondingObjectFromSource(targetComponent);
    16.             var nextTargetComponent = PrefabUtility.GetCorrespondingObjectFromSource(targetComponent);
    17.             if (nextTargetComponent == null)
    18.                 break;//we arived at the end anyway, no changes here.
    19.             var prefab = PrefabUtility.GetPrefabInstanceHandle(nextTargetComponent);
    20.             Debug.Log("PARENT: " + prefab.name + " : "+prefab.GetType());
    21. //IN THE LOG ABOVE I WANT TO HAVE THE NAME OF THE PREFAB I AM CURRENTLY INSIDE
    22.             var modifications = PrefabUtility.GetPropertyModifications(targetComponent);
    23.             PrintAllChanges(nextTargetComponent.GetInstanceID(), modifications);
    24.         }
    25.     }
    26.  
    27.     static void PrintAllChanges(int targetID, PropertyModification[] modifications)
    28.     {
    29.         if (modifications == null)
    30.         {
    31.             Debug.Log("No changed modifications");
    32.             return;
    33.         }
    34.         string changes = "Found changes "+ targetID + ":\n";
    35.         foreach (var item in modifications)
    36.         {
    37.             if (item.target != null && item.target.GetInstanceID() == targetID)
    38.                 changes += item.target.name + " : " + item.target.GetType().ToString() + " : " + item.propertyPath + " : " + item.value.ToString() + "\n";
    39.         }
    40.         Debug.Log(changes);
    41.     }
     
  9. runevision

    runevision

    Joined:
    Nov 28, 2007
    Posts:
    1,892
    You can get the name of the Prefab Asset your component is inside by using
    component.transform.root.gameObject.name
    .
    You can also get the asset path using AssetDatabase.GetAssetPath.
     
  10. Vince-Chimp

    Vince-Chimp

    Joined:
    May 28, 2018
    Posts:
    43
    For anyone stumbling on this ever in the future, i ended up making my own inspector window which was quite complicated, but shows all layers of a nested prefab. I have played this editor through to Unity so maybe it will see a future addition to the editor? Who knows.
    nestedPrefabs.png
     
    leohilbert likes this.
  11. Mads-Nyholm

    Mads-Nyholm

    Unity Technologies

    Joined:
    Aug 19, 2013
    Posts:
    219
    Apologies for the late reply. This gives an excellent overview of Prefab instance overrides. Great work. Did you continue on this tool?
     
    cstahle_littleOrbit likes this.
  12. Vince-Chimp

    Vince-Chimp

    Joined:
    May 28, 2018
    Posts:
    43
    I did not further improve on the tool beyond what you saw above. I know it has some flaws on how it renders things, and how it communicates some changes. Though this was not a showstopper for us, and the tool does what we need it to do when debugging override issues. Which so far has been it's only use-case.
    So i have no way of motivating to my boss to spend more time on this tool beyond what i had :)
     
    Mads-Nyholm and Unifikation like this.
  13. Unifikation

    Unifikation

    Joined:
    Jan 4, 2023
    Posts:
    1,087
    It's an astonishing achievement. I can't imagine the pain of dealing with those two collections of API - prefabs and custom editor stuff. Well done!
     
    Vince-Chimp likes this.