Run Through a Folder and Make a List of TileBase

Hello! Here is some relevant code:

public class TileRepository
{
    public static List<TileBase> tileSprites = new();

    public string[] info;

    private void Awake()
    {
        info = Directory.GetFiles("Assets/Tiles/Sprites", "*.asset");

        List<TileBase> tileSprites = new();

        foreach(string tileSpriteName in info)
        {
            Debug.Log("Should be tile sprite name:" + tileSpriteName);
            tileSprites.Add(Resources.Load<TileBase>("Assets/Tiles/Sprites/" + tileSpriteName));
        }
    }

    public static SalvagedStructureTile rustedSteelWall = new()
    {
        ID = "RustedSteelWall",
        numID = 0,
        shortDesc = "A rusted steel wall, capable of being harvested for simple sheets which may be smelted down into alloys.",
        //Position unset!
        tileSprite = tileSprites[0]     //Results in null reference exception; duh, because
                                       //tileSprites is still empty, Awake was never called

    };

I am currently trying to make a repository of information on individual tiles, in a tile-based game I’m working on. In my tile repository, I define information for each custom tile, like its string ID, numerical ID, a description… etc. I also wish to define its sprite, for use in being hooked into by a tilemap to get what the tilemap should display.

This is where my issue is, and I’m in need of a bit of a creative solution. When trying to set the sprite, I receive a NullReferenceException, as tileSprites[0] does not exist. It would, had Awake ran before the class definitions, however there’s a lot wrong with that; since this is non-monobehavior, Awake will never run. Awake, even if it did run, would happen afterthefact of the object declarations, and would still result in the error.

How can I set the tile sprite from a list like this, where the list is made from what TileBase assets reside in the associated directory? I’m essentially trying to make a modular system where I can simply make a TileBase asset and put it in the folder, and have it be usable as a tile sprite.

You can use RuntimeInitializeOnLoadMethod to have non-monobehaviour code run at certain stages of the application starting: Unity - Scripting API: RuntimeInitializeOnLoadMethodAttribute

Just be aware your project folders and various assets do not exist at runtime as they’re all crunched up into Unity’s various compressed formats.

Though honestly you should just be using scriptable objects to define your various tiles. No need to do everything purely via code.

1 Like

I should also say, I had the idea to have the sprite definitions separate, going through later and defining the sprite before the game starts (i.e., have another script that happens as the game starts, that goes through each tile in the repository and sets the sprite appropriately, rather than do it in the object declaration). Would this be a valid solution, or can I keep everything in the object declaration? I’d ideally love everything to be consolidated into the repository and have all relevant information stored there.

Also yes, I realize a } is missing from the end of the code, this is just a snippet from a much larger script

So I can’t access the sprites directory this way? If that’s the case, it seems it would kind of defeat the purpose, as I need it to get the right directory where the sprites are stored. Is there a way around this?

I looked into ScriptableObjects, but because I need each tile to both be generic and specific, a more custom route was better. I need tiles to be held in a generic matrix where any tile can reside, but also be specific enough to only hold the information that is necessary (i.e., if it holds farming data, construction data, floor data, etc). I’m using a tag-based factory in another script, so that I can just hold the information I need and dynamically allocate tile in the matrix to only hold what it needs to. I looked into ScriptableObjects but they weren’t what I needed, I believe

You can use Resources at runtime. Just your Directory.GetFiles is not going to work at runtime as these folders don’t exist.

Do you understand polymorphism in C#? You can absolutely make any given tile only hold the required information by using a base class/interface and derived class approach. Your scriptable objects can act as a factory for specific kinds of tiles.

I know this will work as this is exactly how my current approach works in my project.

1 Like

How can I access the sprites then? I need the folder to access to be able to find where the sprites are. Resources.Load won’t do me any good if I don’t have the path.

I am still somewhat new to programming, especially in C#. I will definitely look into ScriptableObjects again, I know I dismissed them in the past after looking into them thoroughly as I believed they were not able to hold the exact data necessary, and differ between object types (i.e., constructed, farmable etc). I assume I could have a generic ScriptableObjects list, then a more defined SalvagedStructureTile list (which is currently a final-form object) within that, where all of the associated SalvagedStructureTiles can be hosted and accessed in a repository that way? I have not used these much so I would not be super sure on the implementation, but I will look into it further.

If you plan to use Resources, you need to decide on a path, keep them in that path, and don’t change that path.

I tend not to use Resources for this reason. I just have all my factory objects stores in a scriptable object singleton to make them accessible as I need them.

In your case you’d need a base and derived scriptable objects that act as factories for the appropriate types of tiles.

Something like this at a basic level:

public abstract class TileFactoryBase : ScriptableObject
{
    public abstract WorldTile GetTile(int x, int y);
}

[CreateAssetMenu]
public sealed class TileFactoryBaseSalvageTile : TileFactoryBase
{
    public override WorldTile GetTile(int x, int y)
    {
        return new SalvageableTile(x, y);
    }
}

Note that WorldTile here is a plain C# object. I’m assuming you already have a data structure that represents the world as pure data (if not, you should for these types of games).

Alternatively if you have the right editor tools, you can take advantage of [SerializeReference] and only require the one scriptable object factory, using plain C# classes instead:

[CreateAssetMenu]
public sealed class TileFactoryAsset : ScriptableObject
{
    [SerializeReference]
    private TileFactoryBase _tileFactory = null;
   
    public WorldTile GetTile(int x, int y)
    {
        return _tileFactory?.GetTile(x, y);
    }
}

[System.Serializable]
public abstract class TileFactorybase
{
    public abstract WorldTile GetTile(int x, int y);
}

[System.Serializable]
public sealed class TileFactoryBaseSalvageTile : TileFactoryBase
{
    public override WorldTile GetTile(int x, int y)
    {
        return new SalvageableTile(x, y);
    }
}
1 Like

I will look into that! I would like to have everything store somewhere nice like that for easy access.

And about the factories using ScriptableObjects… Awesome! I’m working with something like that right now, and I’ll see how it works out. The world was already being represented as data, yes. The sprites are just visual representations of what the data is doing, I took you up on your previous advice about keeping the data and visuals separate. Thank you so much!

1 Like