Problem with dictionary (C#)

Hello,

I am actually trying to make a checkers game. I’m trying to create a board (10x10) that contains 100 tiles (public Dictionary<float, Tile> tiles = new Dictionary<float, Tile>();) of the class Tile. Here is my code to make them spawn :

void Awake () {

	for (int i = 0; i < 10; i++) {
							
		for (int j = 0; j < 10; j++) {

		        Tile tile = GetComponent<Tile> ();
			id = id + 1;
			tile.id = id;
			tile.coordinates = new Vector2(i, j);
			tile.piece = default(Piece);
			tiles[id] = tile;

		}

	}
		
}

But the problem I have is that when i want to read this dictionary :

foreach (KeyValuePair<float, Tile> entry in boardComponent.tiles) {
			
	GameObject tileObject = new GameObject("Tile" + entry.Key);
	tileObject.transform.parent = GameObject.Find ("Board").transform;
	tileObject.AddComponent<Tile>();
	Tile tile = GameObject.Find("Tile" + entry.Key).GetComponent<Tile> ();
	tile.coordinates = entry.Value.coordinates;

}

All my tiles coordinates are Vector2(0,0) and I don’t understand why. When i Debug.Log(tile.coordinates); in the for loop, I’m getting right values, but when i push them to the dictionary, it seems like they are loss. What can i do to keep those coordinates ?

Thank you.

Why are you using float as your lookup Key? Why not int?

(Extra info: floats being compared for equality are notoriously inaccurate. 1+1 does not necessarily equal 2 in float math; it may easily equal 1.99999 or 2.000001. If you’re using floats as they’re expected to be used - as smooth, continuous “number lines” - this isn’t really a problem. However, using them for anything that requires particular, precise values is a mistake. This is why representing $1.23 as a float containing 1.23 is usually a bad idea; represent it as an int (123 cents) instead.

I can’t think of a case when using a float as a Key in a dictionary would be the right thing to do, and this one most of all, since you’re just using whole numbers.)

1 Like

Setting Tile.coordinates does not modify Tile.transform.position - hence their staying at (0, 0).

1 Like

Exactly, this is because floats are approximations.

Thank you for your answers, i changed the type of my id to int. Blizzy, to answer you, when i say their coordinates are Vector2(0,0), i mean the Tile.coordinates, not Tile.transform.position, off course :slight_smile:

Still have the problem?

Another thing which, depending on your Scene, might be your issue: Why are you using GameObject.Find when you already have a reference to the GameObject you want? Use tileObject.GetComponent instead. (It’s possible that you’re finding a different object that happens to share a name, for example, which may explain your bug) Whether it’s causing your problem or not, it’s slow and completely unnecessary.

Or, better yet: AddComponent returns the component that was just added.

Tile tile = tileObject.AddComponent<Tile>();

One thing that is more likely to be your root problem: the reference in your dictionary’s Value is very likely to be null. I’m guessing that, in between saving your Dictionary and loading it, you are destroying all the GameObjects. Since your dictionary refers to the components on the GameObject itself (e.g. Tile), when you go to load it, this doesn’t exist.

I would expect this to give you a NullReferenceException, however. But I have an explanation for why it might not be: normally, when you compare a reference to a UnityEngine.Object (including GameObject, MonoBehaviour, etc) to null, Unity has overridden that comparison operator, and it checks for whether the object is null, OR whether is has been destroyed - that is, if you have a reference to a Component whose GameObject was destroyed last frame and compare it to null, it will tell you it’s null (even though it technically isn’t). These are not quite the same thing, but in 99.9% of cases, you’ll never notice the difference.

HOWEVER, there are rare cases - and Dictionary may be one of those cases - where the difference between ‘null’ and ‘destroyed’ is significant. It may be re-initializing all of the script’s variables when the object was destroyed, but it can’t reach into your Dictionary and make all those pointers actually “null”. So, you have a useless, junk reference to an object that sort-of exists.

ANYWAY, that’s long and technical, but here’s the upshot: Instead of storing your tile data in the monobehaviour itself, store it in your own class, and have Tile have one of those as a member.

public class TileData {
public Vector2 coordinates;
//and so on
}
....
//in Tile.cs
TileData data;
void Awake() {
data = new TileData();
}
....
// in your quoted foreach loop
Tile tile = tileObject.AddComponent<Tile>();
tile.data = entry.Value;

I'm seeing yet another problem as I write this: in your Awake() as pasted, you GetComponent<Tile>() on the object where this thing is running. You're finding the EXACT SAME Tile reference over and over, and adding it to your dictionary over and over!

Thank you for this big answer but I don’t understand everything, I’m new to Unity :slight_smile: Basically, to find the solution to this problem, we should debug the dictionary but we actually can’t. And I basically too don’t understand why when I store the coordinates in the tile object and then debug.log it, it shows the right coordinate, but when i add it to my dictionary and then try to debug, it shows Vector2(0,0) and I think THIS is the root of my problem.

EDIT : I’m sorry but I don’t understand why you’re talking about NullReferenceException and destroyed, I have no error on my script and I don’t destroy any GameObject. And I too don’t understand why I am getting the exact same Tile, i made an id for that !

If you don’t destroy any objects, then you must be creating a duplicate board?

Look at line 8 of your first code block up there. You just call GetComponent(). This doesn’t have an id, or anything - it just finds a Tile object that’s attached to the same object as this script. you go through, change a bunch of stuff, increment id, and then the loop begins again, and you call GetComponent()… which doesn’t know about any of the stuff you just did, it just dutifully gives you a Tile object that’s attached to the object the script is running on (the same Tile it got you before), and now you’re just overwriting all the stuff you’d set in the last loop.

So, a single Tile object has its id set to 1, then to 2, then to 3…

I have one board in my scene that is created with this Script :

GameObject boardObject = new GameObject("Board");
boardObject.transform.parent = transform;
board = boardObject.AddComponent<Board>();
Board boardComponent = GameObject.Find ("Board").GetComponent<Board> ();

This code is placed just above the foreach. To help you resolving my problem, I’m going to tell you the structure I use :

  • Checkers.cs - It’s the board manager. Here is the code that is inside :
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

[RequireComponent(typeof(Tile))]
[RequireComponent(typeof(Board))]

public class Checkers : MonoBehaviour {
   
    public Board board; 
   
    void Awake() {

        GameObject boardObject = new GameObject("Board");
        boardObject.transform.parent = transform;
        board = boardObject.AddComponent<Board>();
        Board boardComponent = GameObject.Find ("Board").GetComponent<Board> ();

        foreach (KeyValuePair<int, Tile> entry in boardComponent.tiles) {
           
            GameObject tileObject = new GameObject("Tile" + entry.Key);
            tileObject.transform.parent = GameObject.Find ("Board").transform;
            Tile tile = tileObject.AddComponent<Tile>();
            tile.coordinates = entry.Value.coordinates;

        }

    }
   
}
  • Board.cs - It’s the code for initialising the board and some other functions (not finished yet) :
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

[RequireComponent(typeof(Tile))]

public class Board : MonoBehaviour {
   
    public Dictionary<int, Tile> tiles = new Dictionary<int, Tile>();
    public string test = "Hello";
    private int id = 0;
   
    void Awake () {

        for (int i = 0; i < 10; i++) {
                           
            for (int j = 0; j < 10; j++) {

                Tile tile = GetComponent<Tile> ();
                id = id + 1;
                tile.id = id;
                tile.coordinates = new Vector2(i, j);
                tile.piece = default(Piece);
                tiles[id] = tile;

            }

        }
       
    }
   
    public bool isTileOccupated(Tile tile) {
       
        if (tile.piece) {
           
            return true;
           
        } else {
           
            return false;
           
        }
       
    }
   
}
  • Tile.cs - The code for the Tile class :
using UnityEngine;
using System.Collections;
using System;

public class Tile : MonoBehaviour{

    public int id;
    public Vector2 coordinates;
    public Piece piece;
   
}
  • Piece.cs - The code for the Piece class (actally empty) :
using UnityEngine;
using System.Collections;

public class Piece : MonoBehaviour {

}

Those 4 scripts are attached to an empty GameObject called “Checkers”.
That’s all for my structure !

And to answer you, when i use the GetComponent, for me it’s instead of new Tile(args…), I’m just instanciating my class to create a new Tile.

GetComponent doesn’t instantiate.

I don’t understand your object structure here - why do you have a Tile on the same object as your board? Shouldn’t you instead have a bunch of Tile objects, attached to GameObjects that are children of the Board?

So how do i instanciate a monobehaviour class ?

I do but to make my script work, i was forced to have the script tile on my board.

And i just seen an interesting thing, when i click on my board, on the script of the board, there is an id, 100.

I think this is your problem - you seem to be misunderstanding something about this. Remove the Tile from your board object - conceptually, it shouldn’t be there, right? Presumably, any errors you get at that point aren’t just annoyances - they would be indicators that you’ve done something wrong. Adding a Tile to this object was like putting a band-aid over a tumor. You can’t see the problem anymore, but you didn’t actually fix anything.

Your Checkers script is actually instantiating objects (via “new GameObject” and “.AddComponent”), so you do know how to do that. I think your solution here is to merge these loops - get rid of foreach, put the stuff that’s inside your foreach in your “i and j” nested loop instead. And instead of your GetComponent in your i/j loop, use the object that you created with AddComponent.

Yep. And if you were able to stop that at different points in the middle of the frame, you’d see it count oen by one all the way up to 100.

Ok i think I know. I’m just spawning the Tiles in my Board.cs file. I don’t respect my structure by doing that. This code should be in the Checkers.cs file and in the Checkers empty GameObject, that’s normal that I have every scripts.