I have my level data stored in Unity/Assets/Resources/Levels
I can do Resources.LoadAll() to load everything from this folder, but that seems heavy-handed. I just want a list of filenames, so I know which files I can use Resources.Load() to load later.
How can I check if a file exists without loading the whole file?
What you REALLY need to do is to put the resources you need to load inside your persistentDataPath folder UPON THE GAME STARTING. You can’t use dataPath with all platforms, you can’t use File.IO, so you will have to use Application.persistentDataPath.
I ran in to this problem recently with many of my files for my game. To get it working on Android platforms as well, I end up needing to take my files, put them in my SteamingAssets folder, then from there extract them to my dataPath folder. I use a library called SharpZip for this and use .tgz files (they’re like .zip files but for Linux so I can get all this working on android). I create the .tgz file at start as a copy of the StreamingAssets path (which isn’t accessible on android upon build as it’s packaged into like a .apk).
I then extract that .tgz file into the persistentDataPath, esentially recreating my streamingAssets folder but at persistentDataPath at runtime. You have to check of course if there are already files there upon runtime as well so you don’t overwrite your data files the second time you run the game.
PersistentDataPath is that path that you can access as a directory upon runtime on many different platforms including iOS, Android, Mac etc… Again, you can do this also with StreamingAssetsPath but it won’t work on certain platforms.
Another quick tip: Make sure that you delete the files you need to with void OnApplicationExit() or whatever that method is.
Just for completeness I’ve created a “resource DB” two years ago which allows you to store all file information of assets inside resources folders in a “database” (ScriptableObject) which allows you to use that information at runtime. It only requires a “manual” update of the DB by selecting “Tools/Update ResourceDB”. It will automatically create a scriptable object inside Assets/Resources/ and fill it with all the path information about all resources in any resources folder
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.IO;
public class FileUtility {
/// <summary>
/// Determine whether a given path is a directory.
/// </summary>
public static bool PathIsDirectory (string absolutePath)
{
FileAttributes attr = File.GetAttributes(absolutePath);
if ((attr & FileAttributes.Directory) == FileAttributes.Directory)
return true;
else
return false;
}
/// <summary>
/// Given an absolute path, return a path rooted at the Assets folder.
/// </summary>
/// <remarks>
/// Asset relative paths can only be used in the editor. They will break in builds.
/// </remarks>
/// <example>
/// /Folder/UnityProject/Assets/resources/music returns Assets/resources/music
/// </example>
public static string AssetsRelativePath (string absolutePath)
{
if (absolutePath.StartsWith(Application.dataPath)) {
return "Assets" + absolutePath.Substring(Application.dataPath.Length);
}
else {
throw new System.ArgumentException("Full path does not contain the current project's Assets folder", "absolutePath");
}
}
/// <summary>
/// Get all available Resources directory paths within the current project.
/// </summary>
public static string[] GetResourcesDirectories ()
{
List<string> result = new List<string>();
Stack<string> stack = new Stack<string>();
// Add the root directory to the stack
stack.Push(Application.dataPath);
// While we have directories to process...
while (stack.Count > 0) {
// Grab a directory off the stack
string currentDir = stack.Pop();
try {
foreach (string dir in Directory.GetDirectories(currentDir)) {
if (Path.GetFileName(dir).Equals("Resources")) {
// If one of the found directories is a Resources dir, add it to the result
result.Add(dir);
}
// Add directories at the current level into the stack
stack.Push(dir);
}
}
catch {
Debug.LogError("Directory " + currentDir + " couldn't be read from.");
}
}
return result.ToArray();
}
}
What really bothers me is that all those workarounds people have created took so much effort and experimentation, testing on various platforms, different solutions for different platforms… instead of Unity just providing us with one function:
Resources.ListAll()
That’s all. A resource file most likely already contains a listing of its content (otherwise how would LoadAll work) so all they have to do is to EXPORT the damn function. The same function that LoadAll uses to find all the objects in Resources. Just return us their names, instead of loading them all.
Unity, please export the damn function. It makes no sense that we have to do all these workarounds when the best solution is already there, just hidden from us. Write the word “public” in front of it.
A simple way to approach this is to write the file paths into a .txt file during each build to be fetched later in-game.
First, the following extension methods are used to retrieve the file paths recursively from Resources and StreamingAssets. Then, they are written into a .txt file at a known path in Resources while building the game. Lastly, the .txt file is read as a TextAsset in-game to get the requested file paths.