Search Unity

Including Editor Default Resources in package

Discussion in 'Package Manager' started by rory-dungan, Mar 16, 2020.

  1. rory-dungan

    rory-dungan

    Joined:
    Aug 13, 2019
    Posts:
    1
    Is there any way to include files in a package in a way that can be loaded by EditorGUIUtility.Load? The EditorGUIUtility.Load function looks for files in the Assets/Editor Default Resources folder and unlike most other special named folders in Unity (e.g. Editor, Resources, StreamingAssets, etc), there can only be one Editor Default Resources folder, which must be at the root of your project. This poses a problem for packages because a package cannot include files outside of its own directory tree.

    Possible solutions to this I've thought of so far are:
    • Using AssetDatabase.LoadAssetAtPath instead of EditorGUIUtility.Load. This isn't really ideal for my use-case though because I'm creating packages of third-party code so would rather not have to change the code.
    • Copying files into Editor Default Resources when the package gets imported. Looking at the docs I couldn't find a specific callback for when a package is imported but maybe I could use something in AssetPostprocessor to trigger after the package assets are first imported? I've noticed TextMesh Pro copies files into the main project directory after install so it would be interesting to know how it's doing it.
    Anyway I'm curious to know if anyone else has run into this issue and if so, how you solved it. Would also be good to know if, with Unity becoming more reliant on packages, a solution for Editor Default Resources is on the roadmap for the package manager or if it's being deprecated and there's a better way to load images in editor scripts.
     
  2. Davidtr_Unity

    Davidtr_Unity

    Unity Technologies

    Joined:
    Mar 5, 2020
    Posts:
    34
    Hi @rory-dungan,

    Better support for handling package resources is on the roadmap and being worked on. Until then, the easiest way is definitely to load the asset directly using its path, as you mentioned.

    EditorGUIUtility.Load works with paths, just like AssetDatabase.LoadAssetAtPath. So you could have something like this:

    Code (CSharp):
    1. EditorGUIUtility.Load("Packages/com.unity.my.package/Editor/Images/Image.png");

    As for copying files into the resource folder, It’s a bit tricky but it could work. You'd need to make sure the resources in there are kept up to date, not imported twice (ideally), and to make sure not to cause an infinite post-process loop. The process would be pretty agnostic to the package manager.

    You could have a special import script using InitializeOnLoad which would then move the resources (or create a folder junction if you don't mind doing OS specific implementations) when the script is loaded. You could keep track if resources are up to date and need to be re-imported by storing the package version in some file created for this purpose. While in package form, resources could be placed in a folder that is suffixed with a tilde ('~') so that they don't actually get imported until your custom move operation has run and moved the resources in a non-tilded folder.

    That being said, I definitely recommend that you use paths if you can and save yourself this extra work. Improvements are expected to land in future updates, we're aware that this can be a bit of a hassle :).
     
  3. Andrew-Carvalho

    Andrew-Carvalho

    Joined:
    Apr 22, 2013
    Posts:
    45
    I am trying to do the same thing as rory-dungan and turn a particularly messy third party plugin into a package and this is as the last hurdle I needed to get over.

    In case it's helpful to anyone else, my issue was that I was using the wrong package name. The path given to the method must be

    "Packages/package_name/path/to/asset.extension"
    where the package_name is the name as defined in the package.json. In my case, the package was embedded so I originally used the actual folder name which did not work.

    My assumption is that Unity is processing the path in a special way if it leads with "Packages". This makes sense since a package may live in one of multiple places. My guess is that it requires the defined package name to ask the package manager where it actually lives on disk, and then replaces that path under the hood before trying to load the asset. It would be nice if this was made more clear in the documentation.

    @Davidtr_Unity I have a follow up question which is hopefully easy to answer: Is there some benefit of using one of these over the other?

    AssetDatabase.LoadAssetAtPath<T>(path)
    and

    EditorGUIUtility.Load(path)
    I've gone with using the AssetDatabase call because I prefer the simplicity of the generic but if one handles caching or some other fanciness I want to make sure I'm using the appropriate call.
     
  4. Davidtr_Unity

    Davidtr_Unity

    Unity Technologies

    Joined:
    Mar 5, 2020
    Posts:
    34
    Hi @Andrew-Carvalho,

    You're right about Unity creating virtual paths under the hood, this is actually done at the asset level which means the same path can be used across all of Unity Apis.

    As for which Api is better, I'd say only use EditorGUIUtility.Load if you need to load assets from the Editor Default Resources folder. It's an editor only API and it will fallback to AssetDatabase.LoadAssetAtPath if the asset is not present in this special folder. It serves no other benefits.
     
    zwcloud likes this.
  5. Kryptos

    Kryptos

    Joined:
    Mar 30, 2011
    Posts:
    29
    Is this still being worked on?

    Using a hard-coded path is not practical when developing a package that will eventually be published on the AssetStore.
    While
    Code (CSharp):
    1. EditorGUIUtility.Load("Packages/com.unity.my.package/Editor/Images/Image.png");
    is valid when developing it as an embedded package, it won't be once it is published and imported from the store.
     
  6. Adrian

    Adrian

    Joined:
    Apr 5, 2008
    Posts:
    1,066
    It's still valid. As discussed above, it's a virtual path that has no relation to where the package is located on disk. It works if the package is embedded in the project but also if it's downloaded from a registry or git.

    You only need to update the package path if you move the file to another package or change the package identifier.
     
  7. Andrew-Carvalho

    Andrew-Carvalho

    Joined:
    Apr 22, 2013
    Posts:
    45
    In my post above I mention that the path passed in is not the actual path of the asset. Specifically, if your package folder is named
    com.unity.my.package
    but it is names
    My Package
    in the package.json, then the appropriate "hardcoded" path to use is
    Packages/My Package/Editor/Images/Image.png
    . This isn't actually a hardcoded path but a virtual path, meaning that Unity will automatically resolve
    Packages/My Package
    to be the actual installation folder, which will differ depending on how it is distributed and/or installed. In your case, it will work while the package is embedded and being developed as well as when it is installed in another way via package manager.
     
  8. TG1980

    TG1980

    Joined:
    Oct 23, 2013
    Posts:
    40
    This will handle all three scenarios; first it will try to load the asset as a package path, then it will try to load the asset as a package display path, and finally as an asset path. So you can put your stuff in Assets/Packages/XXX, where XXX matches either the package's display or real name, and it will work both in your project and when residing in a package.

    Code (CSharp):
    1.  
    2.        // Checks packages first and if it doesn't exist there checks assets.
    3.         public static T LoadAssetFromPackage<T>(string packageFilePath) where T : Object
    4.         {
    5.             // try to load as a package path
    6.             var asset = AssetDatabase.LoadAssetAtPath<T>(packageFilePath);
    7.             if (asset != null)
    8.                 return asset;
    9.  
    10.             // try to convert path to a package path from a presumed package display path
    11.             var splits = packageFilePath.Split('/');
    12.             if (splits.Length >= 2)
    13.             {
    14.                 var possiblePackageDisplayName = splits[1];
    15.                 var possiblePackageName = PackageDisplayNameToPackageName(possiblePackageDisplayName);
    16.                 if (!string.IsNullOrEmpty(possiblePackageName))
    17.                 {
    18.                     splits[1] = possiblePackageName;
    19.                     var possiblePackageFilePath = string.Join('/', splits);
    20.  
    21.                     var possibleAsset = AssetDatabase.LoadAssetAtPath<T>(possiblePackageFilePath);
    22.                     if (possibleAsset != null)
    23.                         return possibleAsset;
    24.                 }
    25.             }
    26.  
    27.             // try to load as project asset path
    28.             var possibleAssetFilePath = $"Assets/{packageFilePath}";
    29.             return AssetDatabase.LoadAssetAtPath<T>(possibleAssetFilePath);
    30.         }
    31.  
    32.         public static string PackageDisplayNameToPackageName(string packageDisplayName)
    33.         {
    34.             var packages = UnityEditor.PackageManager.PackageInfo.GetAllRegisteredPackages();
    35.  
    36.             return packages
    37.                 .Where(package => package.displayName == packageDisplayName)
    38.                 .Select(package => package.name)
    39.                 .FirstOrDefault();
    40.         }
    41.  
    Call it such as
    Code (CSharp):
    1.  
    2. var asset = LoadAssetFromPackage<Texture2D>("Assets/Packages/PackageDisplayNameOrRealName/SomeTexture.png");
    3.  
     
    Last edited: Apr 23, 2022
    daneobyrd likes this.