Yes, it seems there is no way to “query” anything from that “virtual folder structure” that the Resources folders build up. Once an object is loaded from the resources folder, it has no relation to where it came from. So for example loading a Texture2D asset from a Resources folder just gives you a Texture2D object, just like “new Texture2D()” would with the exception that “new Texture2D()” gives you a new, empty image.
See this post for a possible solution.
I just created a general purpose ResourceDB script which should close the gap. I created two files:
The “ResourceDB” script is just a normal runtime script, so place it anywhere in your project. The “ResourceDBEditor” script, as the name suggests, is an editor script and therefore has to be placed in a folder called “editor”. The ResourceDBEditor isn’t needed, but it allows easier access to some features and protects the saved data from accidental changes.
What the script does:
This script is a scriptableobject which is ment to be stored as asset into a resources folder. It adds a menu item to Unity under “Tools” which allows you to create / update the resource database file. It’s automatically created under
Assets/Resources/ResourceDB.asset. If you select that file in Unity it should show the custom inspector for this class. There you can see what information is actually stored and it allows you to initiate a manual update or enable automatic updating. When automatic updating is enabled, the integrated AssetPostprocessor will check all assets which got modified (added, moved, deleted) and if they belong to a resourced folder, the postprocessor will trigger an update.
This script will store the following information for each file inside resources folders:
- The relative path to that asset. This is the same path that is needed in
- The filename without extension. Again that is needed when you want to use
- It stores the extension seperately. The extension isn’t used for anything, it’s just there if you need that information.
- It also stores the actual assettype. This is done when the database is updated. It stores the System.Type assembly qualified name of the type. For example this is the type string for the Material class:
UnityEngine.Material, UnityEngine, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null. From this string we can reconstruct the actual System.Type object.
Initially i thought about storing the exact file structure of the resources folders in a custom class hierarchy. However since Unity has a max nesting level of 7 it would limit the user to 7 nested subfolders inside a resources folder. Even almost nobody will reach this depth i like it as robust as possible. That’s why all file information is stored in a flat “master file table” (just like NTFS works). I still rebuild the hierarchy structure at runtime for easier usage.
The ResourceDB uses a custom class called “ResourceItem”. A ResourceItem can represent both: an actual asset file or a subfolder from a resources folder. The virtual hierarchy is build up with this class. This class has several methods to navigate the hierarchy or to search for a certain asset(s)
ResourceDB.GetFolder(path) this method return a single ResourceItem for the given relative path. So if you have a folder like this:
Assets/Resources/sub1/sub2/folder you can pass the string
"sub1/sub2/folder" and you get the ResourceItem for “folder”.
ResourceDB.GetAllAssets(name, [type]) this method returns an enumeration of multiple ResourceItems which match the given parameters. “name” has to be the exact asset name. It doesn’t support partial names or wildcards but when you pass an empty string it will match any asset. The second optional type parameter allows you to specify a certain asset type. If this parameter is null any asset will match. The type parameter supports inheritance. So passing
typeof(Texture) will match any texture asset (Texture2D, RenderTexture, …).
Besides those static methods of the ResourceDB class, the ResourceItem class also has similar functions:
GetChild(path, [rType]) This method allows you to access any child of the current ResourceItem. That ResourceItem has to represent a folder. When used on an asset resource it will return null. “path” is again a relative path which can be used to access deeper nested resources. The optional parameter “rType” allows you to specify if you only want assets, folders or both types.
GetChilds(name, [rType [, sub [, type]]]) This method works similar to the GetAllAssets method above, however relative to the current folder and allows some additional settings. “name” again has to either match the actual filename or has to be an empty string to match any file / folder. With rType you can again specify if you want assets, folders or both. “sub” is a boolean which specifies if the method should only search in the current folder (false, default) or if it should include all sub folders (true). “type” is again a System,Type to filter assets for a certain type.
Load() This method allows you to directly load the resource represented by this ResourceItem. It simply uses the “ResourcesPath” property of the ResourceItem which returns the complete assetpath needed for Resources.Load.
GetAllAssets as well as GetChilds return an
IEnumerable<ResourceItem>. This collection can either be iterated with a foreach loop, or converted into a List / array by using Linq.
The ResourceDB class is a singleton which loads itself from the Resources folder. So if you have created an ResourceDB asset in the editor you can use it anywhere in the project. Some examples:
ResourceItem imagesFolder = ResourceDB.GetFolder("images");
List<ResourceItem> imageItems = imagesFolder.GetChilds("",ResourceItem.Type.Asset, true, typeof(Texture2D)).ToList();
Each ResourceItem can be used to actually load the image it represents. You can use linq to batch-load all images into a Texture2D array / List:
List<Texture2D> images = imageItems.Select(i=>i.Load<Texture2D>()).ToList();
It has some limitations when it comes to prefabs. Since a single prefab can represent an unlimited amount of different types, it’s not supported to “search” for a specific component on a prefab. The DB will simply use and store the type of the main asset which is usually the GameObject. The same holds true for custom “assets” which contain multiple sub assets. However, loading should work just as usual.
So for example a terrain prefab (which is a GameObject with a Terrain script attached) can be loaded like this:
ResourceItem prefab; // we somehow obtained the ResourceItem for the prefab;
Terrain terrain = prefab.Load<Terrain>();
Instead of using the Load function of ResourceItem, you could do the loading manually:
string assetPath = prefab.ResourcesPath;
Terrain terrain = Resources.Load<Terrain>(assetPath); // load as usual with Unity's Load method.
I hadn’t much time to test all features and edge cases, so if you find any errors, feel free to leave a comment.