Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

Question Prefab variants and lists/arrays

Discussion in 'Prefabs' started by CRG-Hans, Apr 29, 2024.

  1. CRG-Hans

    CRG-Hans

    Joined:
    May 9, 2022
    Posts:
    12
    Whats the recommended workflow when working with prefabs and variants where you have components that expose arrays/lists?

    Example; I have a prefab that has a component that exposes a array or list. Initially the prefab has a couple of elements defined in the mentioned list. At some point I create a variant of this prefab, in the variant I add new element to the list. So far everything is fine and dandy.
    But if I want to add a element in the original prefab, things get messy quickly. The variant registers its extra element as a override. So adding a element in the base doesnt add it in the variant, instead the existing element in the variant overrides it. And depending on default values on the object in the list, you get a mix of the base prefabs element and the variants element. By adding the element in the variant, the list also gets an override to the list size. So if you add more elements in the base, the variant wont get those either.

    Is there any way to hook up callback etc, to handle array/list merging for variants?
     
  2. MirceaI

    MirceaI

    Unity Technologies

    Joined:
    Nov 24, 2020
    Posts:
    40
    Hello! I'll try to give you a bit of context on how we do array overrides and why we do it like this.
    In general there are 2 ways of doing array overrides - at element level and at array level.
    In Unity overrides are stored at element level - you can actually see the data in the PrefabInstance object in your Scene or Variant/Nested Prefab files.

    This gives us a the following benefits:
    - changes in the asset (Prefab) are propagated to the instance for all elements without overrides. This is useful in workflows where the number and the order of the elements doesn't change in the asset, like the bones in character animations
    - it minimizes the storage when only a small subset of elements have overrides which is the most common use case
    It also has surprising behaviour when someone changes the asset (like your are observing now):
    - inserting/deleting/reordering elements in the asset might cause the overrides to be applied on unwanted elements
    - simple UI actions like reordering elements can create a large number of overrides
    Unfortunately there is no silver bullet for this problem and any approach will fail in certain use cases. You encountered the limitations of the element override approach...

    One workaround might be to have 2 arrays, one with the Prefab values and one with the overrides and have a unique key in each element as the override target and do your own composition:

    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. [Serializable]
    6. public enum OverrideType
    7. {
    8.     Value,
    9.     Append,
    10.     Remove
    11. }
    12.  
    13. [Serializable]
    14. public struct Element
    15. {
    16.     public int elementID; // you can use Hash128 or something better here
    17.     public string value;
    18.  
    19.     public Element(int elementID, string value)
    20.     {
    21.         this.elementID = elementID;
    22.         this.value = value;
    23.     }
    24. }
    25.  
    26. [Serializable]
    27. public struct Override
    28. {
    29.     public int elementID;
    30.     public OverrideType overrideType;
    31.     public string value;
    32. }
    33.  
    34. public class SafeArrayComposer : MonoBehaviour
    35. {
    36.     public List<Element> prefabValues = new List<Element>();
    37.     public List<Override> overrides = new List<Override>();
    38.     private List<Element> composed = new List<Element>();
    39.  
    40.     private Override? GetOverrideType(int elementID)
    41.     {
    42.         foreach (var o in overrides)
    43.         {
    44.             if (o.elementID == elementID)
    45.             {
    46.                 return o;
    47.             }
    48.         }
    49.  
    50.         return null;
    51.     }
    52.  
    53.     public void Awake()
    54.     {
    55.         composed = new List<Element>();
    56.         foreach (var e in prefabValues)
    57.         {
    58.             var o = GetOverrideType(e.elementID);
    59.             if (o != null)
    60.             {
    61.                 switch (o?.overrideType)
    62.                 {
    63.                     case OverrideType.Value:
    64.                         composed.Add(new Element(e.elementID, o?.value));
    65.                         break;
    66.                     case OverrideType.Remove:
    67.                         continue;
    68.                     default:
    69.                         Debug.Log("Bad override");
    70.                         break;
    71.                 }
    72.             }
    73.             else
    74.             {
    75.                 composed.Add(e);
    76.             }
    77.         }
    78.  
    79.         foreach (var o in overrides)
    80.         {
    81.             if (o.overrideType == OverrideType.Append)
    82.             {
    83.                 composed.Add(new Element(o.elementID, o.value));
    84.             }
    85.         }
    86.     }
    87.  
    88.     public void Start()
    89.     {
    90.         foreach(var c in composed)
    91.         {
    92.             Debug.Log(c.value);
    93.         }
    94.     }
    95. }
    96.  
    The code is just an example and it is wasteful in terms of memory, storage and speed and it's not suitable for a general purpose array overrides system, but it can give you some insights on the complexity of this problem