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. Voting for the Unity Awards are OPEN! We’re looking to celebrate creators across games, industry, film, and many more categories. Cast your vote now for all categories
    Dismiss Notice
  3. Dismiss Notice

Creating prefabs dynamically? Alternative to Case-switch? (Inventory)

Discussion in 'Scripting' started by chmod774, Dec 3, 2018.

  1. chmod774

    chmod774

    Joined:
    Nov 7, 2018
    Posts:
    4
    Hi,

    I m working on a classic RPG inventory. The basic functionalities like display of items, using them etc. are working. I am using prefabs for my items, which use different scripts according to their type. Each item (e.g. "Leather armor" has a unique item ID and range (here: 1001 / ar_1001 - armors use 1000-2000).

    I'm now having a problem instantiating the prefabs "dynamically", e.g. using the item ID that is passed to my scripts (in the form of ar_xxxx). I need to instantiate the prefabs on two occasions: When items are added to the inventory and when a game is loaded.

    Currently I'm using a case switch for the selection of the proper prefab but it seems rather clumsy or not? I mean now I only have 3 test items for testing, but I'm looking at probably 300 items in total or so. So the case switch would get abnormally high.

    Is there a better to achieve a sort of dynamic instantiation ? Or should I even stick with the caseswitch? I am unsure on what is best here / how to proceed. I guess I can automate it's creation. It just seems so ineffficient to me. Atleast for saving, I could also save all values of the prefabs, but this also seems inefficient to me, as fixed values like damage etc. do not change. For items, I only need to save their durability.

    the case switch (69 ff. in full script down below)
                    //prefab is chosen according to armor / Item ID
    switch (ar_id)
    {
    case 1001:
    armor_item = Instantiate(ar_1001) as GameObject;
    break;
    case 1002:
    armor_item = Instantiate(ar_1002) as GameObject;
    break;
    case 1003:
    armor_item = Instantiate(ar_1003) as GameObject;
    break;
    }


    I've included the script for saving/loading down below (I don't know much about serialization, so I am using the Quick Save module from the unity shop,).

    Code (CSharp):
    1. using CI.QuickSave;
    2. using UnityEngine;
    3. using UnityEngine.UI;
    4.  
    5. public class TestManagerController : MonoBehaviour
    6. {
    7.     public Text Content;
    8.     public Canvas ArmorCanvas;
    9.     //Prefabs
    10.     public GameObject ar_1001;
    11.     public GameObject ar_1002;
    12.     public GameObject ar_1003;
    13.     //used for instantiating + setting values
    14.     private GameObject armor_item;
    15.  
    16.     public void Save()
    17.     {
    18.         //Counting how many armor items there are
    19.         int ArmorCount = ArmorCanvas.transform.childCount;
    20.         //Value is written to file
    21.         QuickSaveWriter writer = QuickSaveWriter.Create("Armor_Count");
    22.         writer.Write("Armor_Count", ArmorCount);
    23.         writer.Commit();
    24.  
    25.         //Loop that saves durability and item ID of each armor gameobject (other values are fixed)
    26.         for (int i = ArmorCount - 1; i >= 0; --i)
    27.         {
    28.             QuickSaveWriter writer2 = QuickSaveWriter.Create("Armor" + i);
    29.             writer2.Write("armor_id", ArmorCanvas.transform.GetChild(i).GetComponentInChildren<Armor>().ID);
    30.             writer2.Write("durability", ArmorCanvas.transform.GetChild(i).GetComponentInChildren<Armor>().durability);
    31.             writer2.Commit();
    32.         }
    33.         //Debug: Item files loaded into text
    34.         Content.text = "Armor Count: \n" + ArmorCount + "Item 0:" + "\n" + QuickSaveRaw.LoadString("Armor0.json")+ "Item1:" +"\n"+ QuickSaveRaw.LoadString("Armor1.json")+"Item2:" + "\n"+ QuickSaveRaw.LoadString("Armor2.json");
    35.  
    36.         //All Armor Gameobjects are destroyed after saving (for testing purposes), if there are any
    37.         int childCount = ArmorCanvas.transform.childCount;
    38.         if (childCount > 0)
    39.         {
    40.             for (int i = childCount - 1; i >= 0; --i)
    41.             {
    42.                 GameObject.Destroy(ArmorCanvas.transform.GetChild(i).gameObject);
    43.             }
    44.         }
    45.     }
    46.  
    47.     public void Load()
    48.     {
    49.         //check armor count
    50.         QuickSaveReader reader = QuickSaveReader.Create("Armor_Count");
    51.         int armorcount = reader.Read<int>("Armor_Count");
    52.         Debug.Log("armor count after loading is: " + armorcount);
    53.  
    54.         //add armor item prefabs only if armor items have been found
    55.         if (armorcount > 0)
    56.         {
    57.             Debug.Log("passing if loop for armorcount after loading");
    58.             for (int i = armorcount - 1; i >= 0; --i)
    59.             {
    60.                 Debug.Log("In iteration no. " + i);
    61.                 //acesses root key (ArmorX), writing Armor ID + Durability values into temporary variables
    62.                 QuickSaveReader reader2 = QuickSaveReader.Create("Armor" + i);
    63.                 int durab = reader2.Read<int>("durability");
    64.                 int ar_id = reader2.Read<int>("armor_id");
    65.  
    66.                 Debug.Log("durability for armor id no. " + ar_id + " is " + durab);
    67.  
    68.                 //prefab is chosen according to armor / Item ID
    69.                 switch (ar_id)
    70.                 {
    71.                 case 1001:
    72.                         armor_item = Instantiate(ar_1001) as GameObject;
    73.                         break;
    74.                 case 1002:
    75.                         armor_item = Instantiate(ar_1002) as GameObject;
    76.                         break;
    77.                 case 1003:
    78.                         armor_item = Instantiate(ar_1003) as GameObject;
    79.                         break;
    80.                 }
    81.                 Debug.Log("instantiated with ar id = " + ar_id);
    82.                 //Armor Items are moved to armor canvas and saved values are set
    83.                 armor_item.transform.SetParent(ArmorCanvas.transform, false);
    84.                 armor_item.GetComponentInChildren<Armor>().durability = durab;
    85.             }
    86.             //Q: (any way to create the prefabs name(ar_1001) automatically ?) -> case switch for 200+ items not desireable
    87.             //I can save all the values (even the fixed / static ones), but this seems ineffective when the info is already in the prefabs
    88.         }
    89.     }
    90.     }
     
    Last edited: Dec 3, 2018
  2. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,186
    For your current solution, it looks like you already have the ID's on the armors. I'd replace your ar_1001 and so on with an array of armors:

    Code (csharp):
    1. // old:
    2.     public GameObject ar_1001;
    3.     public GameObject ar_1002;
    4.     public GameObject ar_1003;
    5.  
    6. //new:
    7.     public GameObject[] armorPrefabs
    And then get their ID's and create a Dictionary from that:

    Code (csharp):
    1. // old
    2. public void Load()
    3. {
    4.     ...
    5.     switch (ar_id)
    6.     {
    7.     case 1001:
    8.             armor_item = Instantiate(ar_1001) as GameObject;
    9.             break;
    10.     case 1002:
    11.             armor_item = Instantiate(ar_1002) as GameObject;
    12.             break;
    13.     case 1003:
    14.             armor_item = Instantiate(ar_1003) as GameObject;
    15.             break;
    16.     }
    17.  
    18. // new:
    19.  
    20. public void Load()
    21. {
    22.     Dictionary<int, GameObject> idToArmor = new Dictionary<int, GameObject>();
    23.     foreach (var prefab in armorPrefabs) {
    24.         var id = prefab.GetComponentInChildren<Armor>().ID;
    25.         idToArmor[id] = prefab;
    26.     }
    27.  
    28.     ...
    29.     armor_item = Instantiate(idToArmor[ar_id]) as GameObject;
     
    chmod774 likes this.
  3. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,914
    So yes, use an array. The thing is, arrays are basic computer programming, which is good since it means you can read an intro book chapter explaining all the tricks and rules. A List (as in List<gameObject>) is another version of an array, and works the same.

    An alternative to a Dictionary is sorting the array and using a binary search to find IDs. Advantage is you can see the final sorted list in the Inspector, and can check it. Drawback is having to learn what a binary search is (speed is probably about the same).
     
    chmod774 likes this.
  4. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    In our game we have a convention that all item prefabs have unique names so we use the prefab name as ID. So we use this to seriliaze and deseriliaze inventory config into prefab instances. Once the inventory is deseriliaze we use the items as normal POCOs

    edit: also one last piece of the puzzle, we have a ScriptableObject in our resource folder with all Items, the index of each item becomes their unqiue item ID.
     
    Last edited: Dec 3, 2018
    angrypenguin and chmod774 like this.
  5. chmod774

    chmod774

    Joined:
    Nov 7, 2018
    Posts:
    4
    Ah thank you. I've added it and after some hickup (I've only added the ID for ar_1001 in my prefabs, but not in the prefab instances that I added for testing in the editor), it works great.

    On that note, it's probably better if I use Resources.Load to add the (reference) prefabs to the array? I am currently linking the prefabs with the inspector, now into the array entries after your changes.

    As I will know the total number of item prefabs, I can populate the array and it looks like I can create the item strings dynamically (e.g. "ar_ " +ID number in a for loop for the specific item ranges) as "ar_1001" here is a string, so I shouldn't need a dictionary for the initialization. I will try that out tomorrow.

    GameObject instance = Instantiate(Resources.Load("ar_1001", typeof(GameObject))) as GameObject;


    Ok yup, the second one definitely sounds too advanced for my level. For lists and dictionaries I've read up a bit before, but I did not think of using them before in this way, i.e. storing gameobjects directly there.

    Ah ok so this way you have the unique item ID. I am getting it from the children index in the attached canvas. Do you think there are relevant performance issues between using either of the two approaches? I have read that GameObject Find / FindWithTag are bad on performance, but using the index should be ok? My game will not be super complex though.
     
  6. Karrzun

    Karrzun

    Joined:
    Oct 26, 2017
    Posts:
    123
    Not a problem at all - for prototyping. When compiling and building your game, you should switch to something else, though. As stated in a Unity tutorial:
    Link
     
  7. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    You shouldnt have your actual prefabs in the Resource folder. You should have the bootstrapping of them there, or thats one possible solution atleast

    Her is our sctipable object that resides in Resource Folder

    upload_2018-12-4_0-9-31.png

    The network ids are then generated at runtime

    Code (CSharp):
    1.  
    2.  
    3. [CreateAssetMenu(menuName = "Network/NetworkableObjectsRegistry")]
    4. public class NetworkableObjectsRegistry : ScriptableObject
    5. {
    6.     public List<NetworkedMonoBehavior> networkableGameObjects = null;
    7.  
    8.     public IBidirectionalDictionary<int, NetworkedMonoBehavior> NetworkableObjects
    9.     {
    10.         get
    11.         {
    12.             if (networkableObjects == null)
    13.             {
    14.                 networkableObjects = GetNetworkableObjects();
    15.             }
    16.  
    17.             return networkableObjects;
    18.         }
    19.     }
    20.  
    21.     private static IBidirectionalDictionary<int, NetworkedMonoBehavior> networkableObjects;
    22.  
    23.     private IBidirectionalDictionary<int, NetworkedMonoBehavior> GetNetworkableObjects()
    24.     {
    25.         return new BidirectionalDictionary<int, NetworkedMonoBehavior>(networkableGameObjects.Select((obj, index) => new KeyValuePair<int, NetworkedMonoBehavior>(index, obj)));
    26.     }
    27. }
     
  8. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,504
    I'm thinking of moving one of our systems to a similar system, but with Addressable Assets rather than the resources folder for the bootstrapping.
     
    AndersMalmgren likes this.