0

I have a Dictionary containing positions that correspond to block values that I am using to get the current blocks the player is over (as an alternative to having each individual block have its own BoxCollider2D and raycasting, which may or may not be better, I'm not sure)

In the Update method, I check for a specific blocks position, to which it returns true.

void Update()
{
    if (BlocksDictionary.ContainsKey(new Vector2(-2.56f, 6.4f)))
        print("Value exists");
}

However, in the GetBlocksAtPosition method, when trying to check for the same position, it returns false.

public Dictionary<Block, SpriteRenderer> GetBlocksAtPosition(float x, float y, bool gridifyCoords = false)
{
    float newX = x;
    float newY = y;
    if (gridifyCoords)
    {
        Vector2 gridCoords = new Vector2(Mathf.Round(x / 1.28f) * 1.28f, Mathf.Round(y / 1.28f) *            1.28f);
        newX = gridCoords.x;
        newY = gridCoords.y;
    }

    Vector2 blockCoords = new Vector2(newX, newY);
    if (BlocksDictionary.ContainsKey(blockCoords))
        return BlocksDictionary[blockCoords];
    else
    {
        print("Blocks not found at: " + newX + " - " + newY);
        return null;
    }
}

And here's how I am adding blocks to the dictionary.

if (BlocksDictionary.ContainsKey(blockPos))
{
    if (!BlocksDictionary[blockPos].ContainsKey(blockSO))
        BlocksDictionary[blockPos].Add(blockSO, spriteRenderer);
}
else
{
    Dictionary<Block, SpriteRenderer> newBlockDict = new Dictionary<Block, SpriteRenderer>();
    newBlockDict.Add(blockSO, spriteRenderer);
    BlocksDictionary.Add(blockPos, newBlockDict);
}

It's pretty clear to me that the position value in the dictionary exists, so I don't understand how the GetBlocksAtPosition method doesn't find them.

The only things I've really tried was print statements, since I have no other ideas as to what could be happening.

brenman60
  • 3
  • 1
  • 5
    Calculated float values might have rounding errors like `6.399999f` instead of `6.4f`. – Olivier Jacot-Descombes Aug 28 '23 at 07:41
  • You might want to implement a custom `EqualityComparer` to use in the dictionary to make sure that Vectors comparison is correct. The default comparison uses `.Equals` method while you might want to use `==` operator which is overriden by Unity's `VectorX` types to provide approximate comparisons (to avoid abovementioned rounding errors). – bashis Aug 28 '23 at 07:50
  • You could also store your block position keys as [Vector2Int](https://docs.unity3d.com/ScriptReference/Vector2Int.html) and use the [RoundToInt](https://docs.unity3d.com/ScriptReference/Vector2Int.html) method to convert your Vector2 before lookup. – Voidsay Aug 28 '23 at 07:54
  • 1
    Definitely caused by rounding errors. Try this: `float f = float.Round(6.4f / 1.28f) * 1.28f; Console.WriteLine(f); Console.WriteLine(f == 6.4f);` – Matthew Watson Aug 28 '23 at 08:17
  • For vectors, using a specialized collection like KD-Tree is probably more appropriate. E.g., see: https://github.com/OJacot-Descombes/CySoft.Collections (contains my port to C# of a Java KD-Tree implementation). – Olivier Jacot-Descombes Aug 28 '23 at 08:47

1 Answers1

2

See Is floating point math broken?: As was mentioned by others float might (will) have rounding errors!

You could rather do something like e.g.

private Dictionary<Vector2Int, Dictionary<Block, SpriteRenderer>> BlocksDictionary;

and then e.g.

Vector2 coords = new Vector2(x, y);

if (gridifyCoords)
{
    coords.x = Mathf.Round(x / 1.28f) * 1.28f
    coords.y = Mathf.Round(y / 1.28f) * 1.28f;
}

coords *= 100f;

var blockCoords = Vector2Int.RoundToInt(coords);

if (BlocksDictionary.TryGetValue(blockCoords, out var dictionary))
{
    return dictionary;
}

Debug.LogWarning("Blocks not found at: " + x + " - " + y);
return null;

and accordingly just make sure the same factor (100) is also applied to you other operations

var blockCoords = Vector2Int.RoundToInt(blockPos * 100);

if(!BlocksDictionary.TryGetValue(blockCoords, out var dictionary))
{
    dictionary = new Dictionary<Block, SpriteRenderer>();
    BlocksDictionary[blockCoords] = dictionary;
}

dictionary[blockSO] = spriteRenderer;

and then also in

void Update()
{
    if (BlocksDictionary.ContainsKey(Vector2Int.RoundToInt(new Vector2(-2.56f, 6.4f) * 100)))
        print("Value exists");
}

=> You might want to move this to a method to be less error prone (single point of failure)

public static class Vector2Extensions
{
    private const float precisionFix = 100f;

    public static Vector2Int ToBlocksIndexer(this Vector2 position)
    {
        return Vector2Int.RoundToInt(position * precisionFix);
    }

    // optional if needed at all
    public static Vector2 ToPosition(this Vecto2Int index)
    {
        return new Vector2(index.x, index.y) / precisionFix;
    }
}

and adjust the factor according to your needs (depending on your required precision) - from your provided numbers 100 seems to be enough.

This way you can everywhere just use e.g.

if (BlocksDictionary.ContainsKey(new Vector2(-2.56f, 6.4f).ToBlocksIndexer())

See also

derHugo
  • 83,094
  • 9
  • 75
  • 115