首页 > 解决方案 > 当值是对象时,如何在 Unity 中使用 C# 字典通过值查找键

问题描述

我花了几天的时间试图找到一种方法来访问我在 Unity 中制作的游戏的棋子所在的方格。我想要做的是点击一个棋子并将它的方格返回给我。我目前正在尝试通过获取我单击的正方形的坐标并检查字典中的值来做到这一点。但是,每次它都返回 null。我的课程如下:

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);
    }

    


}

在这种情况下 Debug.Log(getSquare()) 给我 null 无论我点击哪个部分。我尝试使用 square_references 通过 mykey = Game.square_refenreces[new Coordinates[transform.position.x, transform.position.y] 获取密钥,但它也返回 null。我尝试了很多其他的东西,但似乎没有任何效果。有谁知道为什么或我能做些什么来完成这项工作?

标签: c#unity3d

解决方案


问题 A - 浮点精度

通常从不用于==比较浮点格式,如doubleorfloatdecimalif(5 * 0.2 / 10 == 1f)由于浮点精度,类似的东西可能会失败,实际上可能是0.99999999or 1.000000001

通常你宁愿使用一个近似值,比如if(Math.Abs(a-b) <= double.Epsilon)wheredouble.Epsilon基本上是 double 可以不同于的最小值0

问题 B - 字典的引用相等

此外,如果类型不覆盖GetHashCode则默认情况下,引用类型在给定键上Dictionary使用引用相等。

你做

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

您在其中生成两个不同的实例Coordinates

正如你所说,你当时也尝试过

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

你又在哪里创建一个新实例。这个新实例和字典中的两个实例的引用不相等,因此字典找不到给定的键。


(首选)解决方案:只需使用 Vector2!

然而,看看你的价值观……你真的需要double吗?为什么不使用floatsince transform.position(基本上 Unity 中的其他所有东西都只使用float)?

然后在您的实际坐标用例中,为什么不简单地使用Vector2它已经提供了这样的近似值(甚至更好):Vector2 ==精度为0.00001.

此外,它也已经实现,如您在源代码GetHashCode中所见

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

这直接允许您将其用作地图中的键(= 目录)!

最后,在 and 之间已经存在隐式转换Vector2Vector3因此您可以直接使用实例化的返回值和当前位置作为键。

所以你可以简单地做

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);
    ....
}
   

然后使用例如

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;  
} 

或者直接直接因为 Vector2 内部已经实现了提到GetHashCode的字典将使用它来定义给定键和现有键之间的相等性。

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

(替代)解决方案 - 使用 IEquatable/Custom == 运算符或 GetHashCode

因此,如果由于某种原因你想坚持你的自定义类型,我会让它实现提到IEquatableGetHashcode喜欢

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();
        }
    }
}

现在你可以使用

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;           
}

或直接使用

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

请注意,尽管我很确定GethashCode由于浮点精度,这种方式很可能仍然会失败。


推荐阅读