简体   繁体   中英

How do I find key through value with C# dictionary in Unity when value is an Object

I have spent days trying to find a way to access the square my chess pieces are on for a game I am making in Unity. What I want to do is click on a chess piece and have its square returned to me. I am currently trying to do this by getting the coordinates of the square that I click on and checking the value in a dictionary. Every time, however, it returns null. My classes are as follows:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;

public class Game : MonoBehaviour
{
    public GameObject white_king, white_queen, white_rook, white_bishop, white_knight, white_pawn;
    public GameObject black_king, black_queen, black_rook, black_bishop, black_knight, black_pawn;
    public static bool whiteMove;
    public static bool blackMove;


    public string[] squareNames = {"a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8",
    "b1", "b2", "b3", "b4", "b5", "b6", "b7", "b8",
    "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8",
    "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8",
    "e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8",
    "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8",
    "g1", "g2", "g3", "g4", "g5", "g6", "g7", "g8",
    "h1", "h2", "h3", "h4", "h5", "h6", "h7", "h8"};

    public static Dictionary<string, Coordinates> squares = new Dictionary<string, Coordinates>();
    public static Dictionary<Coordinates, string> square_references = new Dictionary<Coordinates, string>();

    private double x = -3.94;
    private double y = -3.92;

    public void Start()
    {
      whiteMove = true;
      blackMove = false;
        for (int i = 0; i < 64; i++)
        {
        if (i % 8 == 0 && i > 0)
        {
            y = -3.94;
            x += 1.1;
        }
        squares.Add(squareNames[i], new Coordinates(x, y));
        square_references.Add(new Coordinates(x, y), squareNames[i]);
        y += 1.1;

        }

        Instantiate(white_queen, new Vector3((float) squares["d1"].getX(), (float) squares["d1"].getY(), -2), Quaternion.identity);
        Instantiate(white_king, new Vector3((float) squares["e1"].getX(), (float) squares["e1"].getY(), -2), Quaternion.identity);
        Instantiate(white_rook, new Vector3((float) squares["a1"].getX(), (float) squares["a1"].getY(), -2), Quaternion.identity);
        Instantiate(white_rook, new Vector3((float) squares["h1"].getX(), (float) squares["h1"].getY(), -2), Quaternion.identity);
        Instantiate(white_bishop, new Vector3((float) squares["c1"].getX(), (float) squares["c1"].getY(), -2), Quaternion.identity);
        Instantiate(white_bishop, new Vector3((float) squares["f1"].getX(), (float) squares["f1"].getY(), -2), Quaternion.identity);
        Instantiate(white_knight, new Vector3((float) squares["b1"].getX(), (float) squares["b1"].getY(), -2), Quaternion.identity);
        Instantiate(white_knight, new Vector3((float) squares["g1"].getX(), (float) squares["g1"].getY(), -2), Quaternion.identity);
        Instantiate(white_pawn, new Vector3((float) squares["a2"].getX(), (float) squares["a2"].getY(), -2), Quaternion.identity);
        Instantiate(white_pawn, new Vector3((float) squares["b2"].getX(), (float) squares["b2"].getY(), -2), Quaternion.identity);
        Instantiate(white_pawn, new Vector3((float) squares["c2"].getX(), (float) squares["c2"].getY(), -2), Quaternion.identity);
        Instantiate(white_pawn, new Vector3((float) squares["d2"].getX(), (float) squares["d2"].getY(), -2), Quaternion.identity);
        Instantiate(white_pawn, new Vector3((float) squares["e2"].getX(), (float) squares["e2"].getY(), -2), Quaternion.identity);
        Instantiate(white_pawn, new Vector3((float) squares["f2"].getX(), (float) squares["f2"].getY(), -2), Quaternion.identity);
        Instantiate(white_pawn, new Vector3((float) squares["g2"].getX(), (float) squares["g2"].getY(), -2), Quaternion.identity);
        Instantiate(white_pawn, new Vector3((float) squares["h2"].getX(), (float) squares["h2"].getY(), -2), Quaternion.identity);
        Instantiate(black_king, new Vector3((float) squares["d8"].getX(), (float) squares["d8"].getY(), -2), Quaternion.identity);
        Instantiate(black_queen, new Vector3((float) squares["e8"].getX(), (float) squares["e8"].getY(), -2), Quaternion.identity);
        Instantiate(black_rook, new Vector3((float) squares["a8"].getX(), (float) squares["a8"].getY(), -2), Quaternion.identity);
        Instantiate(black_rook, new Vector3((float) squares["h8"].getX(), (float) squares["h8"].getY(), -2), Quaternion.identity);
        Instantiate(black_bishop, new Vector3((float) squares["c8"].getX(), (float) squares["c8"].getY(), -2), Quaternion.identity);
        Instantiate(black_bishop, new Vector3((float) squares["f8"].getX(), (float) squares["f8"].getY(), -2), Quaternion.identity);
        Instantiate(black_knight, new Vector3((float) squares["b8"].getX(), (float) squares["b8"].getY(), -2), Quaternion.identity);
        Instantiate(black_knight, new Vector3((float) squares["g8"].getX(), (float) squares["g8"].getY(), -2), Quaternion.identity);
        Instantiate(black_pawn, new Vector3((float) squares["a7"].getX(), (float) squares["a7"].getY(), -2), Quaternion.identity);
        Instantiate(black_pawn, new Vector3((float) squares["b7"].getX(), (float) squares["b7"].getY(), -2), Quaternion.identity);
        Instantiate(black_pawn, new Vector3((float) squares["c7"].getX(), (float) squares["c7"].getY(), -2), Quaternion.identity);
        Instantiate(black_pawn, new Vector3((float) squares["d7"].getX(), (float) squares["d7"].getY(), -2), Quaternion.identity);
        Instantiate(black_pawn, new Vector3((float) squares["e7"].getX(), (float) squares["e7"].getY(), -2), Quaternion.identity);
        Instantiate(black_pawn, new Vector3((float) squares["f7"].getX(), (float) squares["f7"].getY(), -2), Quaternion.identity);
        Instantiate(black_pawn, new Vector3((float) squares["g7"].getX(), (float) squares["g7"].getY(), -2), Quaternion.identity);
        Instantiate(black_pawn, new Vector3((float) squares["h7"].getX(), (float) squares["h7"].getY(), -2), Quaternion.identity);
       
    }





}

public class Coordinates
{
  public double x;
  public double y;

  public double getX()
  {
    return this.x;
  }

  public double getY()
  {
    return this.y;
  }

  public Coordinates(double x, double y)
  {
    this.x = x;
    this.y = y;
  }



}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using System.Linq;

public class Piece : MonoBehaviour
{
    public GameObject moveSquare;
    private bool isDragging;
    Vector2 startPosition;
    string square; 

    void Start()
    {
        startPosition = transform.position;
    }

    private void OnMouseDown()
    {
        if((getColour().Equals("white") && Game.whiteMove) || ((getColour().Equals("black") && !Game.whiteMove)))
        {
            isDragging = true;
            CreateMoveSquare("f5");
            Debug.Log(getSquare());
        
        }
        
    }

    private void OnMouseUp()
    {
        if((getColour().Equals("white") && Game.whiteMove) || ((getColour().Equals("black") && Game.blackMove)))
        {
                isDragging = false;
                DestroyMoveSquares();
                
            if(getColour().Equals("white"))
            {
                Game.whiteMove = false;
                Game.blackMove = true;
            }
            else
            {
                Game.whiteMove = true;
                Game.blackMove = false;
            
            }
        }
        
        
        
    }

    void Update()
    {
        if(isDragging)
        {
            Vector2 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition) - transform.position;
            transform.Translate(mousePosition);
        }
    }

    private string getColour()
    {
        
        switch (this.name)
        {
            case "black_queen 1(Clone)": return "black";
            case "black_knight(Clone)": return "black"; 
            case "black_bishop(Clone)": return "black"; 
            case "black_king(Clone)": return "black"; 
            case "black_rook(Clone)": return "black"; 
            case "black_pawn(Clone)": return "black"; 
        }

        return "white";


    }

    private string getSquare()
    {
        string myKey = Game.squares.FirstOrDefault(x => x.Value.x == (double) transform.position.x && x.Value.y == (double) transform.position.y).Key;
        return myKey;           
    }

    public void DestroyMoveSquares()
    {

        GameObject[] movePlates = GameObject.FindGameObjectsWithTag("MoveSquare");
        for (int i = 0; i < movePlates.Length; i++)
        {
            Destroy(movePlates[i]); 
        }
    }

    public void CreateMoveSquare(string square)
    {
        GameObject ms = Instantiate(moveSquare, new Vector3((float) Game.squares[square].getX(), (float) Game.squares[square].getY(), -3), Quaternion.identity);
    }

    


}

In this instance Debug.Log(getSquare()) gives me null no matter which pieces I click on. I have tried using square_references to get the the key through mykey = Game.square_refenreces[new Coordinates[transform.position.x, transform.position.y] but it also returns null. I have tried many other things but nothing seems to work. Does anyone know why or what I can do to make this work?

Issue A - Floating point precision

In general never use == for comparing floating point formats like double or float or decimal . Something like if(5 * 0.2 / 10 == 1f) might fail due to floating point precision and might actually be 0.99999999 or 1.000000001 !

Usually you would rather use an approximation like if(Math.Abs(ab) <= double.Epsilon) where double.Epsilon is basically the smallest value a double can differ from 0 .

Issue B - Reference equality for Dictionary

Further, if a type does not override GetHashCode then by default for reference types Dictionary uses reference equality on the given keys.

You do

squares.Add(squareNames[i], new Coordinates(x, y));
square_references.Add(new Coordinates(x, y), squareNames[i]);

where you generate two different instances of Coordinates !

And as you said you also tried then to do

mykey = Game.square_refenreces[new Coordinates(transform.position.x, transform.position.y)];

where again you create a new instance . This new instance and the two ones in the dictionary are not reference equal so the dictionary can not find the given key.


(Preferred) Solution: Simply Use Vector2!

However, looking at you values... do you really need double at all? Why not using float since transform.position (and basically everything else in Unity anyway only uses float )?

And then in your use case for actual coordinates why not simply use Vector2 which already provides such an approximation (even better): Vector2 == has an precision of 0.00001 .

And additionally it also already implements GetHashCode as you can see in the source code

 // used to allow Vector2s to be used as keys in hash tables public override int GetHashCode() { return x.GetHashCode() ^ (y.GetHashCode() << 2); })

which directly allows you to use it as a key in maps (= Dirctionary)!

And finally there already exists an implicit conversion forth and back between Vector2 and Vector3 so you can directly use the return value for the instantiate and the current position as the key.

So you could simply do

public static Dictionary<string, Vector2> squares = new Dictionary<string, Vector2>();
public static Dictionary<Vector2, string> square_references = new Dictionary<Vector2, string>();

private float x = -3.94f;
private float y = -3.92f;

public void Start()
{
    whiteMove = true;
    blackMove = false;
    for (int i = 0; i < 64; i++)
    {
        if (i % 8 == 0 && i > 0)
        {
            y = -3.94f;
            x += 1.1f;
        }

        squares.Add(squareNames[i], new Vectro2(x, y));
        square_references.Add(new Vectro2(x, y), squareNames[i]);
        y += 1.1f;
    }

    Instantiate(white_queen, squares["d1"] - Vector3.forward * 2), Quaternion.identity);
    ....
}
   

and then use eg

private string getSquare()
{
    // As said where == uses approximation with a precision of 0.00001
    string myKey = Game.squares.FirstOrDefault(x=> x.Value == transform.position).Key;
    return myKey;  
} 

or simply directly since internally Vector2 already implements mentioned GetHashCode which the dictionary will use to define equality between given key and the existing keys.

private string getSquare()
{
    string myKey = Game.square_references[transform.position];
    return myKey;  
} 

(Alternative) Solution - Use IEquatable/Custom == operator or GetHashCode

So if for some reason you want to stick to your custom type I would make it implement mentioned IEquatable and GetHashcode like

public class Coordinates
{
    public double x;
    public double y;

    public double getX()
    {
        return this.x;
    }

    public double getY()
    {
        return this.y;
    }

    public Coordinates(double x, double y)
    {
        this.x = x;
        this.y = y;
    }
    
    public static bool operator  == (Coordinates a, Coordinates b)
    {
        return Math.Abs(a.x - b.x) <= double.Epsilon && Math.Abs(a.y - b.y) <= double.Epsilon;
    }

    public static bool operator !=(Coordinates a, Coordinates b)
    {
        return !(a == b);
    }

    public bool Equals(Coordinates other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;

        return this == other;
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != this.GetType()) return false;

        return Equals((Coordinates) obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            return (x.GetHashCode() * 397) ^ y.GetHashCode();
        }
    }
}

and now you can either use

private string getSquare()
{
    string myKey = Game.squares.FirstOrDefault(x => x.Value == new Coordinates(transform.position.x,transform.position.y)).Key;
    // or also
    //string myKey = Game.squares.FirstOrDefault(x => x.Value.Equals(new Coordinates(transform.position.x,transform.position.y))).Key;
    return myKey;           
}

or directly use

private string getSquare()
{
    string myKey = Game.square_references[new Coordinates(transform.position.x,transform.position.y)];
    return myKey;           
}

Note though that I'm pretty sure that the GethashCode way would most probably still fail due to floating point precision.

Before diving through your code which is quite long at a first glance I'll make a couple of comments in case they're helpfull. There are casts in your code from double to float and viceversa. Carefull with those, and the equality comparer == as casting to a lower precision type might make inccur in precision loss, and that might give problem finding back a previously kept value. You can check this for further understanding. From there "Calculated values that follow different code paths and that are manipulated in different ways often prove to be unequal" with examples. So even if there is no casting involved, comparing floats is tricky.

Another comment I can make is that to find retrieve your values, if I was not familiar/sure of Linq, I would try to find my value with a for loop, and debug the looped through data structure. Debugging you can check "manually" if your data is ther and you can narrow down the problem to check if the problem is that the data is not in the data structure to look in, or if there is a mismatch in the criteria (testing for equality). After that problem is solved, I would go and try to have the neat linq line that provides what I want directly. Meaning that it might be useful to slice down your problem into smaller ones:)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM