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?
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.
I think you may find your answer here: http://answers.unity3d.com/questions/344153/save-game-using-scriptable-object-derived-custom-a.html
Unfortunately.