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