Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Programmatically setting and saving the icon associated with an imported asset

Tags:

c#

unity3d

I have some auto-generated data being exported into my Unity project. To help me out I want to assign a custom icon to these assets to clearly identify them. This is of course simply possible via the editor itself, but ideally I'd like this to happen automatically on import.

To this effect I have written an AssetPostProcessor which should take care of this for me. In the example below (which applies to MonoScripts as an example but could apply to any kind of asset), all newly imported scripts will have the MyFancyIcon icon assigned to them. This update is both visible on the script assets themselves, as well as on the MonoBehaviours in the inspector.

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();
    }    
}

And it works just fine, except for one problem. The updates aren't saved when closing the project and reopening it. To the best of my knowledge, either the EditorUtility.SetDirty(script); call should take care of this, or at the very least the AssetDatabase.SaveAssets(); call.

However, looking at the difference between manually assigning an icon (which works) and doing it programmatically, there is an icon field in the meta files associated with the assets which does get set when manually assigning an icon, but not in my scripted case. (In the scripted case the meta files aren't even updated)

So what gives? Do I have to do anything in particular when it's (apparently) only meta data I'm changing? Is there anything simple I'm overlooking?

like image 586
Bart Avatar asked Dec 02 '16 13:12

Bart


1 Answers

Experimented with this code and concluded that this is a bug. Made contact with Unity and this is their reply:

Currently , it is a submitted bug and Our Developer Team is investigating it. It seems that this bug seems to happen because of AssetDatabes.SaveAssets() does not save changes.

The work around is to do this manually.

Processing and Saving Data when OnPostprocessAllAssets is called:

1.Create a Json file settings that will hold the settings if it does not exist.

2.When OnPostprocessAllAssets is called, load old Json file settings.

4.Apply fancy icon to the Asset.

5.Loop over the the loaded Json file settings and check if it contains the file from the importedAssets parameter.

If it contains the loaded file, modify that setting and save it. If it does not, add it to the List then save it.

6.Check if the asset importedAssets does not exist on the hard-drive with File.Exists. If it does not exist, remove it from the List of the loaded Json file settings then save it.

Auto re-apply the fancy icon when Unity loads:

1.Add a static constructor to the IconAssignmentPostProcessor class. This static constructor will automatically be called when Editor loads and also when OnPostprocessAllAssets is invoked.

2.When the constructor is called, create a Json file settings that will hold the settings if it does not exist.

3.Load the old Json file settings.

4.Re-apply the fancy icons by looping through the loaded Json file.

5.Check if the loaded Json file still have assets that is not on the drive. If so, remove that asset from the List then save it.

Below is what the new IconAssignmentPostProcessor script should look like:

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;
    }
}

This should hold the fancy icons when Unity is restarted.

like image 158
Programmer Avatar answered Nov 20 '22 01:11

Programmer