Best way to handle duplicated information

I’m learning unity and game development, but there’s a thing that I don’t know how to handle, and it is duplicated information.
For example, I have a class Tile, this tile has some common information with all other tiles of its type, like name and movement cost. Since I do not want to write this information all the time and I only need a reference to one object with this information, I created a Scriptable Object and an AssetHelper to find the SO and set it on the tile object.

 public class AssetHelper
    {
        private static readonly Dictionary<string, TileData> TileDataSoDictionary = new();

        public static TileData GetAndCacheTileData(string id)
        {
            if (TileDataSoDictionary.TryGetValue(id, out var data))
                return data;

            var findObjectsOfType = Resources.FindObjectsOfTypeAll<TileData>();

            foreach (var t in findObjectsOfType)
            {
                if (t.id != id)
                    continue;

                TileDataSoDictionary.Add(id, t);
                return t;
            }

            return null;
        }
    }
  [CreateAssetMenu(fileName = "Tile Data", menuName = "ScriptableObjects/Tile", order = 1)]
    public class TileData : ScriptableObject
    {
        public string id;
    
        public new string name;

        public Sprite sprite;

        public float movementCost;
    }
    public class Tile
    {
        public TileData Data { get; protected set; }

public int X { get; protected set; }

public int Y { get; protected set; }
}
var tile = new Tile(AssetHelper.GetAndCacheTileData("grass"), x , y);

Is there a better way? I’m doing it wrong?

Yep, seems good to me. Maybe the only thing I’d add is a Debug.Warning($“Cannot find tile with id ‘{id}’”) before returning null when no tile is found, just to avoid a headache in two months when something is not working inside a callstack of 15 methods.

Scriptable objects definitely are the Unity way of preventing duplicated data. Though normally you’d just assign a reference to one via the inspector. Is that an option for you, or I’m guessing you require a more dynamic lookup method?

Having some way to type-safe your tile’s rather than using strings would be beneficial too.

1 Like

I’m not assigning through the inspector because I’m using the MVC approach; there’s a class called Level, which is the one who creates the tiles.

 public Level(int width, int height, LevelType type)
        {
            this.width = width;
            this.height = height;
            this.type = type;
            tiles = new Tile[this.width, this.height];
            for (int x = 0; x < this.width; x++)
            {
                for (int y = 0; y < height; y++)
                {
                    var tile = new Tile(AssetHelper.GetAndCacheTileData("grass"), x, y, this);
                    Debug.Log(tile.Data);
                    tiles[x, y] = tile;
                }
            }
        }

What doesn’t seems right is searching for the scriptable object inside the “pure” Level object. Myabe I’m just banging my head against the wall.

I think that this line can be discussed:

var findObjectsOfType = Resources.FindObjectsOfTypeAll<TileData>();

You are calling this for each new tile, when you could have it loaded just once. As a middle ground you could register all the tiles that you need and load them all at once, in the same loop.

Nothing really inherently wrong with that, imo. I do this a lot as I like to work on the pure C# side of things too.

So long as it works I don’t see the issue. Honestly lots of ways to accomplish this kind of pattern. My go to would be a static class that just loads them all on initialisation (using
[RuntimeInitializeOnLoadMethod]
) for example. Then they can be looked up at any time with little overhead.

Much bigger games have done far less elegant things. Minecraft just has what is akin to a static class with static properties of every block type in the game.

I wonder how much collective CPU they save by having static function calls instead of instance ones that have to always pass a this with every call site… I bet for such a low-level property sheet it might be significant.