Other ways to store tiles array

I already have something working to store runtime, player-placed tiles into a multidimensional array.
It works by comparing each tiles world position and translate it to a integer 3D grid system.

// 3 tiered for loops

int posX = (int)((tilePos.x - (gridCellDimension.x / 2) + width / 2)
/ gridCellDimension.x);
int posY = (int)((tilePos.y - (gridCellDimension.y / 2) + height / 2)
/ gridCellDimension.y);
int posZ = (int)((tilePos.z - (gridCellDimension.z / 2) + depth / 2)
/ gridCellDimension.z);

if (posX == a && posY == b && posZ == c)
{
   TILES_DATA_POSITION[a][b][c] = 1;
    // tile exists at this coord
}
else
{
   // tile doesn't exist here
}

Tiles generation also follow this technique of translating between world coordinates and grid.
Is this a commonly used approach, or are there more efficient and clean way of doing it?

Smaller stuff 50 * 1 * 50, works for sure. But a 100 * 100 * 100 grid might already be an overkill? (I haven’t done any tests)
Maybe I should just store the transform position and rotation as is in a json file?

Just thought I should’ve a few inputs before I code to a point of no return.

i dont know a hole alot about tile generation since i have not used it yet. but from what i can read your code seems fine? theres allways ways to optimize code. and its all about checking how other people do it. or litterly do different ways of storing, fetching data etc. and see how fast each one is? might be alot of manual work but that way you can find out which ways are best

The code is working, it’s more towards the implementation. I did thought about using raycast, but is there really an advantage? As rays might’ve been blocked by other objects, etc etc.

I wasn’t sure whether this is the right sub forum to post to, either here or game design.

well RayCast are super expensive. to use.

im not sure if this is what you are asking for but one of my friends did something like this to solve his problem once maps got larger. I’m not sure how to do it either you would have to look it up. but what he did was when 4 tiles were generated together in a square he bundled them together to make one larger square and saving that on square instead of 4. it can be scaled up further to 4 of the larger squares and so on then broken back down when and if needed.

I have used two- or three-dimensional arrays in the past, but at some point I started using A dictionary instead which takes the coordinates as key and returns the Tile, Cell, block or whatever class instance. Since a normal dictionary only accepts one variable as the key, I just wrote a simple Coordinate class that is basically like a Vector2 or Vector3, but with ints instead of floats. The main advantage is that the dimensions or your world are unlimited sicne you can always add a new KeyValuePair to the dictionary with any coordinate, even negative ones. Arrays can’t be easily resized and also don’t take negative numbers as an index, you’d have to use some kind of offset system for that. It also helps with saving & loading data since the file size for a map file will potentially be a lot smaller since all those cell or tile positions which are empty (“air”) don’t exist in the dictionary, but they do in the array (even if they are null).

public class Coordinate2 {
     public int x;
     public int y;
}

public class Coordinate3 {
     public int x;
     public int y;
     public int z;
}

public class Tile {
     public string textureName;
}

public class Cell {
     public string textureName;
}

public Dictionary<Coordinate2,Tile> tiles;
public Dictionary<Coordinate3,Cell> cells;

By the way, the Coordinate2 class in particular is very useful for all sorts of things, like texture tile coordinate in a tile atlas.

1 Like

I’m thinking of something similar for optimization, combining a group of objects/meshes to reduce drawcalls. Close, but not quite what I’m searching for currently.

This seems like a much better way of doing it. I’ll give it a spin.

This should all be properly encapsulated in its own class. All your external code should ever see is the getter and setter. The internal guts of the system should never be exposed. That way you can change the system internals as you like, without affecting external code.

A lot depends on characteristics of your system. Specifically

  • Is the size of the tile grid known in advance?
  • Is the grid sparsely or densely populated?
  • Does the grid need to be saved or loaded?
  • How big can the grid get?
  • What do you need to do with the data?
  • Grid size can be changed on the fly and regenerated

  • Most probably densely populated (player creativity and all)

  • Grid can be saved & loaded

  • No hard cap for dimensions at the moment

  • For now data is just for saving & loading

I’m using this as a world & room editor. Here’s a peek of what I currently have:
3156459--240074--upload_2017-7-24_7-46-59.png
(Blue grid is for tiles, red is for objects)

A room will have a smaller cell size, a 0.25 to 0.50 unit and a cap of around 50 cell counts per x, y, z. A room might be more dense than the world map.

I’m making to be this to be a more flexible system. For now I’m using it just as a map editor in-game. But I can also see of putting this in a city-building game in the future.

I would honestly just use an array until such point as you encounter problems with it. Wrap it decently, and you’ll be able to change the implementation with none of your other code being any wiser.

@Cherno do you use a custom comparer for the dictionary? I have a weird problem of duplicate keys in certain cases.

class CustomComparer : IEqualityComparer<Coordinate3>
        {
            public bool Equals(Coordinate3 first, Coordinate3 second)
            {
                bool x = first.x == second.x;
                bool y = first.y == second.y;
                bool z = first.z == second.z;

                return x & y & z;
            }

            public int GetHashCode(Coordinate3 obj)
            {
                return string.Format("{0}-{1}-{2}", obj.x, obj.y, obj.z).GetHashCode();
            }
        }
if (!cell.ContainsKey(coordinate3))
{
   cell.Add(coordinate3, tiles);
}

If I don’t use cell.Clear() somewhere before I call ContainsKey(coordinate3), the cell count will be different (larger) than the total number of tiles.

My GC alloc is 3.2mb(!) for 400 tiles only because of the GetHashCode.
2500 and its a whooping 90mb!

Any tips?

The full code:

[System.Serializable]
public struct Coordinate3 : IEquatable<Coordinate3>, ICloneable {
    public int x;
    public int y;
    public int z;

    /*
    public Coordinate3() {
        x = 0;
        y = 0;
        z = 0;
    }
    */

    public Coordinate3(int a, int b, int c) {
        x = a;
        y = b;
        z = c;
    }

    public Coordinate3(Vector3 v3) {
        x = Mathf.RoundToInt(v3.x);
        y = Mathf.RoundToInt(v3.y);
        z = Mathf.RoundToInt(v3.z);
    }

    public bool Equals(Coordinate3 other) {

        if (this.x == other.x && this.y == other.y && this.z == other.z) {
            return true;
        }
        else {
            return false;
        }
    }

    public override int GetHashCode() {
        return x.GetHashCode() ^ y.GetHashCode() ^ z.GetHashCode();
    }

    public object Clone() {
        return (Coordinate3)this.MemberwiseClone();
    }

}
1 Like

Thanks for the fast response! I’ll test it out