Search Unity

Setting Editor Icons for Scripts Embedded in DLLs

Discussion in 'Editor & General Support' started by FrederikVerhoef, Jun 22, 2017.

  1. FrederikVerhoef

    FrederikVerhoef

    Joined:
    Jul 31, 2014
    Posts:
    6
    Hi all,

    I'm not sure if a similar solution has been posted before, but I couldn't find anything of the sort anywhere, so I thought I would share it here. Who knows, it might just help someone creating an assembly for their asset :)

    So, the problem: how do I change the script and game object icons shown in the Inspector for classes embedded in DLLs? Ideally, I want the Inspector to use an icon embedded in my DLL.

    For separate script files added as assets, you can do this by using Debug mode in the Inspector (which will show some hidden properties for your script, including the icon assigned to it), but for classes embedded in a custom DLL, this is not possible.

    The solution turned out to be quite simple, reusing code found on the forums for setting icons on game objects. This was done in Unity 5.6.1 but I'm fairly certain it will work in most previous versions as well.

    We need 3 things: 1) a .png image added as an embedded resource to our Class Library Project in Visual Studio, 2) an IconManager class (as noted, based on code found on the forums) allowing us to set Icons on any Unity Object, and then 3) a call to this from an Editor script.

    Here's the IconManager:

    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using System.IO;
    4. using System.Reflection;
    5. using UnityEditor;
    6. using UnityEngine;
    7.  
    8. namespace Example.Editor
    9. {
    10.     /// <summary>
    11.     /// Icon manager.
    12.     /// </summary>
    13.     public static class IconManager
    14.     {
    15.         /// <summary>
    16.         /// Read all bytes in this stream.
    17.         /// </summary>
    18.         /// <param name="stream">The stream.</param>
    19.         /// <returns>All bytes in the stream.</returns>
    20.         public static byte[] ReadAllBytes(this Stream stream)
    21.         {
    22.             long originalPosition = 0;
    23.  
    24.             if (stream.CanSeek)
    25.             {
    26.                 originalPosition = stream.Position;
    27.                 stream.Position = 0;
    28.             }
    29.  
    30.             try
    31.             {
    32.                 byte[] readBuffer = new byte[4096];
    33.  
    34.                 int totalBytesRead = 0;
    35.                 int bytesRead;
    36.  
    37.                 while ((bytesRead = stream.Read(readBuffer, totalBytesRead, readBuffer.Length - totalBytesRead)) > 0)
    38.                 {
    39.                     totalBytesRead += bytesRead;
    40.  
    41.                     if (totalBytesRead == readBuffer.Length)
    42.                     {
    43.                         int nextByte = stream.ReadByte();
    44.                         if (nextByte != -1)
    45.                         {
    46.                             byte[] temp = new byte[readBuffer.Length * 2];
    47.                             Buffer.BlockCopy(readBuffer, 0, temp, 0, readBuffer.Length);
    48.                             Buffer.SetByte(temp, totalBytesRead, (byte)nextByte);
    49.                             readBuffer = temp;
    50.                             totalBytesRead++;
    51.                         }
    52.                     }
    53.                 }
    54.  
    55.                 byte[] buffer = readBuffer;
    56.                 if (readBuffer.Length != totalBytesRead)
    57.                 {
    58.                     buffer = new byte[totalBytesRead];
    59.                     Buffer.BlockCopy(readBuffer, 0, buffer, 0, totalBytesRead);
    60.                 }
    61.                 return buffer;
    62.             }
    63.             finally
    64.             {
    65.                 if (stream.CanSeek)
    66.                 {
    67.                     stream.Position = originalPosition;
    68.                 }
    69.             }
    70.         }
    71.  
    72.         private static readonly Dictionary<string, Texture2D> _embeddedIcons = new Dictionary<string, Texture2D>();
    73.  
    74.         /// <summary>
    75.         /// Get the embedded icon with the given resource name.
    76.         /// </summary>
    77.         /// <param name="resourceName">The resource name.</param>
    78.         /// <returns>The embedded icon with the given resource name.</returns>
    79.         public static Texture2D GetEmbeddedIcon(string resourceName)
    80.         {
    81.             var assembly = Assembly.GetExecutingAssembly();
    82.  
    83.             Texture2D icon;
    84.             if (!_embeddedIcons.TryGetValue(resourceName, out icon) || icon == null)
    85.             {
    86.                 byte[] iconBytes;
    87.                 using (var stream = assembly.GetManifestResourceStream(resourceName))
    88.                     iconBytes = stream.ReadAllBytes();
    89.                 icon = new Texture2D(128, 128);
    90.                 icon.LoadImage(iconBytes);
    91.                 icon.name = resourceName;
    92.  
    93.                 _embeddedIcons[resourceName] = icon;
    94.             }
    95.  
    96.             return icon;
    97.         }
    98.  
    99.         /// <summary>
    100.         /// Set the icon for this object.
    101.         /// </summary>
    102.         /// <param name="obj">The object.</param>
    103.         /// <param name="texture">The icon.</param>
    104.         public static void SetIcon(this Object obj, Texture2D texture)
    105.         {
    106.             var ty = typeof(EditorGUIUtility);
    107.             var mi = ty.GetMethod("SetIconForObject", BindingFlags.NonPublic | BindingFlags.Static);
    108.             mi.Invoke(null, new object[] { obj, texture });
    109.         }
    110.  
    111.         /// <summary>
    112.         /// Set the icon for this object from an embedded resource.
    113.         /// </summary>
    114.         /// <param name="obj">The object.</param>
    115.         /// <param name="texture">The icon.</param>
    116.         public static void SetIcon(this Object obj, string resourceName)
    117.         {
    118.             SetIcon(obj, GetEmbeddedIcon(resourceName));
    119.         }
    120.  
    121.         /// <summary>
    122.         /// Get the icon for this object.
    123.         /// </summary>
    124.         /// <param name="obj">The object.</param>
    125.         /// <returns>The icon for this object.</returns>
    126.         public static Texture2D GetIcon(this Object obj)
    127.         {
    128.             var ty = typeof(EditorGUIUtility);
    129.             var mi = ty.GetMethod("GetIconForObject", BindingFlags.NonPublic | BindingFlags.Static);
    130.             return mi.Invoke(null, new object[] { obj }) as Texture2D;
    131.         }
    132.  
    133.         /// <summary>
    134.         /// Remove this icon's object.
    135.         /// </summary>
    136.         /// <param name="obj">The object.</param>
    137.         public static void RemoveIcon(this Object obj)
    138.         {
    139.             SetIcon(obj, (Texture2D)null);
    140.         }
    141.     }
    142. }
    143.  
    Using it from an Editor script is done as follows. This assumes we want to set the icon for the ExampleBehaviour script, which inherits from MonoBehaviour, as well as any GameObject the script is on. Also, it assumes and that the icon (Icon.png) is added to the DLL in a folder called Resources, has it's build action set to Embedded Resource, and the default namespace of the DLL is Example.Editor.

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEditor;
    3. using UnityEngine;
    4.  
    5. namespace Example.Editor
    6. {
    7.     [CustomEditor(typeof(ExampleBehaviour))]
    8.     public class ExampleEditor : UnityEditor.Editor
    9.     {
    10.  
    11.         private void SetIcons()
    12.         {
    13.             // this sets the icon on the game object containing our behaviour
    14.             (target as ExampleBehaviour).gameObject.SetIcon("Example.Editor.Resources.Icon.png");
    15.  
    16.             // this sets the icon on the script (which normally shows the blank page icon)
    17.             MonoScript.FromMonoBehaviour(target as ExampleBehaviour).SetIcon("Example.Editor.Resources.Icon.png");
    18.         }
    19.  
    20.         protected virtual void Awake()
    21.         {
    22.             SetIcons();
    23.         }
    24.     }
    25. }
    26.  
    The result (the Map class is in a DLL, the icon shown is embedded in the DLL):

    CustomIcons.png

    Let me know if it doesn't work for you for some reason, and I'll see if I can help you out figure out why.

    Cheers,

    Frederik
     
    ModLunar, mgear, kbabilinski and 2 others like this.
  2. kbabilinski

    kbabilinski

    Joined:
    Jul 12, 2012
    Posts:
    19
    Thank you so much ! This is awesome!
     
  3. Jiraiyah

    Jiraiyah

    Joined:
    Mar 4, 2013
    Posts:
    175
    Does someone knows how to do it for the embedded scriptable objects? I have a dll that contains scriptable objects and menu items to create those in project view. how to set icons for them?

    Screenshot_3.png

    Code (CSharp):
    1. public class SacredTileSet : ScriptableObject
    I mean, when you put the same solution without dll into a unity project you can manually set the icon for the script itself and any instance of it that is being generated would have the same icon, but how to do it when those scripts for scriptable objects now sit in the dll? The solution in the OP may not work in this case simply because ScriptabeObjuct directly inherits from Object and that class don't have the same method to be called????
     
    Last edited: Jun 19, 2018
  4. FrederikVerhoef

    FrederikVerhoef

    Joined:
    Jul 31, 2014
    Posts:
    6
    The problem is in the editor; try the following to make this work with a ScriptableObject:

    Code (CSharp):
    1.     [CustomEditor(typeof(ExampleObject))]
    2.     public class ExampleEditor : UnityEditor.Editor
    3.     {
    4.         private void SetIcons()
    5.         {
    6.             // this sets the icon on the game object containing our behaviour
    7.             (target as ExampleObject).SetIcon("Example.Editor.Resources.Icon.png");
    8.  
    9.             // this sets the icon on the script (which normally shows the blank page icon)
    10.             MonoScript.FromScriptableObject(target as ExampleObject).SetIcon("Example.Editor.Resources.Icon.png");
    11.         }
    12.  
    13.         protected virtual void Awake()
    14.         {
    15.             SetIcons();
    16.         }
    17.     }
     
    Jiraiyah likes this.
  5. Estecka

    Estecka

    Joined:
    Oct 11, 2013
    Posts:
    62
    So, when it comes down to changing a monoscript'ss icon, we need a custom editor to reassign the icon everytime on awake? There are no way to get the changes to stick permanently ?
    (At first I tried running
    SetIcon
    via an EditorWindow, but the icon would reset after refreshing the assets.)
     
  6. Chronarch_

    Chronarch_

    Joined:
    Aug 15, 2017
    Posts:
    1
    Very impressive!
     
  7. SonicBloomEric

    SonicBloomEric

    Joined:
    Sep 11, 2014
    Posts:
    1,090
    Please see this post for the native way of handling this: modifying the DLL's meta file to add settings for the iconMap.

    That you cannot assign icons to classes in a DLL through the same approach as with a raw script file is apparently a bug (according to some Unity engineers I've spoken with).