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

Question How to pint-point what makes my inventory system so resource hungry at initialization?

Discussion in '2D' started by iluvpancakes, Sep 6, 2020.

  1. iluvpancakes

    iluvpancakes

    Joined:
    Jul 19, 2020
    Posts:
    18
    I have a simple Inventory Manager system based on Scriptable objects and it works fine so far if it was not for the fact that every time I populate my inventory (for the first time, either by having an empty inventory and picking up an item in the world, or by loading a saved inventory) I get a 45ms spike in profiler and it is a very noticeable fps drop when you play as well. Once you have picked up your first item, or loaded a saved inventory you can pick up additional items without experiencing the same stutter again.

    This is how my profiler looks at the point my Inventory gets loaded, alternatively, pick up an item into a previously empty (non initialized) inventory:
    Capture2.PNG The huge spike is covered by the frame selector but you get the idea, I'm sure.

    If I check the hierarchy in profiler for the same frame:
    profilerHierarchy1.PNG
    Most of whatever is going on is going on in Update.ScriptRunBehaviourUpdate and PostLateUpdate.PlayerUpdateCanvases. So I drilled down these two and it looks like this:
    drilldown1.PNG
    And:
    drilldown2.PNG

    The logic of when it happens tells me that it should have something to do with initializing the InventoryDisplay for the first time, but I don't get what is so expensive about it. All I am doing is to instantiate a prefab which has a 12x14 pixel image that has a child with a textmeshpro UI Text component on to a panel on my Canvas:
    inventorybar1.PNG

    Here is my DisplayInventory script:

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using TMPro;
    3. using Unity.Mathematics;
    4. using UnityEngine;
    5.  
    6. public class DisplayInventory : MonoBehaviour
    7. {
    8.     [SerializeField] InventoryObject inventory;
    9.     [SerializeField] float xStart;
    10.     [SerializeField] float yStart;
    11.     [SerializeField] float horizontalSpacing;
    12.     [SerializeField] int numberOfColumns;
    13.     [SerializeField] float verticalSpacing;
    14.    
    15.     Dictionary<InventorySlot, GameObject> _itemsDisplayed = new Dictionary<InventorySlot, GameObject>();
    16.  
    17.     void Start()
    18.     {
    19.         InitializeDisplay();
    20.     }
    21.  
    22.     void Update()
    23.     {
    24.     }
    25.  
    26.     public void UpdateDisplay()
    27.     {
    28.         for (int i = 0; i < inventory.container.Count; i++)
    29.         {
    30.             if (_itemsDisplayed.ContainsKey(inventory.container[i]))
    31.             {
    32.                 _itemsDisplayed[inventory.container[i]].GetComponentInChildren<TextMeshProUGUI>().text =
    33.                     "x" + inventory.container[i].amount.ToString("N0");
    34.             }
    35.             else
    36.             {
    37.                 var obj = Instantiate(inventory.container[i].item.prefab, Vector2.zero, quaternion.identity, transform);
    38.                 obj.GetComponent<RectTransform>().localPosition = GetPosition(i);
    39.                 obj.GetComponentInChildren<TextMeshProUGUI>().text = "x" + inventory.container[i].amount.ToString("N0");
    40.                 _itemsDisplayed.Add(inventory.container[i], obj);
    41.             }
    42.         }
    43.     }
    44.  
    45.     public void InitializeDisplay()
    46.     {
    47.         for (int i = 0; i < inventory.container.Count; i++)
    48.         {
    49.             var obj = Instantiate(inventory.container[i].item.prefab, Vector2.zero, quaternion.identity, transform);
    50.             obj.GetComponent<RectTransform>().localPosition = GetPosition(i);
    51.             obj.GetComponentInChildren<TextMeshProUGUI>().text = "x" + inventory.container[i].amount.ToString("N0");
    52.         }
    53.     }
    54.  
    55.     public Vector2 GetPosition(int i)
    56.     {
    57.         return new Vector2(xStart + (horizontalSpacing * (i % numberOfColumns)), yStart + (-verticalSpacing * (i/numberOfColumns)));
    58.     }
    59. }
    My InventoryObject script:

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using System.IO;
    3. using System.Runtime.Serialization.Formatters.Binary;
    4. using UnityEngine;
    5.  
    6. [CreateAssetMenu(fileName = "New Inventory", menuName = "Inventory System/Inventory")]
    7.  
    8. public class InventoryObject : ScriptableObject, ISerializationCallbackReceiver
    9. {
    10.     public string InventoryFileName;
    11.     public ItemDatabaseObject database;
    12.     public List<InventorySlot> container = new List<InventorySlot>();
    13.  
    14.     public void AddItem(ItemObject addItem, int addAmount)
    15.     {
    16.         for (int i = 0; i < container.Count; i++)
    17.         {
    18.             if (container[i].item == addItem)
    19.             {
    20.                 container[i].AddAmount(addAmount);
    21.                 return;
    22.             }  
    23.         }
    24.         container.Add(new InventorySlot(database.GetId[addItem], addItem, addAmount));
    25.     }
    26.  
    27.     public void OnBeforeSerialize()
    28.     {
    29.     }
    30.  
    31.     public void SaveInventory()
    32.     {
    33.         string saveData = JsonUtility.ToJson(this, true);
    34.         var binForm = new BinaryFormatter();
    35.        
    36.         using(FileStream file = File.Create(Path.Combine(Application.persistentDataPath, InventoryFileName)))
    37.         {
    38.             binForm.Serialize(file, saveData);
    39.             file.Close();    
    40.         }
    41.     }
    42.    
    43.     public void LoadInventory()
    44.     {
    45.         if (File.Exists(Path.Combine(Application.persistentDataPath, InventoryFileName)))
    46.         {
    47.             var binForm = new BinaryFormatter();
    48.             using (FileStream file = File.Open(Path.Combine(Application.persistentDataPath, InventoryFileName), FileMode.Open))
    49.             {
    50.                 JsonUtility.FromJsonOverwrite(binForm.Deserialize(file).ToString(), this);
    51.                 file.Close();
    52.             }  
    53.         }  
    54.     }
    55.    
    56.     public void OnAfterDeserialize()
    57.     {
    58.         for (int i = 0; i < container.Count; i++)
    59.             container[i].item = database.GetItemById(container[i].Id);
    60.     }
    61. }
    62.  
    63. [System.Serializable]
    64. public class InventorySlot
    65. {
    66.     public int Id;
    67.     public ItemObject item;
    68.     public int amount;
    69.  
    70.     public InventorySlot(int id, ItemObject addItem, int addAmount)
    71.     {
    72.         Id = id;
    73.         item = addItem;
    74.         amount = addAmount;
    75.     }
    76.  
    77.     public void AddAmount(int value)
    78.     {
    79.         amount += value;
    80.     }
    81. }
    I think those two scripts are the only ones pertaining to the issue at hand. I am more than happy to send my whole project (it's basically just an inventory system atm).

    Any ideas would be greatly appreciated because I have spent three days trying to find the cause of this and I just can't progress.

    Thank you.
     
  2. Lo-renzo

    Lo-renzo

    Joined:
    Apr 8, 2018
    Posts:
    1,319
    Preinstantiate at a time when it won't be noticeable, for example when the scene loads. You don't have any control over the major costs here, so you can only really disguise it by instantiating when people have the expectation of waiting a bit. You could alternatively spread them out so it's not all on the same frame. A lot of that is just Mono.JIT (the just-in-time compiler) which will only be a cost the first time it happens.
     
  3. iluvpancakes

    iluvpancakes

    Joined:
    Jul 19, 2020
    Posts:
    18
    My good old saviour @Lo-renzo to the rescue haha. Thank you yet again for showing up. So from what you can tell there is nothing I can do to reduce the cost of the instantiation other than spreading it out or pre-instantiate it? Just seems so weird that it is noticeable, I just assumed I must have messed up somehow. There are more expensive operations happening every other frame in a modern 3d game, no? Could it be that i instantiate the TMP components as well? Should I maybe have those on the panel itself and load it with the panel object?

    How do I go about pre-instantiating an empty inventory though by the way?

    Thank you.
     
  4. Lo-renzo

    Lo-renzo

    Joined:
    Apr 8, 2018
    Posts:
    1,319
    It looks like your item is just two G.O.s, one for text the other for the image. Create a prefab out of that, instantiate several in Awake() or Start() from some initializing script. Store them in a list/array. Iterate that and disable them all. They're now hidden until you activate them, which - if the profiler is right - covers most of the cost you face. This is all before the first Update so player won't notice. All that's left is setting the text and the image components later, then re-activate them. Now you pay activation cost instead of instantiation/JIT costs, which should improve things.
     
    iluvpancakes likes this.