- Home /
Recover path information for an asset, but at runtime...
Say you have a folder Resources/Stuff
In "Stuff" you have 100s of folders each with many small assets.
You do a LoadAll on "Stuff" -- everything works perfectly.
However for each of the small assets ... it would be great to know the NAME OF THE FOLDER which was the parent of that asset.
Is there any way at all to do this?
I don't mind if it is in Resources, or a normally-loaded folder.
In short, for some asset X, it would be great to know the name of the parent folder, at run time, for organisational reasons.
It seems hard to believe there is not a way to do this...
is it a fact that you ABSOLUTELY CAN NOT recover path information at runtime?
cheers...
Answer by Bunny83 · Jan 25, 2016 at 04:33 PM
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.
edit
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
Resources.Load
.The filename without extension. Again that is needed when you want to use
Resources.Load
.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<T>() 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.
ps:
I hadn't much time to test all features and edge cases, so if you find any errors, feel free to leave a comment.
Got it, thanks for confir$$anonymous$$g that indeed it seems one "loses" the folder information, from, Project panel, once it rolls through to the actual game.
(I've sent along 10 RewardPoints
to say thanks ... I wouldn't want to see someone struggling for points ;) )
I see the linked Anxo's code, good one, unfortunately it didn't help me figure out an Editor script :/
Basically I have a few hundred AudioClip, in some dozens of folders. Simply, at runtime, I want to know the name of each folder (category, if you will).
I can see two good approaches,
(A) Thank goodness these days, you can just select multiple AudioClip, and, set the values all at once, and drag them to the Hierarchy and it creates a gameobject+`AudioSource` for each one.
If it would additionally make "folders" for each folder, that is to say using an empty game object with the correct name, that would be the job done. I ask this here but it's too hard so far :)
Secondly per Axno's code if an editor script, very simply created a long text file that had, for each AudioClip (or indeed ... every asset would be fine) the name of the containing folder, again the job would be done.
Anyways that's what I had in $$anonymous$$d, heh. Cheers!
Thanks for the code, this looks very useful. However I'm having some problems with it.
Works very well until I do a build (eg desktop PC or Android). Then ResourceDB.GetFolder() always just returns null. Seems the dictionary is empty for some reason.
When I exit and re-open Unity, selecting the ResourceDB asset, or running my app, or doing "Tools->Update ResourceDB" all lead to the same error about Unity calling a "pure virtual function call", after which it exits. I had to manually delete the ResourceDB.asset outside of Unity to get anything working again.
These are show-stoppers obviously. Is it just me? Thanks, Rob.
Yes, i noticed that myself ^^. I've found a problem which i just fixed. I added a constructor that sets the static singleton variable:
public ResourceDB()
{
m_Instance = this;
}
The problem was when the ResourceDB asset got deserialized by Unity, the deserialization code accessed the singleton instance. If it's not initialized yet it tries to use Resources.Load to get the reference to the asset. However since that happens from Unity's serialization callback it caused problems.
This should also fix the null-ref problem at runtime.
I also changed the editor script as i forgot to mark the DB dirty when you toggle the autoupdate button. Now the state should be saved in the asset.
I've updated the files.
Awesome! Yep thanks that fixed all the problems.
While I've got your ear, there's one other thing that would make this even nicer. The ability to limit the database to a given folder. I don't need all my resources, including resources relating to third party assets, in the database. I'd like to just create a folder called Assets/Resources/DB and tell it to only keep track of items under that folder. Would that be hard?
Thanks, Rob.
Hey!! first of all thanks for sharing, really nice code, but i am having problems when building for android. It seems that the scriptable object reference is always missing, i dont know really why, i just know everything explodes when building for mobile devices, do you have any idea why?
I see the links to ResourceDB.cs and ResourceDBEditor.cs are now returning file not found. I wanted to check and see if this was deliberate, or if they are still meant to be available? Really helpful answer though!
Dropbox has removed the public folder feature. Now we have to explicitly create a link to each file seperately. I've updated the links.