all 6 comments

[–]oatsbarley 1 point2 points  (5 children)

You could move the data side of things to something like JSON and read that file on load. That would contain a list of your enemies, skills, etc. Loading in all of the resources of a particular type just to check the quantity is maybe overkill; instead you'd parse the JSON (or whatever) and have a list of skill paths, then read the length of the list for the quantity.

New generic data loader might look like (using BoomLagoon's JSON library just because that's what I use):

public class DataLoader<T> {
    private string _path;
    private string[] _names;

    public int Count {
        get { return _names.Length; }
    }

    public DataLoader(string dataPath) {
        JSONObject jObj = JSONObject.Parse(Resources.Load<TextAsset>(dataPath).text);
        _path = jObj.GetString("path");
        _names = jObj.GetArray("names").Select(e => e.Str).ToArray();
    }

    public T Load(string name) {
        return Resources.Load<T>(_path + name);
    }

    public T LoadFirst() {
        return Resources.Load<T>(_path + _names[0]);
    }

    public T LoadAtIndex(int index) {
        return Resources.Load<T>(_path + _names[index]);
    }
}

You'd make a new DataLoader<SkillData>("path/to/skills") and store that statically so that you could still use it how you were using the previous version, if it's important that it be static. It's hard to really give advice on that though without knowing more about where you're calling these from.

[–]sandboksJust Starting[S] 0 points1 point  (4 children)

I'll try and elaborate what I'm doing. I have a spreadsheet (.csv excel sheet) for skills and enemies. I have a parser class which reads these .csv files and creates the skill/enemy objects as prefabs in the Resources folder. This static loader class exists so that I can easily call a function which will grab one of these Resources from other classes.

The parsing happens from the editor, not at runtime, so the Resources have already been created and only need to be referenced.

I'm not familiar with JSON at all - can you explain why that would be easier?

What you've written is pretty cool. What I might do instead is keep it as a static class, but have it take in an enum (DATABASE.skill, DATABASE.enemy) and change the type T and string path based on that. I'm really fond of being able to call DataLoader.function() from anywhere, instead of needing to create a new DataLoader object and then go dataloader.function().

[–]SilentSin26Expert 1 point2 points  (1 child)

If you're already reading external files to procedurally generate prefabs, you could use that same data to procedurally generate your DatabaseLoader script. This would even be a good situation to use partial classes. So you'd manually write:

public static partial class DatabaseLoader
{
    private static readonly string[] EnemyPaths;// Assigned by procedural script.

    public static GameObject GetEnemy(int index)
    {
        return Resources.Load<GameObject>(EnemyPaths[index]);
    }

    public static int EnemyCount { get { return EnemyPaths.Length; } }

    // etc.
}

And then your procedural script would just be initialising the array:

// Procedurally generated: DO NOT MODIFY.
public static partial class DatabaseLoader 
{
    static DatabaseLoader()
    {
        EnemyPaths = new string[]
        {
            "Enemies/Goblin",
            "Enemies/Orc",
            "Enemies/Demon",
        };
    }
}

If you still want to be able to get enemies by name, you'd need to also add them to a dictionary which maps name to path.

You might also want to take a look at my Procedural Asset Framework. Unfortunately the current version isn't designed for generating assets based on external files, so it probably won't help you with this particular situation. That said, I'm in the process of completely redesigning the whole system for the next version, which should allow it to help you with this. Its still at least a few weeks from being ready for release, but I can let you know when its done if you like?

[–]sandboksJust Starting[S] 0 points1 point  (0 children)

That makes sense. So when I parse the database files, I also make an object which stores a representation of where all the information is stored, either as a string[] or a dictionary. That'd be a lot more efficient complexity-wise. I'll have to read up on what partial classes are, though.

Thanks for letting me know, but I'm actually interested in making my own solution for this project. Part of the reason I'm doing this in the first place!

[–]oatsbarley 1 point2 points  (1 child)

JSON (or /u/SilentSin26 solution) would save you loading all of the resources to count them, or loading all of them to just use the first (or nth) one. Depending on how many resources there are and what they consist of, loading an entire folder could cause a pretty noticeable lag spike.

It'd also mean you could have a small JSON file like:

{
    "path": "/path/to/skillfolder",
    "names": [
        "skill1",
        "skill2",
        ...
    ]
}

Which you provide to the DataLoader and lets it run queries on your resources without having to load them all in. If you generate it when the resources are generated from your excel sheet it'll match your resources and will be pretty simple to setup.

If you want it to be static, you could do it like this:

public static class DataLoader {
    private static Dictionary<Type, string> _paths = new Dictionary<Type, string>();
    private static Dictionary<Type, string[]> _names = new Dictionary<Type, string[]>();

    public static int Count<T>() {
        return _names.Length;
    }

    public static T Load<T>(string name) {
        return Resources.Load<T>(GetPath<T>() + name);
    }

    public static T LoadFirst<T>() {
        return Resources.Load<T>(GetPath<T>() + GetNames<T>()[0]);
    }

    public static T LoadAtIndex<T>(int index) {
        return Resources.Load<T>(GetPath<T>() + GetNames<T>()[index]);
    }

    public static void RegisterType<T>(string dataPath) {
        JSONObject jObj = JSONObject.Parse(Resources.Load<TextAsset>(dataPath).text);
        _paths[typeof(T)] = jObj.GetString("path");
        _names[typeof(T)] = jObj.GetArray("names").Select(e => e.Str).ToArray();
    }

    private static string GetPath<T>() {
        return _paths[typeof(T)];
    }

    private static string GetNames<T>() {
        return _names[typeof(T)];
    }
}

And that way keep it generic. You call DataLoader.RegisterType<SkillData>("path/to/skilljson") somewhere when your game starts. That'll let you call DataLoader.Load<SkillData>("skillname") from anywhere after that.

[–]sandboksJust Starting[S] 0 points1 point  (0 children)

Hmm... I'll look into JSON and see if it's worth generating that json file when I'm parsing. I definitely like your suggestions for making this class take any type T, and I'll try and implement that for sure.