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. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

[TLDR;] Best way of dealing with randomly generated items?

Discussion in 'Scripting' started by GeekStories, Jun 24, 2018.

  1. GeekStories

    GeekStories

    Joined:
    Jan 22, 2015
    Posts:
    74
    So, in my game I have a generator that spits out Items (object). Each Item has a few variables, the most important (and troublesome) being the Sprite/Icon. I need to find out the best way to handle these Items in a dynamic way. What I mean by that, is the Sprite being thrown around a lot. But ensuring the correct Sprite is passed. Sorry if that's confusing. Here is what I mean a bit more in-depth as well as the problem I was having with using ScriptableObjects for this situation.

    Say there are three main categories:
    Armour,
    Weapons and
    Consumables

    Each category has a sub-category:
    Armour > Head wear
    Weapons > Knifes
    Consumables > Food

    Each sub-category is full of items relating to that sub-category:
    Armour > Head wear > Metal Helmet
    Weapons > Knifes > Fishing Blade
    Consumables > Food > Bread

    When an item is generated, at random a main category is selected then a random item from that categories sub-category is given to that item indicating the type of item it is.

    So a new Item is being generated:
    Say Armour is selected for the main category, Then Head wear, then Metal Helmet would be the sprite. The rest of the information for that item is randomly generated, a couple ints and floats.

    The newly generated item would then appear on screen where the user can click on it and be shown a bigger picture with more information and a few options. I had gotten everything working except the sprite. All the correct information would be there, and all the options would work, but sometimes the sprite being shown would be the wrong one. There was also a problem I had with the scriptableobjects where not all the sprites were being used in generation, it would sort of only use the sprite attached to the scriptableobject in the Editor.

    However, I'm not sure how I would store all the information for each items. I've tried ScriptableObjects. I can't wrap my head around getting that working with how I need to, as mentioned before. And creating a new GameObject for each Item doesn't seem like a good idea as the user could have hundreds of items at any one stage and I think it would decrease performance.

    Do note: Not ALL the generated items are kept, most are deleted after a couple of minutes, and that's that. But the ones that stay, are the ones the user hand-picked.

    Any idea on how I could handle a large amount of objects in this case?

    Cheers.
    P.S (sorry if this was difficult to understand and or this didn't make any sense. It's late and I am struggling to think of a better way to describe what I need help with)
     
  2. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    1,596
    That partly depends on how unique you want each item to be. For example, if each item has its own distinct set of (non-algorithmic) values, then you will have to have some way of tying that unique grouping together. Using scriptable objects is one reasonable way to do this.

    I agree that having a GameObject specifically for each item seems a little unnecessary in this case, but not on the grounds of performance. :)

    So here is some code. Hopefully it may help a little. It describes a simple interface hierarchy. This is to allow for polymorphism in code that needs to handle these types.

    As a demonstration of its use, it allows for the creation of assets of new types of Armour. In the Editor, follow menu Assets -> Create -> Armour -> Headwear. You will then be able to create a new head wear item by dragging a sprite into the inspector for it. You now have a type that can be instantiated in code and will have the correct image associated with it.
    Code (CSharp):
    1. public interface IItem
    2. {
    3.     Sprite Image { get; } // This would be a prefab set in the Editor, never changed
    4. }
    5.  
    6. public interface IArmour : IItem
    7. {
    8.     float MaxStrength { get; }
    9.     float CurrentStrength { get; }
    10.     void DamageTaken(float damage);
    11. }
    12.  
    13. public interface IWeapon : IItem
    14. {
    15.     float DamageDealt { get; }
    16. }
    17.  
    18. public interface IConsumable : IItem
    19. {
    20.     float HealthRestored { get; }
    21. }
    22.  
    23. public interface IItemSubCategory
    24. {
    25.     List<IItem> AllItems { get; }
    26.     IItem Random { get; }
    27. }
    28.  
    29. public interface IItemCategory
    30. {
    31.     List<IItemSubCategory> AllSub { get; }
    32.     IItemSubCategory Random { get; }
    33. }
    34.  
    35.  
    36. [CreateAssetMenu(fileName = "Helmet", menuName = "Armour/Headwear", order = 1)]
    37. public class ItemArmour : ScriptableObject, IArmour
    38. {
    39.     void Start()
    40.     {
    41.         m_currentStrength = maxStrength;
    42.     }
    43.  
    44.     Sprite IItem.Image { get { return m_sprite; } }
    45.     float IArmour.MaxStrength { get { return maxStrength; } }
    46.     float IArmour.CurrentStrength { get { return m_currentStrength; } }
    47.  
    48.     void IArmour.DamageTaken(float damage)
    49.     {
    50.         throw new System.NotImplementedException();
    51.     }
    52.  
    53.  
    54. #pragma warning disable 649
    55.     [SerializeField] Sprite m_sprite;
    56.     [SerializeField] float maxStrength;
    57. #pragma warning restore 649
    58.  
    59.     float m_currentStrength;
    60. }
     
  3. GeekStories

    GeekStories

    Joined:
    Jan 22, 2015
    Posts:
    74
    Hey, thanks for the detailed reply!

    The only uniqueness of each item is the sprite. Everything else is relatively the same.

    I see what you mean with the code you've supplied. That, is essentially the exact way I had it in the first place. I'd create an item in the Editor for each actual item, then apply a sprite to it. However, I have 300+ sprites that can be used and that'd be a lot of objects created in the editor. So doing that all by hand would take a while. So I had it setup where each item was a blank slate, and the name of the item would determine the type of item it would be. For example, I had it setup where The main category and Sub category were folders in the editor, and inside the sub-category folder were all the items that are ScriptableObjects with no sprites applied, because each ScriptableObject had a set of sprites that would be chosen at random when said item was generated.

    Code (CSharp):
    1.  
    2. // This is how I had it written
    3. [CreateAssetMenu(fileName = "Create Item", menuName = "Item", order = 1)]
    4.  
    Then when I create an item in the editor, I'd give the Item object a name such as Metal Helmet, and because there were, say, 3 sprites that were all metal helmets, they would be randomly picked on generation of the item in-game.

    I should also mention that the other variables for each item are: _rarity(int), _value (int), _startingPrice (bool), _pricePaid(int) and _evaluated (bool). The game is a auction type game where items are generated and sold at an auction where you bid against AI players, you can then sell items you win in your shop for a profit as well as view them in your inventory. Probably should have mentioned this earlier as it would help with the style I am going for. Sorry :oops:!

    If it helps, this is how my ScriptableObjects code looked.

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3.  
    4. [CreateAssetMenu(fileName = "Create Item", menuName = "Item", order = 1)]
    5. public class Item : ScriptableObject {
    6.     //public string _name, _description;
    7.  
    8.     public Sprite _icon;
    9.     public int _rarity, _value, _startingPrice, _pricePaid;
    10.     public bool _evaluated = false;
    11.  
    12.     //This would be called when the item was created
    13.     public void FillInfo(){
    14.         switch (name)
    15.         {
    16.             case "Foods":
    17.                 _icon = Database._icons[0][0][Random.Range(0, Database._icons[0][0].Length)];
    18.                 break;
    19.             case "Potions":
    20.                 _icon = Database._icons[0][1][Random.Range(0, Database._icons[0][1].Length)];
    21.                 break;
    22.             case "Misc":
    23.                 _icon = Database._icons[1][0][Random.Range(0, Database._icons[1][0].Length)];
    24.                 break;
    25.             case "Spells":
    26.                 _icon = Database._icons[2][0][Random.Range(0, Database._icons[2][0].Length)];
    27.                 break;
    28.             case "Axes":
    29.                 _icon = Database._icons[3][0][Random.Range(0, Database._icons[3][0].Length)];
    30.                 break;
    31.             case "Bows":
    32.                 _icon = Database._icons[3][1][Random.Range(0, Database._icons[3][1].Length)];
    33.                 break;
    34.             case "Cannons":
    35.                 _icon = Database._icons[3][2][Random.Range(0, Database._icons[3][2].Length)];
    36.                 break;
    37.             case "Daggers":
    38.                 _icon = Database._icons[3][3][Random.Range(0, Database._icons[3][3].Length)];
    39.                 break;
    40.             case "Maces":
    41.                 _icon = Database._icons[3][4][Random.Range(0, Database._icons[3][4].Length)];
    42.                 break;
    43.             case "Spears":
    44.                 _icon = Database._icons[3][5][Random.Range(0, Database._icons[3][5].Length)];
    45.                 break;
    46.             case "MiscWeapons":
    47.                 _icon = Database._icons[3][6][Random.Range(0, Database._icons[3][6].Length)];
    48.                 break;
    49.             case "Staffs":
    50.                 _icon = Database._icons[3][7][Random.Range(0, Database._icons[3][7].Length)];
    51.                 break;
    52.             case "Swords":
    53.                 _icon = Database._icons[3][8][Random.Range(0, Database._icons[3][8].Length)];
    54.                 break;
    55.             case "Throwables":
    56.                 _icon = Database._icons[3][9][Random.Range(0, Database._icons[3][9].Length)];
    57.                 break;
    58.             case "BodyArmour":
    59.                 _icon = Database._icons[4][0][Random.Range(0, Database._icons[4][0].Length)];
    60.                 break;
    61.             case "Headwear":
    62.                 _icon = Database._icons[4][1][Random.Range(0, Database._icons[4][1].Length)];
    63.                 break;
    64.             case "Medallions":
    65.                 _icon = Database._icons[4][2][Random.Range(0, Database._icons[4][2].Length)];
    66.                 break;
    67.             case "Necklace":
    68.                 _icon = Database._icons[4][3][Random.Range(0, Database._icons[4][3].Length)];
    69.                 break;
    70.             case "Rings":
    71.                 _icon = Database._icons[4][4][Random.Range(0, Database._icons[4][4].Length)];
    72.                 break;
    73.             case "Shields":
    74.                 _icon = Database._icons[4][5][Random.Range(0, Database._icons[4][5].Length)];
    75.                 break;
    76.             case "Shoes":
    77.                 _icon = Database._icons[4][6][Random.Range(0, Database._icons[4][6].Length)];
    78.                 break;
    79.             default:
    80.                 break;
    81.         }
    82.  
    83.         _rarity = Random.Range(1, 11);
    84.         _value = _rarity * (Random.Range(1, 100) * Random.Range(1,5));
    85.         _startingPrice = _value / _rarity;
    86.     }
    87.  
    As you can see, the sprites were dependent on the name of the Item in the editor. All the item types were apart of a big array in the Database script and were selected at Random.

    In the Database script, the Sprites were sorted like this. Im not sure how I can explain it but heres the code for it which might make sense.

    Code (CSharp):
    1.  
    2. using UnityEngine.UI;
    3. using UnityEngine;
    4. using System.Collections.Generic;
    5.  
    6. public class Database : MonoBehaviour {
    7.     public static List<List<Sprite[]>> _icons; // Collection of images for items
    8.  
    9.     List<Sprite[]> consumables;
    10.     List<Sprite[]> misc;
    11.     List<Sprite[]> spells;
    12.     List<Sprite[]> weapons;
    13.     List<Sprite[]> wearables;
    14.    
    15.     //Shop inventory
    16.     //public List<Item> _shopInv;
    17.  
    18.     //Consumables
    19.     public Sprite[] food;
    20.     public Sprite[] potions;
    21.  
    22.     //Misc
    23.     public Sprite[] miscIcons;
    24.  
    25.     //Spells
    26.     public Sprite[] spellIcons;
    27.  
    28.     //Weapons
    29.     public Sprite[] axes;
    30.     public Sprite[] bows;
    31.     public Sprite[] cannon;
    32.     public Sprite[] dagger;
    33.     public Sprite[] mace;
    34.     public Sprite[] spears;
    35.     public Sprite[] miscWeapons;
    36.     public Sprite[] staffs;
    37.     public Sprite[] swords;
    38.     public Sprite[] throwables;
    39.  
    40.     //Wearables
    41.     public Sprite[] bodyArmour;
    42.     public Sprite[] headwear;
    43.     public Sprite[] medallions;
    44.     public Sprite[] necklace;
    45.     public Sprite[] rings;
    46.     public Sprite[] shields;
    47.     public Sprite[] shoes;
    48.  
    49.   //public static string[] _names; // List of names for items
    50.   //public static string[] _descriptions; // List of descriptions for items
    51.     public static string[] _bidders = { "John","Michael","Smith","Josh","Zane","Dorothy","Kim","Kathy","Liam","Tyler","Ruby" }; // Names of other bidders for the game
    52.  
    53.     private void Awake()
    54.     {
    55.         _icons = new List<List<Sprite[]>>();
    56.  
    57.         consumables = new List<Sprite[]>();
    58.         misc = new List<Sprite[]>();
    59.         spells = new List<Sprite[]>();
    60.         weapons = new List<Sprite[]>();
    61.         wearables = new List<Sprite[]>();
    62.  
    63.  
    64.         //Consumables
    65.         consumables.Add(food);
    66.         consumables.Add(potions);
    67.  
    68.         //Misc
    69.         misc.Add(miscIcons);
    70.  
    71.         //Spells
    72.         spells.Add(spellIcons);
    73.  
    74.         //Weapons
    75.         weapons.Add(axes);
    76.         weapons.Add(bows);
    77.         weapons.Add(cannon);
    78.         weapons.Add(dagger);
    79.         weapons.Add(mace);
    80.         weapons.Add(spears);
    81.         weapons.Add(miscWeapons);
    82.         weapons.Add(staffs);
    83.         weapons.Add(swords);
    84.         weapons.Add(throwables);
    85.  
    86.         //Wearables
    87.         wearables.Add(bodyArmour);
    88.         wearables.Add(headwear);
    89.         wearables.Add(medallions);
    90.         wearables.Add(necklace);
    91.         wearables.Add(rings);
    92.         wearables.Add(shields);
    93.         wearables.Add(shoes);
    94.  
    95.         //Add the List of arrays to the list of icons
    96.         _icons.Add(new List<Sprite[]>());
    97.         _icons[0] = consumables;
    98.  
    99.         _icons.Add(new List<Sprite[]>());
    100.         _icons[1] = misc;
    101.  
    102.         _icons.Add(new List<Sprite[]>());
    103.         _icons[2] = spells;
    104.  
    105.         _icons.Add(new List<Sprite[]>());
    106.         _icons[3] = weapons;
    107.  
    108.         _icons.Add(new List<Sprite[]>());
    109.         _icons[4] = wearables;
    110.     }
    111. }
    112.  
    To make sense of that, here is how I had it on paper before putting it in to code.

    To access a sprite I would do this
    _icons[x][y][z]
    X being the Category and Y being the sub-category and Z being the singular sprite

    Category =
    Consumables = [0][y][z]
    Misc = [1][y][z]
    Spells = [2][y][z]
    Weapons = [3][y][z]
    Wearables = [4][y][z]​
    Sub-Category =
    Food = [0][0][z] //Consumable
    Potion = [0][1][z] //Consumable
    MiscIcon = [1][0][z] //Misc
    Spells = [2][0][z] //Spells
    Axe = [3][0][z] //Weapons
    Bows = [3][1][z] //Weapons
    ...
    ...
    bodyArmour = [4][0][z] //Armour
    ...
    ...
    Shields = [4][5][z]
    Shoes = [4][6][z]
    Hopefully this all makes sense and helps with making it easier to understand what I'm trying to do.

    Also, sorry again for the TLDR!
     
  4. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    1,596
    Ok, I think that gives a little more idea of what you are doing.

    So is the problem happening in
    FillInfo
    ? Or is that function allocating the correct image that then goes wrong at a later stage?
     
  5. GeekStories

    GeekStories

    Joined:
    Jan 22, 2015
    Posts:
    74
    I don't believe its happening directly in FillInfo. But I think it is taking part in the problem. I think how I've setup the function to run, causes the Sprite on the Editor copy of the Item to be set, rather than the item created through the generation code. And whenever the Item is copied, the new sprite is ignored on generation. Some items will show the correct sprite, but other items will show a different sprite from the same sub-category item, and because its from the same sub-category item, is what makes me think its not directly happening in FillInfo, but rather some place else. Whether that be in the Database, or the GenerateListing script. FillInfo is called from the GenerateListing script once the new Item as been created.

    Code (CSharp):
    1.  
    2. //This is called 14 times in the Start() function of this script through a for loop so the first 14 listing are created
    3. public void Generate(){
    4. [//Create a new Listing object
    5. GameObject go = Instantiate(Listing, transform.position, transform.rotation);
    6.  
    7. //Shove the new Listing inside of the container for the user to see
    8. go.transform.SetParent(ListingContainer.transform, false);
    9.  
    10. //Create a new item object
    11. Item item = new Item();
    12.  
    13. //Fill in the information for the newly created item
    14. item.FillInfo();
    15.  
    16. //This gives the newly created listing the item so it can display the information of the item to the user
    17. list.item = item;
    18. }
    19.