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

Bug Prefab reseting their value when I enter in play mode

Discussion in 'Scripting' started by Nefisto, Nov 15, 2022.

  1. Nefisto

    Nefisto

    Joined:
    Sep 17, 2014
    Posts:
    324
    First, let me apologize for the big post, but it was hard to explain the problem in words so I've made some videos to explain it, I think that it would not be necessary to show much code to express the error, but if things are not clear enough let me know and then I add more code to it.

    The problem: It seems that unity is resetting field values to prefab values when entering play mode.

    Just to exemplify the scenario, I have a class called
    SlotAcessor
    that has a
    Slot
    reference inside it and it is being serialized in the inspector, I also have created some buttons to help to debug it, one to Add an item and other to Clear the Slot
    upload_2022-11-15_11-28-56.png

    So, the slot accessor should just show this item in HUD and things is working pretty okay, but suddenly this glitch starts to happen (30-sec video)

    https://imgur.com/a/KZGZnKP

    Then you can say, nothing wrong until now, you probably are updating or clearing HUD at some point, but now take a second look at this one (15-sec video)

    https://imgur.com/a/98MQCJ7

    o_O if I unpack the prefab the problem stop, and to make things weird if I make a CTRL+Z with some item set in the prefab in the scene this turns the default of this item (40-sec video)

    https://imgur.com/a/ddqnbxO

    If someone has already passed through something similar and has solved by anyway, let me know, I already tried to recreate the prefab but this hasn't solved
     
  2. GroZZleR

    GroZZleR

    Joined:
    Feb 1, 2015
    Posts:
    3,201
    Looks like you have a custom inspector, which is the likely culprit, care to share the code for that?
     
  3. Munchy2007

    Munchy2007

    Joined:
    Jun 16, 2013
    Posts:
    1,731
    Agreed, it sounds very much like you aren't 'dirtying' the target object, so the changes aren't being serialized.

    Probably, the best approach is to use SerializedObject methods and make sure you call serializedObject.UpdateIfRequiredOrScript at the start of Your InspectorGUI method and serializedObject.ApplyModifiedProperties at the end of your InspectorGUI method.
     
  4. Nefisto

    Nefisto

    Joined:
    Sep 17, 2014
    Posts:
    324
    I'm not, I`m just using Odin inspector for the Title and the Buttons but I'm serializing things on the unity side

    But anyway, here is the code
    Code (CSharp):
    1. public class SlotAccessor : MonoBehaviour
    2. {
    3.     [Title("Set by inventory")]
    4.     [SerializeField]
    5.     private Slot slot;
    6.  
    7.     [Title("References")]
    8.     [SerializeField]
    9.     private Image backgroundImage;
    10.  
    11.     [SerializeField]
    12.     private TMP_Text amountLabel;
    13.  
    14.     [SerializeField]
    15.     private Image amountLabelBackground;
    16.  
    17.     public bool IsEmpty => slot.item == null;
    18.  
    19.     public void Setup (Inventory owner, int index, Item initialItem = null, int itemAmount = 0)
    20.     {
    21.         slot.owner = owner;
    22.         slot.index = index;
    23.         if (initialItem != null)
    24.         {
    25.             slot.item = initialItem;
    26.             slot.amount = itemAmount;
    27.         }
    28.  
    29.         UpdateHUD();
    30.     }
    31.  
    32.     [Button]
    33.     public void Clear()
    34.     {
    35.         slot.item = null;
    36.         slot.amount = 0;
    37.         slot.index = 0;
    38.      
    39.         UpdateHUD();
    40.     }
    41.  
    42.     [Button]
    43.     public void AddItem (IngredientAsset item, int amount = 1)
    44.         => AddItem((Item)item, amount);
    45.  
    46.     public void AddItem (Item item, int amount = 1)
    47.     {
    48.         slot.AddItem(item, amount);
    49.  
    50.         UpdateHUD();
    51.     }
    52.  
    53.     public void ChangeSlots (SlotAccessor otherSlot)
    54.     {
    55.         if (this == otherSlot)
    56.             return;
    57.  
    58.         var resultSlot = otherSlot.slot.ChangeSlots(slot);
    59.         slot.ChangeSlots(resultSlot);
    60.  
    61.         UpdateHUD();
    62.         otherSlot.UpdateHUD();
    63.     }
    64.  
    65.     [Button]
    66.     private void UpdateHUD()
    67.     {
    68.         backgroundImage.sprite = slot?.item?.Icon;
    69.         amountLabel.text = slot?.item
    70.             ? slot.amount.ToString() : "";
    71.         amountLabelBackground.enabled = slot?.item;
    72.     }
    73.  
    74.     public void UpdateSlotAlpha (float newAlpha)
    75.     {
    76.         backgroundImage.SetAlpha(newAlpha);
    77.         amountLabelBackground.SetAlpha(newAlpha);
    78.         amountLabel.SetAlpha(newAlpha);
    79.     }
    80. }
    Code (CSharp):
    1. [Serializable]
    2. public class Slot
    3. {
    4.     [ReadOnly]
    5.     public Item item;
    6.  
    7.     [ReadOnly]
    8.     public int amount;
    9.  
    10.     [ReadOnly]
    11.     public int index;
    12.  
    13.     [HideInInspector]
    14.     public Inventory owner;
    15.  
    16.     public void AddItem (Item item, int amount = 1)
    17.     {
    18.         if (this.item != null && this.item.IsEqualTo(item))
    19.         {
    20.             this.amount += amount;
    21.         }
    22.         else
    23.         {
    24.             this.item = item;
    25.             this.amount = amount;
    26.         }
    27.     }
    28.  
    29.     public Slot ChangeSlots (Slot otherSlot)
    30.     {
    31.         if (HasSameItemThat(otherSlot))
    32.         {
    33.             IncrementAmountIn(otherSlot.amount);
    34.  
    35.             return new Slot();
    36.         }
    37.  
    38.         var tempSlot = (Slot)MemberwiseClone();
    39.         ShallowCopy(otherSlot);
    40.  
    41.         return tempSlot;
    42.     }
    43.  
    44.  
    45.     private void ShallowCopy (Slot other)
    46.     {
    47.         index = other.index;
    48.         item = other.item;
    49.         amount = other.amount;
    50.     }
    51.  
    52.     private void IncrementAmountIn (int many)
    53.         => amount += many;
    54.  
    55.     public bool HasSameItemThat (Slot otherSlot)
    56.         => item.IsEqualTo(otherSlot.item);
    57.  
    58.     public bool HasSameItemThat (Item otherItem)
    59.         => item.IsEqualTo(otherItem);
    60. }
    Code (CSharp):
    1. public abstract class Item : SerializedScriptableObject
    2. {
    3.     [OdinSerialize]
    4.     public Sprite Icon { get; set; }
    5.  
    6.     public bool IsEqualTo (Item other)
    7.     {
    8.         if (ReferenceEquals(other, null))
    9.             return false;
    10.        
    11.         return name == other.name;
    12.     }
    13. }
     
  5. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,883
    It's still a custom inspector and can have ramifications as to how things are/aren't saved properly.

    Careful about using Odin's ReadOnly attribute. It enforces that when the values are viewed in the inspector, so these values often don't persist. The work around is to use
    [DisableIf("@ true")]
    . (Had to put a space because it was tagging someone)

    Make sure you're dirtying the object, even though Odin's buttons try to dirty the root object, I like to do it explicitly just in case. Odin has a utility method called
    Sirenix.Utilities.Editor.InspectorUtilities.RegisterUnityObjectDirty(Object)
    .

    I watched your video multiple times and couldn't really follow what was happening, to be honest.

    Also there's no need to use Odin serialisation to serialise just a property that Unity can serialise anyway. That's overkill.
     
  6. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,883
    Okay I'm daft, I see it: the icon is reverting.

    Then yes, you need to make sure you manually dirty the entire game object in this case with the aforementioned utility method.
     
  7. Nefisto

    Nefisto

    Joined:
    Sep 17, 2014
    Posts:
    324
    But it's a custom inspector for a title and to access a method, how would this has something to do with the current problem?

    Don't get this part, I thought that it just disable the GUI for the specific field

    I'll try this one

    The slot that you saw in video in made this way
    upload_2022-11-17_8-30-30.png

    When I add items throught add method (that is being called by odin's button) this change the values in slot reference (as you can see in video 1) and update the HUD using the Ccon.sprite (from item) to the Background image in slot HUD, but when I press play, this is what you see
    upload_2022-11-17_8-33-27.png

    So it has set the background image in HUD to NULL and set the text in amount to empty, but if I look to Slot reference in slot accessor the values are still there

    Then in the second video, I show that if I just unpack the prefab (without touch in the code) the problem disappears, this is the reason for the title

    Then in the last video, I show that If I press CTRL+Z to "re-pack" the prefab but having a current item inside the slot reference (in this case was the Mario's fireflower), then clear (or add another item in this slot) and then press play, instead of changing the background image to null it start to set background image to the Mario's flower (the image that is set when I "re-packed" the prefab"

    Sure, thanks
     
    Last edited: Nov 17, 2022
  8. Nefisto

    Nefisto

    Joined:
    Sep 17, 2014
    Posts:
    324
    I've added it on the UpdateHUD method (that is being called in AddItem and in Clear) and didn't work.

    Code (CSharp):
    1. [Button]
    2.     private void UpdateHUD()
    3.     {  
    4.         backgroundImage.sprite = slot?.item?.Icon;
    5.         amountLabel.text = slot?.item
    6.             ? slot.amount.ToString() : "--";
    7.         amountLabelBackground.enabled = slot?.item;
    8.         InspectorUtilities.RegisterUnityObjectDirty(gameObject);
    9.     }
     
  9. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,883
    Well dirtying the object just flags it for saving. You'll need to actually save the changes too. Ergo,
    AssetDatabase.SaveAssets()
    .

    Trust me, 95% of the time when something isn't saving it's due to a custom inspector. The other 5% is incorrect use of the Odin serialiser.
     
  10. Nefisto

    Nefisto

    Joined:
    Sep 17, 2014
    Posts:
    324
    Not yet :(, do I need to do something else?
    Code (CSharp):
    1. private void UpdateHUD()
    2.     {  
    3.         backgroundImage.sprite = slot?.item?.Icon;
    4.         amountLabel.text = slot?.item
    5.             ? slot.amount.ToString() : "--";
    6.         amountLabelBackground.enabled = slot?.item;
    7.        
    8.         InspectorUtilities.RegisterUnityObjectDirty(gameObject);
    9.         AssetDatabase.SaveAssets();
    10.     }
    11.  
     
  11. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,883
    Having a think about this, seeing as we know this is due to it being a prefab, it's probably due to prefab overrides/modifications. I suppose prefabs don't know a value has been modified if set value code and dirtying the object?

    When you run the button, do the appropriate fields on the altered components show up as modified (bolded name)? If not, I think we may have our culprit.

    I imagine there's something in the prefab utility API that handles this, but I've never used it so I wouldn't know exactly.
     
  12. Nefisto

    Nefisto

    Joined:
    Sep 17, 2014
    Posts:
    324
    Yap, it is shown as modified
    capture.gif

    What makes me crazy is that if I just unpack it, the error is gone, I tried to unpack and recreate the prefab but the error has persisted...
     
  13. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,883
    I mean the properties on the other components. The TMP image components, etc, that you're altering.
     
  14. Nefisto

    Nefisto

    Joined:
    Sep 17, 2014
    Posts:
    324
    You found the culprit, the components from the child isn't been marked as changed \o/ :D
    capture.gif

    Just flagging child would solve it? Because I tried it and the problem persist
    Code (CSharp):
    1. private void UpdateHUD()
    2.     {  
    3.         backgroundImage.sprite = slot?.item?.Icon;
    4.         amountLabel.text = slot?.item
    5.             ? slot.amount.ToString() : "--";
    6.         amountLabelBackground.enabled = slot?.item;
    7.  
    8.         InspectorUtilities.RegisterUnityObjectDirty(gameObject);
    9.         foreach (Transform child in transform)
    10.             InspectorUtilities.RegisterUnityObjectDirty(child.gameObject);
    11.         AssetDatabase.SaveAssets();
    12.     }
     
  15. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,883
    Dirtying the child doesn't really do anything more than dirtying the parent game object itself. When you dirty a game object it just dirties the scene asset it's in, marking the scene for saving.

    In this case, as mentioned, you will need to look at the prefab API to see if you can make it recognise that the properties have been modified.
     
  16. Munchy2007

    Munchy2007

    Joined:
    Jun 16, 2013
    Posts:
    1,731
  17. Nefisto

    Nefisto

    Joined:
    Sep 17, 2014
    Posts:
    324
  18. Nefisto

    Nefisto

    Joined:
    Sep 17, 2014
    Posts:
    324
    Do you have any idea why this is happening? I mean, I've already made changes in the children of prefab instances before and this never happened
     
  19. Munchy2007

    Munchy2007

    Joined:
    Jun 16, 2013
    Posts:
    1,731
    JevinLevin_, spiney199 and Nefisto like this.
  20. Nefisto

    Nefisto

    Joined:
    Sep 17, 2014
    Posts:
    324
    This worked \o/, as a note I needed to pass the specific components to this API
    Code (CSharp):
    1.     private void UpdateHUD()
    2.     {  
    3.         backgroundImage.sprite = slot?.item?.Icon;
    4.         amountLabel.text = slot?.item
    5.             ? slot.amount.ToString() : "--";
    6.         amountLabelBackground.enabled = slot?.item;
    7.  
    8. #if UNITY_EDITOR
    9.         if (!Application.isPlaying)
    10.         {
    11.             PrefabUtility.RecordPrefabInstancePropertyModifications(backgroundImage);
    12.             PrefabUtility.RecordPrefabInstancePropertyModifications(amountLabel);
    13.             PrefabUtility.RecordPrefabInstancePropertyModifications(amountLabelBackground);
    14.         }
    15. #endif
    16.     }
     
    Munchy2007 and spiney199 like this.