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.

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.