Search Unity

[SOLVED] Key Not Found - But it's definitely there!

Discussion in 'Scripting' started by Smoth48, Nov 17, 2014.

  1. Smoth48

    Smoth48

    Joined:
    Nov 17, 2014
    Posts:
    14
    I am trying to make a game in which I have an inventory. Each GameObject within the game is associated with an Item object, via a dictionary. The goal is to have the player click an object in the game world and have it transferred into the inventory.

    When I try to convert the object into its associated item by checking the dictionary reference, it seems that the key is not found.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. public class SynapseThings : MonoBehaviour {
    6.  
    7.     //GameObject List - All physical items in the game. Every single one. All of 'em.
    8.     public static GameObject emptyPrefab;
    9.     public static GameObject sparkleyRockPrefab;
    10.  
    11.     public GameObject emptyPrefabMask;
    12.     public GameObject sparkleyRockPrefabMask;
    13.    
    14.     public Dictionary<int, GameObject> intObjectDictionary = new Dictionary<int, GameObject>();
    15.     public Dictionary<int, Item> intItemDictionary = new Dictionary<int, Item>();
    16.     public Dictionary<GameObject, int> objectIntDictionary = new Dictionary<GameObject, int>();
    17.     public Dictionary<Item, int> itemIntDictionary = new Dictionary<Item, int>();
    18.  
    19.     void Awake(){ //Object mask statification
    20.  
    21.         emptyPrefab = emptyPrefabMask;
    22.         sparkleyRockPrefab = sparkleyRockPrefabMask;
    23.  
    24.         intObjectDictionary [ItemList.emptyItem.id] = emptyPrefab;
    25.         intObjectDictionary [ItemList.sparkleyRockItem.id] = sparkleyRockPrefab;
    26.  
    27.         intItemDictionary [ItemList.emptyItem.id] = ItemList.emptyItem;
    28.         intItemDictionary [ItemList.sparkleyRockItem.id] = ItemList.sparkleyRockItem;
    29.  
    30.         objectIntDictionary [emptyPrefab] = ItemList.emptyItem.id;
    31.         objectIntDictionary [sparkleyRockPrefab] = ItemList.sparkleyRockItem.id;
    32.  
    33.         itemIntDictionary [ItemList.emptyItem] = ItemList.emptyItem.id;
    34.         itemIntDictionary [ItemList.sparkleyRockItem] = ItemList.sparkleyRockItem.id;
    35.  
    36.     }
    37.        
    38.     public Item objectToItem(GameObject obj){
    39.         Debug.Log ("ST ln28 " + obj.gameObject);
    40.         if (sparkleyRockPrefab==obj){
    41.             Debug.Log("ST ln29 true");
    42.         }
    43.         int objectId = objectIntDictionary [obj];
    44.         return intItemDictionary [objectId];
    45.     }
    46.  
    47. }
    I am aware that this code is messy, and I am sure there are better ways to do certain things, but that is not the current concern. The shown code returns a KeyNotFound exception at line #43, when we call "objectIntDictionary[obj]".

    I have tried using a single dictionary that relates directly from GameObject to Item, but it was not working, so I switched over to this method (GameObject to Int, then Int to Item) to see if my luck was any better. Needless to say, this was not any more effective. I know that (obj==sparkleyRockPrefab) returns true, so I do not understand why the key is not found.

    If anyone has any suggestions to (potentially) solve my problem it would be greatly appreciated, that is the primary goal of this thread. Also, while you're here, if you feel like it, I also welcome you to offer suggestions on formatting and other things that are going wrong in my code.

    Thanks!
     
  2. slay_mithos

    slay_mithos

    Joined:
    Nov 5, 2014
    Posts:
    130
    I think you have multiple potential problems, but the one relative to your question is this one:
    A dictionnary with a complex object as the key means that it needs the exact same object when trying to retrieve the value.

    What it means is that the object has to be the exact same one as the reference one, and the == comparator does not provide this.
    You might want to compare with 'sparkleyRockPrefab.Equals(obj)', or even with the === comparator.

    It all comes down to the fact that the Dictionary type is supposed to have a simple type as the key (int, string...).
    To "solve" this problem, you can go with tags, or adding an ID or similar to all the items that need to be referenced.


    Ranting ON
    Also, while it might not be the question, I feel the need to say that Dictionary is not a type that should be a go-to for storing a lot of data.
    It might sound nice, with the key-value system, but it will impact your performances quite a bit as you put more and more data into them.
    Ranting OFF
     
  3. Stoven

    Stoven

    Joined:
    Jul 28, 2014
    Posts:
    171
    A quick question.

    How are you asigning emptyPrefabMask and sparkleyRockPrefabMask? It's probably obvious that you're assigning prefabs to these variables, but I'm asking because you could be assigning GameObject instances from your scene onto those variables.

    @slay_mithos Could you please explain why Dictionary is bad for storing a lot of data? Are you talking about the look-up time, insertion time (which, I suppose, requires an additional lookup to make sure the key doesn't already exist for a new entry), or just the amount of memory that it will eventually take up... or all of the above?

    How many items would you say is appropriate for a Dictionary?
     
  4. Smoth48

    Smoth48

    Joined:
    Nov 17, 2014
    Posts:
    14
    @Stoven, to clarify, I am assigning the masks through the inspector. Game objects which are present in the scene are inserted into those variables. The reason I am doing it that way is because I can't figure out a way to access non-static variables within functions, but I can't assign static variables through the inspector? I'm sure there's a better workaround than that. Idk lol.

    @slay_mithos, would you mind giving an example of the proper implementation of the Equals() function or the === comparator? I do not have experience with either of those utilities and I would appreciate the clarification,

    Thanks!
     
  5. slay_mithos

    slay_mithos

    Joined:
    Nov 5, 2014
    Posts:
    130
    It mostly comes down to the fact that it is iterating through the keys every time you try to access anything.
    It has to compare the value to every key until it finds the one it is looking for.
    Basically, it is pretty CPU intensive, especially if the key is not an int.

    I am not saying that you should never use Dictionary, but more often than not, a clever use of arrays, or List<T> are able to net you better performances.
    It does means you have to think about things a bit differently, and designing good systems for your use case are not always an easy thing to do.

    Code (CSharp):
    1.  
    2.   public Item objectToItem(GameObject obj){
    3.      Debug.Log ("ST ln28 " + obj.gameObject);
    4.    
    5.      //By default, compares the objects
    6.      if (sparkleyRockPrefab==obj){
    7.        Debug.Log("== is true");
    8.      }
    9.    
    10.      //By default Equals compares the content of the objects.
    11.      if (sparkleyRockPrefab.Equals(obj)){
    12.        Debug.Log(".Equals is true");
    13.      }
    14.    
    15.      //Never use === on simple variables (int, string, float, double ...),
    16.      //because it compares the instances, not just the values
    17.      //Usually, this operator is not very used, because too specific.
    18.      if (sparkleyRockPrefab === sparkleyRockPrefab){
    19.        Debug.Log("=== is true");
    20.      }
    21.    
    22.      //Note that you it is usual to override == or Equals for complex objects,
    23.      //when you only want to compare specific fields, as opposed to the whole object.
    24.    
    25.      //int objectId = objectIntDictionary [obj];
    26.      //return intItemDictionary [objectId];
    27.    
    28.      //TODO: return proper item
    29.      return new Item();
    30.    }
    31.  
    I don't advise on using === unless you document yourself quite a bit on it before hand.
    Well, it is also true for the other two, but they are pretty easy to understand, usually.

    My point is that comparing complex objects is never really strait forward, especially when you compare a prefab to instanciated objects, because the smallest difference between them will cause the comparators to say that they are not the same.

    If you absolutely want to continue the way you are, I suggest that you do something like this (needs to be adapted)
    Code (CSharp):
    1.  
    2.    Item item= null;
    3.    if (objectIntDictionary.ContainsKey(obj)){
    4.      int objId = objectIntDictionary[obj];
    5.      if (intItemDictionary.ContainsKey(objId)){
    6.        item = intItemDictionary[objId];
    7.      }
    8.    }
    9.    return item
    10.  
     
  6. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    A few things. Dictionaries use a hash of whatever type their key is. This calls GetHashCode which returns an integer. It's the hash values that are compared in order to find a key, not Equals() or ==. To that end, hash lookups tend to be fairly quick (a HashSet<T> for example is designed to be a list of only unique values and makes heavy use of hash codes).

    It also makes no sense to call ContainsKey() before doing the lookup because you're essentially doing the lookup twice at that point. Dictionary has a TryGetValue method which takes an out parameter to fill with the value associated with the given key, if any, and returns a boolean indicating whether or not a value was found. So you'd be better off doing something like this:
    Code (csharp):
    1.  
    2. Item item = null;
    3. if (dictionary.TryGetValue(someKey, out item))
    4. {
    5.     // do something with 'item'
    6. }
    7.  
    Lastly, C# does not have a === operator. This is a web JavaScript (ECMAScript) operator used to compare equality of 2 objects without doing any casting (because it is a loosely typed language).
     
  7. Smoth48

    Smoth48

    Joined:
    Nov 17, 2014
    Posts:
    14
    To everyone who has replied in one way or another;

    I thank you for your time and assistance. However, I am still having issues.

    @slay_mithos I tried several different implementations using the .Equals() function and still had no luck. I believe the error there is the same one you alluded to: the objects are not THE SAME object, and therefore are not treated as equal within the function. I was hesitant to try the '===' operator after receiving the information from @KelsoMRK.

    Kelso, you seem to have a grasp of the necessary information and I appreciate your assistance. I understand use of the TryGetValue function, but I do not think that it will help solve my problem. To reiterate my initial problem, I am not able to get the script to detect the key as being present, because the objects, although being equivalent, are not the same entirely. Do you have any other ideas, suggestions, etc?

    Thanks again everyone for your input so far. I hope that we will be able to come to a solution soon.
     
  8. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Can you explain, without code, what you're trying to achieve?
     
  9. hpjohn

    hpjohn

    Joined:
    Aug 14, 2012
    Posts:
    2,190
    Dictionary<GameObject,other> works fine
    I'm wondering why you need the prefabs to be static
    This makes no sense
     
  10. Smoth48

    Smoth48

    Joined:
    Nov 17, 2014
    Posts:
    14
    When I try to assign/reference the value of a variable within another function, it returns an error if the variables are non-static. Like I also mentioned, I'm probably doing something wrong there. If you have suggestions, lemme know.

    The goal is to allow the player to click objects in the game world and have them transferred into the inventory as an Item. Depending on what the game object is that gets clicked on, the respective item is inserted into the player's inventory.

    The method in question here (objectToItem) is tasked with taking the in-game object and turning it into the in-inventory Item.
     
  11. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    How are you creating the link between a GameObject in the world and the inventory Item it represents? IE - I click on SomeGameObject - how do I know what type of Item it is?
     
  12. Smoth48

    Smoth48

    Joined:
    Nov 17, 2014
    Posts:
    14
    I have been using dictionaries to link the two types together.

    I initially used a Dictionary<GameObject, Item>, but was experiencing problems.
    In the code seen above, I use Dictionary<GameObject, int> in conjunction with Dictionary<int, Item> to make a less direct link, but this did not help. Would there be a better way to create a linkage between the objects and their repsective items?
     
  13. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Where are you populating the Dictionary<GameObject, Item> with the GameObjects in your world?
     
  14. hpjohn

    hpjohn

    Joined:
    Aug 14, 2012
    Posts:
    2,190
    Are those functions also static?
     
  15. Smoth48

    Smoth48

    Joined:
    Nov 17, 2014
    Posts:
    14
    Sorry for taking so long to get back to you guys.

    The dictionary is populated in the SynapseThings class, the same one I have posted above. Presently, the only existing GameObject I am concerned with is the sparkleyRockPrefab. This project is in the very beginning phases and currently, that is the only existing in-game item. The entire dictionary with all in-game objects will be populated within this class.

    Hmm... No. In all honesty, I do not have a very good understanding of what the static modifier does. I have put a good couple of hours into researching the static modifier but I just don't really understand it. Would making the functions static allow them to access non-static variables? Because a non-static function seems to only have access to static variables, as far as I have experienced within Unity / C#
     
  16. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    If this is the case then the only time your code would work would be if you passed sparklyRockPrefab to the method (the thing itself, not some instance of it in the world).
     
  17. Smoth48

    Smoth48

    Joined:
    Nov 17, 2014
    Posts:
    14
    Okay, I suppose that makes sense. In the given scenario, however, the function will ONLY have instances from the world passed into it. What might you suggest that I look into in order to allow that to work? I have another theory that I might try to go after if I can't get something simpler to work (my other theory would be a pretty serious rewrite of a couple scripts).
     
  18. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    The most straightforward way to do it would be to attach an Item MonoBehaviour to the prefab that has data about what kind of Item it is.
     
  19. Smoth48

    Smoth48

    Joined:
    Nov 17, 2014
    Posts:
    14
    That's what I thought about doing, and ended up doing in the end. I now have the system working, based on your suggested method. Thank you very much for all your help, you have been extremely helpful to me.