- Home /
GetAssetPath returning incomplete path for default materials
I have an editor script in which I’m attempting to get the path of a user selected material. While the following code works fine for user created materials, but if the user happens to select a built-in material, the function does not return the full path of the material. (It looks like it’s just returning the folder.)
Material defaultMat = (Material)AssetDatabase.LoadAssetAtPath(matPath, typeof(Material));
Material newdefaultMat = (Material)EditorGUILayout.ObjectField(defaultEdgeRendererMaterialGUID.label, defaultMat, typeof(Material), false);
matPath = AssetDatabase.GetAssetPath(newdefaultMat);
if (defaultMat != newdefaultMat)
Debug.Log("new mat path: "+matPath);
Log Output:
new mat path: Resources/unity_builtin_extra
Question: Given the material selected by the user, how can I get its path, in all cases?
Other details: I’m trying to store the user selected material as a string, of some form, so that I can store it in an EditorPreference. Then I can load and reference this material later, possibly even in a different session, as a default Material when creating custom objects. Workarounds to achieve this are welcome answers as well.
Answer by Bunny83 · Jul 12, 2017 at 05:31 PM
As you might know you can pack multiple assets into a single "asset file". That is the case here. The individual assets do not have seperate asset files. The actual asset file is located here:
C:\Program Files\Unity\Editor\Data\Resources\unity_builtin_extra
It contains many assets. If you need to find one particular asset you have to use a method like AssetDatabase.LoadAllAssetsAtPath and find your desired asset. Keep in mind that all UnityEngine.Objects can have a "name". Though not all internal assets have one.
edit
Here's an example script which allows you to get a list of all assets stored in the unity_builtin_extra asset file:
//BuiltInAssetsExtra.cs
using UnityEngine;
public class BuiltInAssetsExtra : MonoBehaviour
{
public UnityEngine.Object[] objs;
[ContextMenu("GetAllExtraAssets")]
void GetAllExtraAssets()
{
#if UNITY_EDITOR
objs = UnityEditor.AssetDatabase.LoadAllAssetsAtPath("Resources/unity_builtin_extra");
#endif
}
}
Attach it to an object and execute the method from the context menu.
second edit
I haven't really tested this, but it probably should work:
//AssetDatabaseHelper.cs
using UnityEditor;
using System.Linq;
public class AssetDatabaseHelper
{
public static T LoadAssetFromUniqueAssetPath<T>(string aAssetPath) where T : UnityEngine.Object
{
if (aAssetPath.Contains("::"))
{
string[] parts = aAssetPath.Split(new string[] { "::" },System.StringSplitOptions.RemoveEmptyEntries);
aAssetPath = parts[0];
if (parts.Length > 1)
{
string assetName = parts[1];
System.Type t = typeof(T);
var assets = AssetDatabase.LoadAllAssetsAtPath(aAssetPath)
.Where(i => t.IsAssignableFrom(i.GetType())).Cast<T>();
var obj = assets.Where(i => i.name == assetName).FirstOrDefault();
if (obj == null)
{
int id;
if (int.TryParse(parts[1], out id))
obj = assets.Where(i => i.GetInstanceID() == id).FirstOrDefault();
}
if (obj != null)
return obj;
}
}
return AssetDatabase.LoadAssetAtPath<T>(aAssetPath);
}
public static string GetUniqueAssetPath(UnityEngine.Object aObj)
{
string path = AssetDatabase.GetAssetPath(aObj);
if (!string.IsNullOrEmpty(aObj.name))
path += "::" + aObj.name;
else
path += "::" + aObj.GetInstanceID();
return path;
}
}
This allows you to create a unique "assetpath" which includes the actual asset name or instance ID after a double colon "::". The "LoadAssetFromUniqueAssetPath" method will then do the reverse. The type check should allow inheritance. So LoadAssetFromUniqueAssetPath<Object>(somePath)
will work with any type (in case you don't know the type at compile time).
Again, not tested yet.
Ok i quickly tested both methods and it works with the built-in "Default-Material". It gives you a path like this:
"Resources/unity_builtin_extra::Default-Material"
Which does create a proper reference to the Default-Material. I also works with an actual assetpath. If you use GetUniqueAssetPath on a prefab reference you get this:
"Assets/MyPrefab.prefab::MyPrefab"
"LoadAllAssetsAtPath" also works on single assets.
So how do I reference a $$anonymous$$aterial stored like this, using a string? Looks like: Store its name/GUID seperately along with the path.
Then check if GetAssetAtPath returns null, if so use LoadAllAssetsAtPath to load everything at that path, then search through all those loaded assets for the one with the stored name/GUID.
Yuk, is there a better way?
Yes, that's probably the only way. You can't get a direct assetpath to an asset that is inside an "assetpackage".
I'm not sure if that would work across multiple sessions, but you could try storing the instance ID of the object in question and use EditorUtility.InstanceIDToObject.
ps: choosing the right type might be necessary. For example the assetpath:
"Resources/unity_builtin_extra::Checkmark"
still refers to two assets. The first is a Texture2D, the second is a Sprite that references the texture. So using the proper type might be necessary in some cases.
Good gosh Bunny, you just saved me hours! I have confirmed it works in my project too (also only tested for mats, so far) , it fit right in seamlessly (just added if (!aObj) return "";
to GetUniqueAssetPath)!
Edit/Update: Tests confirm this also works for built-in meshes!
builtinCube$$anonymous$$esh=AssetDatabaseHelper.LoadAssetFromUniqueAssetPath< $$anonymous$$esh > ( "Library/unity default resources::Cube");
This string was generated by simply passing the builtin cube mesh to the GetUniqueAssetPath function, and displaying the output.
Did this stop working in more recent Unity versions? UnityEditor.AssetDatabase.LoadAllAssetsAtPath("Resources/unity_builtin_extra");
comes back empty for me in 2018.3.
Oh, yes, it was a regression in Unity 2018.3, fixed in 2019.1: https://issuetracker.unity3d.com/issues/using-loadallassetsatpath-to-load-the-paths-does-not-load-objects-stored-at-paths-and-leaves-the-the-expecting-object-empty