以编程方式设置和保存与导入资产相关联的图标

10

我有一些自动生成的数据被导出到我的Unity项目中。为了帮助我,我想给这些资源分配一个自定义图标以清晰地识别它们。当然,在编辑器本身中也可以很简单地实现这一点,但是理想情况下,我希望在导入时自动完成。

为此,我编写了一个AssetPostProcessor来替我处理这个问题。在下面的示例中(以MonoScripts为例,但也适用于任何类型的资产),所有新导入的脚本都将分配MyFancyIcon图标。这个更新在脚本资产本身以及检查器中的MonoBehaviours上都可见。

using UnityEngine;
using UnityEditor;
using System.Reflection;

public class IconAssignmentPostProcessor : AssetPostprocessor
{
    static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
    {
        Texture2D icon = AssetDatabase.LoadAssetAtPath<Texture2D>("Assets/Iconfolder/MyFancyIcon.png");
        foreach (string asset in importedAssets)
        {
            MonoScript script = AssetDatabase.LoadAssetAtPath<MonoScript>(asset);
            if(script != null)
            {
                PropertyInfo inspectorModeInfo = typeof(SerializedObject).GetProperty("inspectorMode", BindingFlags.NonPublic | BindingFlags.Instance);
                SerializedObject serializedObject = new SerializedObject(script);
                inspectorModeInfo.SetValue(serializedObject, InspectorMode.Debug, null);
                SerializedProperty iconProperty = serializedObject.FindProperty("m_Icon");
                iconProperty.objectReferenceValue = icon;
                serializedObject.ApplyModifiedProperties();
                serializedObject.Update();

                EditorUtility.SetDirty(script);
            }
        }

        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();
    }    
}
除了一个问题,它可以正常工作。在关闭项目并重新打开时,更新未保存。据我所知,EditorUtility.SetDirty(script);调用应该解决此问题,或者至少是AssetDatabase.SaveAssets();调用。
然而,当手动分配图标(可行)和以编程方式分配图标之间的差异时,与资产相关的元数据中存在一个icon字段,当手动分配图标时确实设置了该字段,但在我的脚本情况下没有设置。(在脚本情况下,元数据文件甚至都没有更新)
那是怎么回事?当我(显然)只更改元数据时,我需要做些特别的事情吗?我是否忽略了任何简单的东西?

你在运行时和运行后都能看到它,但是当关闭Unity并重新打开时,它就消失了 - 我理解得对吗?听起来像是一个bug... - Fredrik Schön
确切地说,@Fredrik。一定有某个“脏”标志没有被设置或者其他类似的问题,导致更改没有被写入元文件,但我不知道该在哪里寻找它。 - Bart
令人印象深刻的脚本! - Fattie
2个回答

7

我尝试了这段代码并得出结论,这是一个bug。我联系了Unity,他们的回复是:

目前,这是一个已提交的bug,我们的开发团队正在调查。似乎这个bug是由于AssetDatabase.SaveAssets()没有保存更改所导致的。

解决方法是手动执行以下步骤:

在OnPostprocessAllAssets被调用时处理和保存数据:

1.创建一个Json文件settings来保存设置(如果不存在)。

2.当OnPostprocessAllAssets被调用时,加载旧的Json文件settings。

3.检查importedAssets参数中的文件是否存在,如果存在,则跳过此步骤。

4.将图标应用到资源上。

5.循环遍历已加载的Json文件settings,并检查其是否包含importedAssets参数中的文件。

如果它包含已加载的文件,则修改该设置并保存它。如果没有,则将其添加到列表中,然后保存它。

6.使用File.Exists检查硬盘上是否存在importedAssets参数中的资源。如果不存在,则从已加载的Json文件settings列表中删除它,然后保存它。

当Unity加载时自动重新应用图标:

1.在IconAssignmentPostProcessor类中添加一个静态构造函数。这个静态构造函数将在编辑器加载时自动调用,并在调用OnPostprocessAllAssets时也会被调用。

2.当构造函数被调用时,创建一个Json文件settings来保存设置(如果不存在)。

3.加载旧的Json文件settings。

4.通过循环遍历已加载的Json文件来重新应用图标。

5.检查已加载的Json文件是否仍然有不在硬盘上的资源。如果有,则从列表中删除该资源,然后保存它。

下面是新的IconAssignmentPostProcessor脚本:

using UnityEngine;
using UnityEditor;
using System.Reflection;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System;

public class IconAssignmentPostProcessor : AssetPostprocessor
{
    // Called when Editor Starts
    static IconAssignmentPostProcessor()
    {
        prepareSettingsDir();
        reloadAllFancyIcons();
    }

    private static string settingsPath = Application.dataPath + "/FancyIconSettings.text";
    private static string fancyIconPath = "Assets/Iconfolder/MyFancyIcon.png";

    private static bool firstRun = true;

    static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
    {
        prepareSettingsDir();

        //Load old settings 
        FancyIconSaver savedFancyIconSaver = LoadSettings();


        Texture2D icon = AssetDatabase.LoadAssetAtPath<Texture2D>(fancyIconPath);

        for (int j = 0; j < importedAssets.Length; j++)
        {
            string asset = importedAssets[j];

            MonoScript script = AssetDatabase.LoadAssetAtPath<MonoScript>(asset);
            if (script != null)
            {
                //Apply fancy Icon
                ApplyIcon(script, icon);

                //Process each asset 
                processFancyIcon(savedFancyIconSaver, fancyIconPath, asset, pathToGUID(asset));
            }
        }

        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();
    }


    public static string pathToGUID(string path)
    {
        return AssetDatabase.AssetPathToGUID(path);
    }

    public static string guidToPath(string guid)
    {
        return AssetDatabase.GUIDToAssetPath(guid);
    }

    public static void processFancyIcon(FancyIconSaver oldSettings, string fancyIconPath, string scriptPath, string scriptGUID)
    {
        int matchIndex = -1;

        if (oldSettings == null)
        {
            oldSettings = new FancyIconSaver();
        }

        if (oldSettings.fancyIconData == null)
        {
            oldSettings.fancyIconData = new List<FancyIconData>();
        }

        FancyIconData fancyIconData = new FancyIconData();
        fancyIconData.fancyIconPath = fancyIconPath;
        fancyIconData.scriptPath = scriptPath;
        fancyIconData.scriptGUID = scriptGUID;

        //Check if this guid exist in the List already. If so, override it with the match index
        if (containsGUID(oldSettings, scriptGUID, out matchIndex))
        {
            oldSettings.fancyIconData[matchIndex] = fancyIconData;
        }
        else
        {
            //Does not exist, add it to the existing one
            oldSettings.fancyIconData.Add(fancyIconData);
        }

        //Save the data
        SaveSettings(oldSettings);

        //If asset does not exist, delete it from the json settings
        for (int i = 0; i < oldSettings.fancyIconData.Count; i++)
        {
            if (!assetExist(scriptPath))
            {
                //Remove it from the List then save the modified List
                oldSettings.fancyIconData.RemoveAt(i);
                SaveSettings(oldSettings);
                Debug.Log("Asset " + scriptPath + " no longer exist. Deleted it from JSON Settings");
                continue; //Continue to the next Settings in the List
            }
        }
    }

    //Re-loads all the fancy icons
    public static void reloadAllFancyIcons()
    {
        if (!firstRun)
        {
            firstRun = false;
            return; //Exit if this is not first run
        }

        //Load old settings 
        FancyIconSaver savedFancyIconSaver = LoadSettings();

        if (savedFancyIconSaver == null || savedFancyIconSaver.fancyIconData == null)
        {
            Debug.Log("No Previous Fancy Icon Settings Found!");
            return;//Exit
        }


        //Apply Icon Changes
        for (int i = 0; i < savedFancyIconSaver.fancyIconData.Count; i++)
        {
            string asset = savedFancyIconSaver.fancyIconData[i].scriptPath;

            //If asset does not exist, delete it from the json settings
            if (!assetExist(asset))
            {
                //Remove it from the List then save the modified List
                savedFancyIconSaver.fancyIconData.RemoveAt(i);
                SaveSettings(savedFancyIconSaver);
                Debug.Log("Asset " + asset + " no longer exist. Deleted it from JSON Settings");
                continue; //Continue to the next Settings in the List
            }

            string tempFancyIconPath = savedFancyIconSaver.fancyIconData[i].fancyIconPath;

            Texture2D icon = AssetDatabase.LoadAssetAtPath<Texture2D>(tempFancyIconPath);
            MonoScript script = AssetDatabase.LoadAssetAtPath<MonoScript>(asset);
            if (script == null)
            {
                continue;
            }

            Debug.Log(asset);
            ApplyIcon(script, icon);
        }
        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();
    }

    private static void ApplyIcon(MonoScript script, Texture2D icon)
    {
        PropertyInfo inspectorModeInfo = typeof(SerializedObject).GetProperty("inspectorMode", BindingFlags.NonPublic | BindingFlags.Instance);
        SerializedObject serializedObject = new SerializedObject(script);
        inspectorModeInfo.SetValue(serializedObject, InspectorMode.Debug, null);
        SerializedProperty iconProperty = serializedObject.FindProperty("m_Icon");
        iconProperty.objectReferenceValue = icon;
        serializedObject.ApplyModifiedProperties();
        serializedObject.Update();
        EditorUtility.SetDirty(script);
        Debug.Log("Applied Fancy Icon to: " + script.name);
    }

    //Creates the Settings File if it does not exit yet
    private static void prepareSettingsDir()
    {
        if (!File.Exists(settingsPath))
        {
            File.Create(settingsPath);
        }
    }

    public static void SaveSettings(FancyIconSaver fancyIconSaver)
    {
        try
        {
            string jsonData = JsonUtility.ToJson(fancyIconSaver, true);
            Debug.Log("Data: " + jsonData);

            byte[] jsonByte = Encoding.ASCII.GetBytes(jsonData);
            File.WriteAllBytes(settingsPath, jsonByte);
        }
        catch (Exception e)
        {
            Debug.Log("Settings not Saved: " + e.Message);
        }
    }

    public static FancyIconSaver LoadSettings()
    {
        FancyIconSaver loadedData = null;
        try
        {
            byte[] jsonByte = File.ReadAllBytes(settingsPath);
            string jsonData = Encoding.ASCII.GetString(jsonByte);
            loadedData = JsonUtility.FromJson<FancyIconSaver>(jsonData);
            return loadedData;
        }
        catch (Exception e)
        {
            Debug.Log("No Settings Loaded: " + e.Message);
        }
        return loadedData;
    }

    public static bool containsGUID(FancyIconSaver fancyIconSaver, string guid, out int matchIndex)
    {
        matchIndex = -1;

        if (fancyIconSaver == null || fancyIconSaver.fancyIconData == null)
        {
            Debug.Log("List is null");
            return false;
        }

        for (int i = 0; i < fancyIconSaver.fancyIconData.Count; i++)
        {
            if (fancyIconSaver.fancyIconData[i].scriptGUID == guid)
            {
                matchIndex = i;
                return true;
            }
        }
        return false;
    }

    public static bool assetExist(string path)
    {
        return File.Exists(path);
    }

    [Serializable]
    public class FancyIconSaver
    {
        public List<FancyIconData> fancyIconData;
    }

    [Serializable]
    public class FancyIconData
    {
        public string fancyIconPath;
        public string scriptPath;
        public string scriptGUID;
    }
}

当Unity重启时,应该显示这些漂亮的图标。


1
这真是太棒了。我本来就怀疑有个bug,但毕竟我使用的是一些非标准的代码来完成我的工作。非常感谢你的帮助。 - Bart
我在编辑器中尝试了所有的保存API后,仍然无法正常工作,怀疑是个bug。后来Unity证实了这一点。很高兴能够帮上忙。 - Programmer

0

1
感谢您的输入。然而,我并不完全相信这是完全相同的问题。我可以理解ScriptableObject的情况,但这与资产和SerializedObject不同。(至少我认为如此)。我会进一步查看并保持更新。 - Bart
1
祝你好运!很想听听结果。 - Fredrik Schön

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接