Search Unity

Question 3D array/dictionary

Discussion in 'Scripting' started by alexb2008, Mar 15, 2023.

  1. alexb2008

    alexb2008

    Joined:
    Mar 16, 2022
    Posts:
    34
    How would I have a 2d array with each part having different parts to it?
    I am trying to make a crafting system for my game and need some way to keep on top of the recipes (preferably without using Scriptable Objects because I have not had fun with those so far!)
    My idea is some weird combination of 2d, 3d arrays and dictionaries and stuff, to be honest I don't really know what I have made...
    (I think there is probably a better solution to this other than what I am trying to do so if you know what that is then please let me know!)
    Basically, I need an array of arrays of dictionaries. e.g:
    Code (CSharp):
    1. public var[ [ [ Dictionary1<string,int>] , Dictionary2<string,int> ], [ [ Dictionary3<string,int>, Dictionary4<string,int>] , Dictionary5<string,int> ] ] arrayName;
    If that makes sense... the Dictionaries 1, 3 and 4 in this case would be the inputs and Dictionaries 2 and 5 would act as the output items. The string would refer to the name of the input/output and the int, the amount of that item.

    By the way, if there is a way to make it so that I can easily add newer recipes within the editor that would be great but if the only real good way to do that is with ScriptableObjects then I am alright, I have spent way too long with those already for them to now work on me.
     
  2. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,859
    I mean an array of dictionaries is just:
    Code (CSharp):
    1. Dictionary<string, int>[] // 1D array
    2.  
    3. Dictionary<string, int>[,] // 2d array
    4.  
    5. Dictionary<string, int>[][] // array of arrays of dictionaries
    And so on and so forth. But that feels like complete overkill, not to mention you won't be able to serialise these types of data structures.

    A list of a wrapper class that denotes an item and a quantity should be enough for basic crafting systems.

    I'm not sure what your issue with scriptable objects is. There's not much to them, they are objects you can code, can make instances of, and reference in the inspector. You'd be hard pressed to make an item/inventory system without them.
     
  3. alexb2008

    alexb2008

    Joined:
    Mar 16, 2022
    Posts:
    34
    the whole annoyance with SOs is that I need to loop through all of them so i need to keep them all together and I have made attempts at this literally this week but have had no success for a while so I'm, trying to find another solution
     
  4. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,859
    Looking at your previous thread, this seems to be due to a fundamental misunderstanding as to how Unity works.

    If you have a scriptable object like so:
    Code (CSharp):
    1. [CreateAssetMenu]
    2. public class Item : ScriptableObject
    3. {
    4.    
    5. }
    You can make instances of these in your project files with the right click menu.

    Then you just have a collection of these instances, and can just drag them into the collection:
    Code (CSharp):
    1. [SerializeField]
    2. private List<Item> _items = new List<Item>();
    That's all there is to it honestly.

    'Scripts' are just text documents. They aren't part of your game. You need to make actual instances of scriptable objects in your assets to be able to use them.
     
  5. alexb2008

    alexb2008

    Joined:
    Mar 16, 2022
    Posts:
    34
    I vaguley know how they work... but when I create a lot of recipies, I store them all in a file in the editor which then leads to the problem that looping through that file of SOs and trying to read the data from all of them is rather awkward for me (at my level) to do.
     
  6. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,859
    That's not an issue with scriptable objects, this is just a case of punching above your experience level.

    Every inventory/item system is going to require a central repository of all items. However, I haven't made a crafting system that has required constantly looking up in this database.

    A crafting recipe could be as simple as:
    Code (CSharp):
    1. [Serializable]
    2. public class ItemListing
    3. {
    4.     public Item Item;
    5.    
    6.     public int Quantity = 0;
    7. }
    8.  
    9. [CreateAssetMenu]
    10. public class CraftingRecipe : ScriptableObject
    11. {
    12.     [SerializeField]
    13.     private List<ItemListing> _recipeIngredients = new();
    14.    
    15.     public bool CanCraftRecipe(IEnumerable<ItemListing> items)
    16.     {
    17.         return // check items can satisfy recipe ingredients
    18.     }
    19. }
    I have the feeling you're over complicating matters.
     
  7. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,992
    Right. I actually have minecraft running at the moment and the pack I play has around 16k recipes. I'm not 100% certain how MC actually checks a recipe, however when you use proper data structures checking 16k recipes wouldn't be that slow. It may even just cache the last used / checked recipes. Though it's of course also possible to create some sort of hash / lookup index based on the provided items in the grid. It's also posssible to just group recipes based on the first item in the grid. That should usually cut down the number of recipes to just a few. Though of course it depends on the size of the crafting table / grid and the number of recipes what makes sense and what doesn't. In the pack I play it includes an extreme crafting table (9x9 from mod avaritia). It adds a whole new set of recipes, mainly to craft rare stuff like some creative items legally, though at a quite high price of course.
     
    orionsyndrome likes this.
  8. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,108
    I'm 99% sure it uses some sort of hashing. Each item has an ID, each slot has an ID, you make sure the hashing space is large enough and make sure to use a well-distributed hashing function to minimize collisions. The only other issue with this are the symmetrical recipes and "loose" recipes, for which I'm sure they just preload them with mirror flags, or simply discard the slot IDs.

    Of course, of course. Maybe Mojang complicated all of this in the meantime, and who knows what else mods do, but I strongly believe that the original recipe-solver by Notch and Jens was as simple as possible. Maybe even such a primitive solution, like serializing recipes into flat strings, then producing string maps (it was Java after all), it's all still driven by hashes, but you stop caring about the actual hashes, you kind of relay that to the language.

    Wait what was the question again?

    I was like you at some point, I made horrible stuff when I was young, it's ok, it's a learning process.
    But don't be naive in approaching your software from a standpoint of a brute-force goblin. Sure, computers are fast, but you can't be always blunt about it. Make use of memory, make use of clever solutions. Think ahead. You want your recipes to work blazing fast. You don't want to read a database of an ever-growing list of recipes every time the player tinkers with crafting.

    If hashing sounds too horrible for you, try thinking in terms of simple connectivity graphs. As bunny said, you can design your system around culling your recipes quickly every time an item is inserted. Because that should indeed narrow down the search space significantly. Or you can attempt to do this with the strings as I mentioned above, that's actually a decent solution for a beginner.

    I made a text viewer for DOS once (that was in the 90s). It was pretty and brilliant, and colorful, and even had an animated ASCII chrome.... Really neat and worked really well. Well, it actually didn't work well past a certain file size (that I haven't even considered to test properly), because I was constantly reading the file sequentially (due to lack of experience at that time with all the various ways you could access the file on a disk), and everything was fine below a certain size NOT because the language was great for optimizing my crappy code, but because the OS itself had a read cache memory. This was called smartdrive or something like that, you had to turn it on and it significantly impacted the longevity and speed of the drive, and I completely forgot about it (because it would boot with the system through so called autoexec, good old times), I was too busy thinking how awesome my reader was, and neglected its key functionality.

    The fix was relatively simple, but I got somewhat embarrassed regardless. These twists in how we think and approach our solutions make the world of difference in the perceived quality of the final product. If only I explored my options a bit further, tested ludicrous scenarios and file sizes, understood what was actually going on and how much a random access would help with reading exactly what was necessary, instead of wasting time with the invisible stuff, reading potentially huge text files that were supposed to be in someone else's hands. The way it scaled was similar to a Porsche doing 0-60 mph into a brick wall. So I'd compare that design approach to "blind Porsche driver".

    Computers are not magic. They actually shift stuff around and particularly bad instruction combinations will make them choke quite often, especially today with super tight hardware that is ever so consolidated around very specific "industry-standard" paths of optimal performance. People are sometimes obsessed with shaving every little microsecond from their logic, math and updates, yet the issues lie in the extremely bad overall design choices for their business core. You can't micromanage that later, because it's all over the place.
     
    Last edited: Mar 16, 2023
    Bunny83 and mopthrow like this.
  9. SeerSucker69

    SeerSucker69

    Joined:
    Mar 6, 2021
    Posts:
    68
    You haven't given me much code or an idea of what the recipes look like, or what you are trying to do, but I think if you structure your data a little differently it can work easily for you, which you can extend 1 at a time, and doesn't use SO's.

    I will show you an example that covers all unit "abilities" which may be similar to your recipes:

    First you have a class for the ability/recipe

    That class can contain any data your game needs, mine has a few things like range, cooldown, and I use an INT to store 16 bools very efficiently.

    Then each of your units can have a list of abilities they know, which is just an INT storing the "ability" or "Recipe" ID

    Code (CSharp):
    1.     public List<int> UnitAbilities;      // List of the units special abilities
    when you need the data of a reciepie you can create it on the fly using the class "constructor", and pass it the ID you want to create, which will return the ability/recipe and all the data you need, like this:

    var myAbility = new(AbilityID); // create it

    var myrange = myAbility.Range; // use it!

    You can easily add new abilties/recipies by cutting and pasting a new Case block and changing the data, as often and as easily as you want.

    This approach may have the purists tongue wagging, but gets you running quick, gives you room to expand and you can look up recipe data in an instant.

    Hope this helps or steers you into a working soultion!
    Good luck!


    Example class container for abilities:

    Code (CSharp):
    1. public class Ability  //class to describe units special abilities
    2. {
    3.     public int AbilityID;                // Ability ID
    4.     public string Desc;                  // Descrition of Ability            
    5.     public int Range;                    // Range of ability
    6.     public int AbilityFlags;             // 16 flags you can use
    7.     public int CoolDown;                 // Number of turns left to cooldown ability
    8.  
    9.     public Ability(int AbilityType) // Constructor
    10.     {
    11.         AbilityID = AbilityType;
    12.         AbilityFlags = 0;
    13.         CoolDown = 0;
    14.  
    15.         switch (AbilityID)
    16.         {
    17.             case 1:
    18.                 Desc = "Repentance";         // Remove curse adjacent ally
    19.                 Range = 1;
    20.                 break;
    21.             case 2:
    22.                 Desc = "Hand of God";        // Shotgun blast
    23.                 Range = 6;
    24.                 CoolDown = 99;
    25.                 AbilityFlags = SetBitFlag(AbilityFlags, 2);   //BitFlagAbilityAttack This ability is an attack
    26.                 AbilityFlags = SetBitFlag(AbilityFlags, 3);   //BitFlagAbilityAttackNeedsLOS This ability needs LOS
    27.                 break;
    28.             case 3:
    29.                 Desc = "Sniff";         // Detect enemies
    30.                 Range = 20;
    31.                 break;
    32.             case 4:
    33.                 Desc = "Bite";        // bite adjacent target
    34.                 AbilityFlags = SetBitFlag(AbilityFlags, 2);   //BitFlagAbilityAttack This ability is an attack
    35.                 Range = 1;
    36.                 break;
    37.             case 5:
    38.                 Desc = "Reload Shotgun";        // Reloads shottie takes an action point
    39.                 Range = 0;
    40.                 break;
    41.             case 6:
    42.                 Desc = "Bite";        // bite adjacent target
    43.                 AbilityFlags = SetBitFlag(AbilityFlags, 2);   //BitFlagAbilityAttack This ability is an attack
    44.                 Range = 1;
    45.                 break;
    46.             case 7:
    47.                 Desc = "Groan";        // Calls Other zombies to the feast
    48.                 Range = 10;
    49.                 break;
    50.             default:
    51.                 Debug.Log("Invalid Ability ID passed to Ability Constructor");
    52.                 break;
    53.  
    54.         }
    55.     }