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

Bug KeyNotFound Exception Error

Discussion in 'Scripting' started by nebuchednezar, May 11, 2023.

  1. nebuchednezar

    nebuchednezar

    Joined:
    Feb 6, 2021
    Posts:
    31
    Hello, i'm making a tile blasting game in Unity. However there's a bug in my game where i can no longer destroy the matched cubes when i click on them. After i do some debugging, i saw that the when i destroy some of the matches the keynotfound exception error appear on the console when i click on the cubes which i cannot destroy. I'm a very beginner level in C# but i learned that i get this error because some of the keys inside the dictionary are missing. How can i stop getting this error and fix the bug in the game. Here's the github link of my project repository if anyone's willing to take a look:
    https://github.com/JCD3nton/Toon-Blast-clone
    There's also a video link of the game and the bug i'm experiencing in the game.

    Please let me know if you need any further information. Thanks anybody who would help me in advance
     
  2. RadRedPanda

    RadRedPanda

    Joined:
    May 9, 2018
    Posts:
    1,593
    Where is the Dictionary being handled? It's a lot of code to run through.
     
  3. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,711
    You can always guard against invalid Dictionary lookups by using the .ContainsKey() method to test before using the key.

    This might completely fix your problem, or there might be another problem.

    Otherwise, time to start debugging! Here is how you can begin your exciting new debugging adventures:

    You must find a way to get the information you need in order to reason about what the problem is.

    Once you understand what the problem is, you may begin to reason about a solution to the problem.

    What is often happening in these cases is one of the following:

    - the code you think is executing is not actually executing at all
    - the code is executing far EARLIER or LATER than you think
    - the code is executing far LESS OFTEN than you think
    - the code is executing far MORE OFTEN than you think
    - the code is executing on another GameObject than you think it is
    - you're getting an error or warning and you haven't noticed it in the console window

    To help gain more insight into your problem, I recommend liberally sprinkling
    Debug.Log()
    statements through your code to display information in realtime.

    Doing this should help you answer these types of questions:

    - is this code even running? which parts are running? how often does it run? what order does it run in?
    - what are the names of the GameObjects or Components involved?
    - what are the values of the variables involved? Are they initialized? Are the values reasonable?
    - are you meeting ALL the requirements to receive callbacks such as triggers / colliders (review the documentation)

    Knowing this information will help you reason about the behavior you are seeing.

    You can also supply a second argument to Debug.Log() and when you click the message, it will highlight the object in scene, such as
    Debug.Log("Problem!",this);


    If your problem would benefit from in-scene or in-game visualization, Debug.DrawRay() or Debug.DrawLine() can help you visualize things like rays (used in raycasting) or distances.

    You can also call Debug.Break() to pause the Editor when certain interesting pieces of code run, and then study the scene manually, looking for all the parts, where they are, what scripts are on them, etc.

    You can also call GameObject.CreatePrimitive() to emplace debug-marker-ish objects in the scene at runtime.

    You could also just display various important quantities in UI Text elements to watch them change as you play the game.

    Visit Google for how to see console output from builds. If you are running a mobile device you can also view the console output. Google for how on your particular mobile target, such as this answer or iOS: https://forum.unity.com/threads/how-to-capturing-device-logs-on-ios.529920/ or this answer for Android: https://forum.unity.com/threads/how-to-capturing-device-logs-on-android.528680/

    If you are working in VR, it might be useful to make your on onscreen log output, or integrate one from the asset store, so you can see what is happening as you operate your software.

    Another useful approach is to temporarily strip out everything besides what is necessary to prove your issue. This can simplify and isolate compounding effects of other items in your scene or prefab.

    Here's an example of putting in a laser-focused Debug.Log() and how that can save you a TON of time wallowing around speculating what might be going wrong:

    https://forum.unity.com/threads/coroutine-missing-hint-and-error.1103197/#post-7100494

    "When in doubt, print it out!(tm)" - Kurt Dekker (and many others)

    Note: the
    print()
    function is an alias for Debug.Log() provided by the MonoBehaviour class.
     
  4. nebuchednezar

    nebuchednezar

    Joined:
    Feb 6, 2021
    Posts:
    31
    Ok, 2 dictionary variables are declared in the GameManager script:
    Code (CSharp):
    1. public static Dictionary<Tuple<int, int>, PickUp> Item = new Dictionary<Tuple<int, int>, PickUp>();
    2. public static Dictionary<Tuple<int, int>, List<GameObject>> Square = new Dictionary<Tuple<int, int>, List<GameObject>>();
    The Item dictionary is populated inside first for loop of the Spawn_FillGrid method which instantiates the cube prefabs:

    Code (CSharp):
    1. for(int x = 0; x < Row; x++)//Generate Horizontal  Vector 2 Axis
    2.         {
    3.             for(int y = 0; y < Column; y++)//Generate Vertical  Vector 2 Axis
    4.             {
    5.                 var clone = Instantiate(NormalCubePrefabs[UnityEngine.Random.Range(0, RandomColorCount)], new Vector2(x, y), Quaternion.identity);
    6.                 clone.transform.SetParent(SetItem);
    7.                 clone.AddComponent<CapsuleCollider2D>();
    8.                 clone.tag = "Item";
    9.                 clone.AddComponent<PickUp>();
    10.                 clone.name = x.ToString() + "." + y.ToString();
    11.                 clone.GetComponent<PickUp>().x = x;
    12.                 clone.GetComponent<PickUp>().y = y;
    13.                 Item.Add(new Tuple<int, int>(x, y), clone.GetComponent<PickUp>());
    14.                 clone.GetComponent<IDName>().IsRunChangeSprites = true;
    15.             }
    16.         }
    There's no problem when the cubes are firs instantiated, the problem starts when i destroy a couple of matched cubes.

    The Start_Square method handles the sprite changing mechanic. i.g if there's between 5 to 7 adjacent "normal" cubes of the same color their sprites are swapped with the rocket sprite. This line:
    Code (CSharp):
    1.  Square.Add(new Tuple<int, int>(item.x, item.y), new List<GameObject>());
    adds the not special cubes to Square dictionary. That dictionary is cleared at the end of the method.

    Inside the Calculate_CubeCallback and Continue_CalculateCubeCallback methods Item dictionary is used to check and add cubes to the current square.

    The Item collection is cleared at the bottom of the Delete_Cubes method.

    Then there's the Change script which is used to change the x,y coordinates of the the newly spawned cubes according to the fixed coordinates of the Background cubes. When a new cube touches the collider of the background cube, it gets it's x,y coordinates.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using System;
    5.  
    6. public class Change : MonoBehaviour
    7. {
    8.     public int x, y;
    9.  
    10.     private void OnTriggerEnter2D(Collider2D collision)
    11.     {
    12.         if (collision.gameObject.CompareTag("Item"))
    13.         {
    14.             collision.gameObject.name = x.ToString() + "." + y.ToString();
    15.             collision.gameObject.GetComponent<PickUp>().x = x;
    16.             collision.gameObject.GetComponent<PickUp>().y = y;
    17.             if(!GameManager.Item.ContainsKey(new Tuple<int, int>(x, y)))
    18.             GameManager.Item.Add(new Tuple<int, int>(x, y), collision.gameObject.GetComponent<PickUp>());
    19.         }
    20.     }
    21. }
    22.  
    This script checks if the static Item dictionary in the GameManager class contains a key with the same (x, y) tuple. If it doesn't contain the key, it adds a new entry to the dictionary with the (x, y) tuple as the key and the PickUp component as the value.

    Also inside the PickUp script Continue_CalculateCallBack(Tuple<int, int> id) calls the Calculate_CubeCallBack method on the GameManager instance, passing the PickUp component, the IDName component, and a tuple of integers (id) as parameters.

    So we have two dictionaries: "Item" and "Square". I tried to show where and for what purpose they are used. I hope i could explain it well
     
    Last edited: May 12, 2023
  5. nebuchednezar

    nebuchednezar

    Joined:
    Feb 6, 2021
    Posts:
    31
    I added two lines of debug code inside the PickUp script to see if there's a discrepency between x,y coordinates of the clicked cube and the keys inside the Item dictionary like this:

    Code (CSharp):
    1.  private void OnMouseDown()
    2.     {
    3.         Debug.Log($"Clicked Cube Coordinates: ({x}, {y})");
    4.         Debug.Log($"Cube Dictionary Value: {GameManager.Item[new Tuple<int, int>(x, y)]}");
    5.         if (!GameManager.IsClick)
    6.         {
    7.             if (GetComponent<IDName>().IsSpecialCube) // True ==> Special cube
    8.             {
    9.                 // TODO: Blank ==> Next time
    10.                 GameManager.IsClick = true;
    11.                 var i = GetComponent<IDName>();
    12.                 //Check Disco Ball Cube
    13.                 if(i.IsDiscoBall && !i.IsBomb && !i.IsSwitchHorizontal && !i.IsSwitchVertical)
    14.                 {
    15.                     FindObjectOfType<GameManager>().CalculateCubeForDiscoBall(i);
    16.                 }
    17.                 //Check Bomb Cube
    18.                 if (!i.IsDiscoBall && i.IsBomb && !i.IsSwitchHorizontal && !i.IsSwitchVertical)
    19.                 {
    20.                     FindObjectOfType<GameManager>().CalculateCubeForBomb(i);
    21.                 }
    22.                 //Check Switch Horizontal Cube
    23.                 if (!i.IsDiscoBall && !i.IsBomb && i.IsSwitchHorizontal && !i.IsSwitchVertical)
    24.                 {
    25.                     FindObjectOfType<GameManager>().CalculateCubeForSwitchHorizontal(i);
    26.                 }
    27.                 //Check Switch Vertical Cube
    28.                 if (!i.IsDiscoBall && !i.IsBomb && !i.IsSwitchHorizontal && i.IsSwitchVertical)
    29.                 {
    30.                     FindObjectOfType<GameManager>().CalculateCubeForSwitchVertical(i);
    31.                 }
    32.             }
    33.             else
    34.             {
    35.                 GameManager.Calculate_CubeCallBack(GetComponent<PickUp>(), GetComponent<IDName>());
    36.                 FindObjectOfType<GameManager>().Delete_Callback();
    37.                 GameManager.IsClick = true;
    38.             }
    39.         }
    40.     }
    In fact i started to see the KeyNotFound exception errors whenever i click on a cube which supposed to be destroyed but does not after i added those lines. I was seeing the KeyNotFound exception error only when i clicked on the horizontal rocket cube before. Is this a right way of debugging? Do you think i am on the right track here? I debugged other things in the code as well such as some bools, params and variables