Search Unity

  1. Improved Prefab workflow (includes Nested Prefabs!), 2D isometric Tilemap and more! Get the 2018.3 Beta now.
    Dismiss Notice
  2. The Unity Pro & Visual Studio Professional Bundle gives you the tools you need to develop faster & collaborate more efficiently. Learn more.
    Dismiss Notice
  3. Let us know a bit about your interests, and if you'd like to become more directly involved. Take our survey!
    Dismiss Notice
  4. Improve your Unity skills with a certified instructor in a private, interactive classroom. Watch the overview now.
    Dismiss Notice
  5. Want to see the most recent patch releases? Take a peek at the patch release page.
    Dismiss Notice

Prefab relative overrides?

Discussion in 'Improved Prefabs' started by Per, Sep 14, 2018.

  1. Per

    Per

    Joined:
    Jun 25, 2009
    Posts:
    456
    Is it possible to set up a relative override with the prefabs? It seems like you can only set an absolute override value, sometimes it would be useful to keep relative values.

    e.g. lets say you had a prefab with a default value for some parameter of 10.0 and you had an override on a local prefab that set this value to 12.0. But for whatever reason you needed to change the original prefab so that this value was now 1.0 your overriden values would now be either 1.2 (scaled relative) or 3.0 (relative offset). Most of the time this would be more useful to me than absolute overrides that always keep the overriden value at 12.0.
     
  2. Per

    Per

    Joined:
    Jun 25, 2009
    Posts:
    456
    Anyone know if this is possible?
     
  3. hippocoder

    hippocoder

    Digital Ape Moderator

    Joined:
    Apr 11, 2010
    Posts:
    23,328
    Moved to Improved Prefabs (Unity prefers the discussion in one place)
     
  4. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    450
    In the old scheme, which I think is perfectly valid and even preferable, you would attach a script and have it manage minor differences, rather than a lot of complex UI to manage variation like this. "OgreClan.cs" would have an "OgreVariation" enum, and then artists could select fifty ogres as "OokThogClan" which on startup increases their HP by 1.25x, colors their skin red, and gives them a premium weapon.

    I think the whole Prefab/Variant thing is sounding a lot like a shiny new hammer which makes everything look like a nail.
     
    Deeeds likes this.
  5. Deeeds

    Deeeds

    Joined:
    Mar 15, 2018
    Posts:
    739
    Yes. This.

    It's a case of programmers imagining the designers would thank them for surfacing OOP hierarchical "thinking", without realising designers don't work to stipulated requirements, they're creatives, for whom the entire thing is too rigid and brittle to be much use for anything but completely preconceived and utterly specified production, if Unity were able to teach them what Variants are, and how they work.

    Programmers can understand Variants because they're about inheritance and model classic OOP hierarchical Class-like thinking.

    But they can't articulate what that means to users that aren't programmers, nor how it works, or what it offers.

    Decades of 3ds Max trying to explain the benefits of the Modifier Stack are the best example of this, in design software history.

    Further, it seems nobody responsible for articulating Prefab Variants to the unwashed masses has any idea how Smart Objects and Symbols work in other creative software, the only equivalent space from which the discussion can reference something slightly similar and reasonably well known. Mind you, it's not a huge percentage of designers that fully understand and utilise these facilities, either...
     
  6. runevision

    runevision

    Unity Technologies

    Joined:
    Nov 28, 2007
    Posts:
    1,319
    It's not. (not before and not now)

    You can still do that. All functionality in the improved Prefabs is based on user feedback and use cases developed together with users and verified with users, but it's obviously not a solution to every conceivable problem. If something is better solved with another existing solution, use that other solution.
     
    halley likes this.
  7. hippocoder

    hippocoder

    Digital Ape Moderator

    Joined:
    Apr 11, 2010
    Posts:
    23,328
    Users before the users posting now ;)

    But regarding the issues with prefabs in general, I think really it's just a powerful feature so obviously there should be a best practise guide to accompany it once it comes out, so people don't end up creating more problems. I think it's a similar lesson to learn for any feature really. The feedback pushing against the prefabs can be used to write up the best practises guide / docs.
     
  8. Per

    Per

    Joined:
    Jun 25, 2009
    Posts:
    456
    Ok, that's a shame. I'm not sure the need to let me know it wasn't possible before though, this I already know. It just seemed like an obvious thing to be able to do once I saw the new system.

    Anyhoo, I can't comment on the issues anyone else brought up in the thread, but I sure would like to be able to have relative values so please add it to the wishlist.
     
    Deeeds likes this.
  9. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    450

    He simply answered what looked like a question instead of a feature-request. And often, explaining things directly helps educate other people on the forums, so don't take it personally if someone answers a question that was tangent to your inquiry, although I personally also took your original question as a question. It kinda looks like a question, with the question mark and all.
     
  10. Per

    Per

    Joined:
    Jun 25, 2009
    Posts:
    456
    It felt unnecessarily emphatic. I didn't think the question was unreasonable or rude. "It's not." was enough.
     
    Deeeds likes this.
  11. Jeff-Berry

    Jeff-Berry

    Joined:
    Aug 31, 2012
    Posts:
    82
    Bumping this as this feature just seems like a natural progression for prefab variants.

    When i think of a prefab variant, I think of a new unit or power up that is 2x stronger, of 1/2 as fast, not some hard override of "this unit is 34985.43 speed, and this one is 9098.2 speed" and they will both remain locked in stone.

    To adjust values globally we would have to go in and adjust them all individually. This diminishes one of the primary reason of using prefab variants in the first place.

    I only realized I needed this feature after getting the beta and actually making variants, and I believe it's only a matter of time before this idea gets more traction, but here's another vote in this phase of beta.

    I fully understand the need of having laser focus to bring this system to prime time, and the headache of getting these assets serialized and working from version to version, but if its logistically possible to expand this system in the future, this functionality would be incredibly powerful for designers across the board looking to use prefab variants to globally balance their assets against each other.
     
    Last edited: Oct 11, 2018
  12. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    3,346
    It's not very hard to make a multiplier-value type with a nice property drawer. You could even have the property drawer only draw the multiplier value if the property's on a prefab override.

    That's much more flexible than having every single value in Unity have support for a relative override. They'd have to change the serialization format for prefab overrides, and possibly add a bunch of data to all overrides in order to mark what kind of override it is.
     
    hippocoder and runevision like this.
  13. runevision

    runevision

    Unity Technologies

    Joined:
    Nov 28, 2007
    Posts:
    1,319
    Very good points here.

    Relative overrides could mean many things. Should an override add a certain amount over the Prefab's value (addition) or multiply it with a value (multiplication)? How does it work for floats versus integers (not to speak of other types that are not numbers at all)? What should happen if the addition or multiplication takes the value over the allowed range for the property? And if it means it should get clamped or similar, how do we display it all in a sensible way?

    All these considerations really are quite gameplay specific, and even if we made a very complicated system that handles a lot of cases, it would still not cover everything.

    I think @Baste's suggestion of instead forming your properties such that you can apply multipliers already with the existing system is spot on. Want a multiplier? Have a multiplier property in your script which is by default 1 but can be set to a different value in a Variant or similar. Want it to work as addition instead of multiplication? Call it "boost" or something and make it be added to the other property (zero by default). How it handles allowed ranges and other special cases is something you can decide based on what makes sense for your game. And instead of adding extra serialized data for every override that will bloat Prefab files, it will only take up extra data where you are actually using it.
     
    hippocoder likes this.
  14. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    3,346
    I felt like I needed some experience with all of these new prefab things, so I just went and made it.

    upload_2018-10-11_14-24-59.png

    Code (csharp):
    1. using System;
    2. using UnityEngine;
    3.  
    4. [Serializable]
    5. public class IntVariant
    6. {
    7.     [SerializeField]
    8.     private int value;
    9.  
    10.     [SerializeField]
    11.     private int multiplier = 1;
    12.  
    13.     public static implicit operator int(IntVariant variant)
    14.     {
    15.         return variant.value * variant.multiplier;
    16.     }
    17. }

    Code (csharp):
    1. using System;
    2. using UnityEngine;
    3.  
    4. public class TestScript : MonoBehaviour
    5. {
    6.     public IntVariant startingHP;
    7.  
    8.     [NonSerialized]
    9.     public int currentHP;
    10.  
    11.     void Start()
    12.     {
    13.         currentHP = startingHP;
    14.  
    15.         Debug.Log(name + "'s starting hp is " + currentHP);
    16.     }
    17. }

    Code (csharp):
    1. using UnityEditor;
    2. using UnityEngine;
    3.  
    4. [CustomPropertyDrawer(typeof(IntVariant))]
    5. public class IntVariantDrawer : PropertyDrawer
    6. {
    7.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    8.     {
    9.         var isVariant = PrefabUtility.GetPrefabAssetType(property.serializedObject.targetObject) == PrefabAssetType.Variant;
    10.  
    11.         if (isVariant)
    12.             OnGUIVariant(position, property, label);
    13.         else
    14.             OnGUIDefault(position, property, label);
    15.     }
    16.  
    17.     private void OnGUIVariant(Rect position, SerializedProperty property, GUIContent label)
    18.     {
    19.         var value = property.FindPropertyRelative("value");
    20.         var multiplier = property.FindPropertyRelative("multiplier");
    21.  
    22.         var rect = EditorGUI.PrefixLabel(position, label);
    23.         var valueRect = rect;
    24.         var multiplierRect = rect;
    25.         var productRect = rect;
    26.  
    27.         valueRect.xMax = multiplierRect.xMin = rect.xMin + (rect.width /  3);
    28.         multiplierRect.xMax = productRect.xMin = rect.xMin + (rect.width * 2 / 3);
    29.  
    30.         valueRect.xMax -= 5;
    31.         multiplierRect.xMin += 5;
    32.         multiplierRect.xMax -= 5;
    33.         productRect.xMin += 5;
    34.  
    35.         var multLabel = rect;
    36.         multLabel.xMin = valueRect.xMax;
    37.         multLabel.xMax = multiplierRect.xMin;
    38.  
    39.         var equalsLabel = rect;
    40.         equalsLabel.xMin = multiplierRect.xMax;
    41.         equalsLabel.xMax = productRect.xMin;
    42.  
    43.         EditorGUI.BeginDisabledGroup(true);
    44.         EditorGUI.IntField(valueRect, GUIContent.none, value.intValue);
    45.         EditorGUI.EndDisabledGroup();
    46.  
    47.         EditorGUI.LabelField(multLabel, "*");
    48.  
    49.         EditorGUI.PropertyField(multiplierRect, multiplier, GUIContent.none);
    50.  
    51.         EditorGUI.LabelField(equalsLabel, "=");
    52.  
    53.         var product = value.intValue * multiplier.intValue;
    54.  
    55.         EditorGUI.BeginDisabledGroup(true);
    56.         EditorGUI.IntField(productRect, GUIContent.none, product);
    57.         EditorGUI.EndDisabledGroup();
    58.     }
    59.  
    60.     private void OnGUIDefault(Rect position, SerializedProperty property, GUIContent label)
    61.     {
    62.         //ensure multiplier is 1 for non-override variants
    63.         property.FindPropertyRelative("multiplier").intValue = 1;
    64.  
    65.         var value = property.FindPropertyRelative("value");
    66.         EditorGUI.PropertyField(position, value, label);
    67.     }
    68. }


    This exercise revealed two issues:

    1: when making custom property drawers, the right-click-apply menu isn't there. Here I've got a different value for comparison:

    upload_2018-10-11_14-27-50.png

    What I need here is some way to indicate in code that right-clicking the label I create in line 22 of my drawer script should show the right-click menu for the "multiplier" property. It'd also be nice if I could tie the bolding of the PrefixLabel to the same property.

    2: The information that an object was a prefab goes away in play mode. This makes the special UI go away in play mode. This could be confusing. I'm not sure if there's any way to fix this - Unity's editor player could be made to hang on to the information about if the object is a prefab instance or not, which would probably be a massive undertaking. It might also not be worth it even if it was free - people are already confused enough about the fact that prefabs are kinda an editor-only feature.
     
    LurkingNinjaDev likes this.
  15. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    240
  16. hunterofakara

    hunterofakara

    Joined:
    Oct 4, 2018
    Posts:
    16
    This doesn't seem to work when inspecting from inside the prefab edit mode - the root gets unpacked or such, so the original shows as not a prefab and the variant shows as a regular prefab.
     
  17. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    3,346
    Forgot all about that one! Thanks.

    For @hunterofakara, yeah, I was too fast.

    I've dug around the API, and it seems like there's no way to tell, when a PrefabStage is open, if this is a prefab variant or not. Unity knows it internally, otherwise the "apply to prefab" button couldn't show up, but I don't know if it's available to us.

    The only way I found to make the isVariant check work in the prefab stage is...

    Code (csharp):
    1. var isVariant = PrefabUtility.GetPrefabAssetType(targetObject) == PrefabAssetType.Variant;
    2.  
    3. if (!isVariant) {
    4.     if (targetObject is Component component)
    5.     {
    6.         var gameObject = component.gameObject;
    7.  
    8.         var prefabStage = PrefabStageUtility.GetPrefabStage(gameObject);
    9.         if (prefabStage != null)
    10.         {
    11.             var assetPath = prefabStage.prefabAssetPath;
    12.             var assetObj = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
    13.  
    14.             isVariant = PrefabUtility.GetPrefabAssetType(assetObj) == PrefabAssetType.Variant;
    15.         }
    16.     }
    17. }
    And that's horrible. Does anyone have a better idea?

    @SteenLund, @runevision, the issue pointed out in this thread seems to be a real killer. It's in no way intuitive that PrefabUtility.GetPrefabAssetType just doesn't work if we're in a prefab stage. It sounds like it's way less work to create a special-path for that method when the prefab stage is open rather than trying to document the current, very surprising behaviour.
     
  18. hunterofakara

    hunterofakara

    Joined:
    Oct 4, 2018
    Posts:
    16
    Just had a look at the source for unity showing the variant icon at the top of the prefab mode hierarchy.

    Seems it uses PrefabUtility.IsPartOfNonAssetPrefabInstance and PrefabUtility.IsDisconnectedFromPrefabAsset on the root gameobject to check, so at least loading the prefab again isn't needed:

    Code (CSharp):
    1. var isVariant = PrefabUtility.GetPrefabAssetType(targetObject) == PrefabAssetType.Variant;
    2.    
    3.     if (!isVariant) {
    4.         if (targetObject is Component component)
    5.         {
    6.             var gameObject = component.gameObject;
    7.    
    8.             var prefabStage = PrefabStageUtility.GetPrefabStage(gameObject);
    9.             if (prefabStage != null)
    10.             {
    11.                 GameObject root = prefabStage.prefabContentsRoot;
    12.                 bool partOfInstance = PrefabUtility.IsPartOfNonAssetPrefabInstance(root);
    13.                 bool disconnected = PrefabUtility.IsDisconnectedFromPrefabAsset(root);
    14.                 isVariant = partOfInstance && !disconnected;
    15.             }
    16.         }
    17.     }
    I don't know how nested varients and such would factor in, but all that probably depends on how/when someone would want to use the multiplier type as an override.
     
  19. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    3,346
    Thanks, that led me to a much cleaner implementation:

    Code (csharp):
    1.  
    2. var isVariant = prefabAssetType == PrefabAssetType.Variant ||
    3.                 (PrefabStageUtility.GetCurrentPrefabStage() != null && prefabAssetType == PrefabAssetType.Regular);
    Because a prefab variant in a prefab stage is not a variant anymore, but it's not "not a prefab" either - it's a non-variant prefab.

    This API is BONKERS.
     
  20. runevision

    runevision

    Unity Technologies

    Joined:
    Nov 28, 2007
    Posts:
    1,319
    What you're looking at in a Prefab Stage is not a Prefab instance, nor a Prefab Asset, but the contents that is inside a Prefab Asset (technically we load the contents and display that). This is why a regular Prefab Asset has a regular GameObject at its root in Prefab Mode, and a Prefab Variant Asset has a regular Prefab instance at its root in Prefab Mode.

    This is not only a technicality, but is also conceptually needed to be able to "dig inside" Prefabs and see the overrides to nested Prefabs and the base of Prefab Variants while in Prefab Mode. We cover this in more detail in our talk here:


    There is API to check if you're in Prefab mode (see replies in the thread you linked to) and deduce that way if an object is part of the contents of a Prefab opened in Prefab Mode. And if the root of the contents is a Prefab instance, then you're inside a Prefab Variant.
     
  21. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    3,346
    Thanks for the talk link!

    Is there anything you can do now, that you couldn't do if PrefabUtility didn't consider prefabs open in the prefab scene as a prefab?
    I promise you, nobody is going to expect GetPrefabAssetType to return not a prefab when you open a prefab. That's guaranteed going to lead to bugs and frustration. An API that is consistent between the scene view and the prefab view will be a lot more useful to us, and we'll be able to write much simpler code to interact with the prefab system.

    It seems very strange that looking at the same object (ie. the prefab asset, not the instance) gives different results depending on if I'm looking at it "normally" and in the prefab scene. Note line 14 on the code in this post.

    Consider if your choices here are helping us achieve anything, or if it's simply the implementation details of how the prefab view works that is leaking out.
     
  22. LurkingNinjaDev

    LurkingNinjaDev

    Joined:
    Jan 20, 2015
    Posts:
    834
    Actually, what they chose is completely logical. I think. I'm not sure if I agree with it, but it has logic to it.
    Prefab vs. content.

    Analogy: You order a vacuum cleaner from the Amazon or something. The box has arrived.

    When you look at the box, you say: it's a package. (prefab)
    When you open the box, it becomes: a vacuum cleaner (content)
    If you close the box again, it becomes a package again (prefab)

    No matter where you're, if the box is closed (prefab), it's a package, if you can see the content, it becomes the content. It seems logical to me and not too hard to comprehend.
     
  23. hunterofakara

    hunterofakara

    Joined:
    Oct 4, 2018
    Posts:
    16
    The disparity for most people is an instance in the scene view is boxed but editable (albeit an instance specific override).
    The instance in the prefab mode is just as editable, but is the unboxed version.

    The scene view instance can save to the prefab by applying overidden changes, whereas the prefab mode instance is saved more directly.

    Conceptually there's these differences, but visually both are near identical (You see what appears to be content inside the box).
    This might be different if nested prefabs had different icons than root prefabs (Would help to tell where to apply overrides in a cluttered scene view hierarchy too). Ideally also showing tye type of the root parent:
    Unless you're looking up the hierarchy tree to manually find the root parent or the first parent with a +, then you currently have no idea if the child object being selected is going to say 'Prefab' or 'Varient' by the Open button in the inspector.

    The use case in this thread (Custom inspector that treats the prefab differently depending on it's asset type) highlights how ackward covering both cases equally in the same code can be.

    If enough people have these issues, then it might be handy to have something like PrefabStage.GetCurrentPrefabAssetType to directly check the outer asset type (currently unboxed but will be the type re-boxed to when saved - shown by the icon in the header of the prefab mode hierarchy) without needing to work backwards from what it might become when unpacked.

    Outside of prefab mode, the entire instance is counted as a single prefab of the root type, so a nested variant being selected shows as not a variant if the root of the instance isn't.

    To get this to work the same inside prefab mode means testing the root object there:

    Code (CSharp):
    1. bool variant = false;
    2.         Component c = property.serializedObject.targetObject as Component;
    3.         if( c != null )
    4.         {
    5.             PrefabStage stage = PrefabStageUtility.GetPrefabStage(c.gameObject);
    6.             if( stage == null )
    7.             {
    8.                 variant = PrefabUtility.IsPartOfVariantPrefab(c);
    9.             }
    10.             /*else
    11.             {//test root type for variant (unpacked to Regular) or variant of variant (unpacked to Variant)
    12.                 PrefabAssetType prefabAssetType = PrefabUtility.GetPrefabAssetType(stage.prefabContentsRoot);
    13.                 variant = (prefabAssetType == PrefabAssetType.Regular) || (prefabAssetType == PrefabAssetType.Variant);
    14.             }*/
    15.             else
    16.             {//test root for variant (If connected instance, it's a varient)
    17.                 GameObject stageRoot = stage.prefabContentsRoot;
    18.                 variant = PrefabUtility.IsPartOfNonAssetPrefabInstance(stageRoot) && !PrefabUtility.IsDisconnectedFromPrefabAsset(stageRoot);
    19.             }
    20.         }
    If you want to base it on the selected object being a varient regardless of nesting, then I can't find a way to tell without either asset loading/unpacking in scene view or relying on the cached asset icon being unchanged:
    Something like comparing the assetdatabase icon of the nearest instance root since the inspector/hierarchy shows the variant icon. You'd also have to compare to the added varient icon. And as usual, some exception for the root instance of prefab edit mode.


    PrefabUtility.GetNearestPrefabInstanceRoot will get the root of the nested varient, but PrefabUtility.GetPrefabAssetType on it will show the type of the outermost root instead in scene view.
    Would be nice to have something like 'PrefabUtility.GetPrefabAssetTypeOfNearestInstanceRoot' that includes nested too, but I couldn't find an equivilant.



    Edit: Just realized what my main problem conceptually is. Prefab edit mode is a mode for editing existing prefabs, but is showing the equivilant hierarchy for making a new prefab. It shows the same set of objects that you'd see in the scene view before dragging to make a new prefab.
    Unless using a 'prefab originals' scene to keep the pre-prefab gameobjects, the previous way to edit prefabs was to edit an instance/copy and apply changes back to the asset/first prefab.

    Similarly, if you had a prefab edit mode that showed an instance of a prefab, and saving applied overrides to the original.
    Currently, you can add children this way in the scene view, but you cannot remove them. It's not apparantly obvious why the 'Cannot restructure Prefab instance' box doesn't just have a 'Do this and apply to prefab asset' button, but if this and rearranging/reparenting children could be solved then would there be any reason you couldn't have a choice between a packed and an unpacked view in prefab edit mode?
     
    Last edited: Oct 13, 2018
  24. runevision

    runevision

    Unity Technologies

    Joined:
    Nov 28, 2007
    Posts:
    1,319
    You can use 'PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot' and then check the type of that Asset. That's what we do internally to determine icons, which Prefab to open etc.

    Nested Prefabs and Prefab Variants introduce some complexity due to the fact that the same objects can be part of many Prefabs at the same time. Our APIs operate on the outermost Prefab unless otherwise noted (since the alternative is in many cases not possible or much more complex). You have to "dig in" (with 'PrefabUtility.GetCorrespondingObjectFromSource" and similar APIs) to reach inner Prefabs and be able to query their state. Nested Variants are tricky though. You don't want to query the outermost Prefab, but you don't want to query the innermost either (that would be the original base of the Variant). The 'GetPrefabAssetPathOfNearestInstanceRoot' method is good for those use cases.