Custom Icon on ScriptableObject

Hey guys, i doing some itens for my game make the development easer, i wish add custom icons to my itens, which are ScriptableObjects, how i can do this? i know the trick of put the icon on Gizmo folder with the same name as the script, but the icons should be different for different itens which have the same script

Hi.

It’s possible!

Short history:

I downloaded and explore this project:

I noticed that in this project, icons from scriptable objects are displayed in the inspector:
197008-example-0.png

After, i found some editor script, and edited him for my project.

I don’t understand how it works, but it works. =)
197009-primer.png

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System;
using System.Reflection;
using Object = UnityEngine.Object;

[CustomEditor(typeof(item_so),true)]
[CanEditMultipleObjects]
public class ItemSOEditor : Editor
{
    private item_so item { get { return (target as item_so); } }

    public override Texture2D RenderStaticPreview(string assetPath,Object[] subAssets,int width,int height)
    {
        if(item.standart_item_struct.item_icon!=null)
        {
            Type t = GetType("UnityEditor.SpriteUtility");
            if(t!=null)
            {
                MethodInfo method = t.GetMethod("RenderStaticPreview",new Type[] { typeof(Sprite),typeof(Color),typeof(int),typeof(int) });
                if(method!=null)
                {
                    object ret = method.Invoke("RenderStaticPreview",new object[] { item.standart_item_struct.item_icon,Color.white,width,height });
                    if(ret is Texture2D)
                        return ret as Texture2D;
                }
            }
        }
        return base.RenderStaticPreview(assetPath,subAssets,width,height);
    }

    private static Type GetType(string TypeName)
    {
        var type = Type.GetType(TypeName);
        if(type!=null)
            return type;

        if(TypeName.Contains("."))
        {
            var assemblyName = TypeName.Substring(0,TypeName.IndexOf('.'));
            var assembly = Assembly.Load(assemblyName);
            if(assembly==null)
                return null;
            type=assembly.GetType(TypeName);
            if(type!=null)
                return type;
        }

        var currentAssembly = Assembly.GetExecutingAssembly();
        var referencedAssemblies = currentAssembly.GetReferencedAssemblies();
        foreach(var assemblyName in referencedAssemblies)
        {
            var assembly = Assembly.Load(assemblyName);
            if(assembly!=null)
            {
                type=assembly.GetType(TypeName);
                if(type!=null)
                    return type;
            }
        }
        return null;
    }
}

Hi there @arkadsgames. You can do it without the Gizmos folder. Just select the ScriptableObject script (not the ScriptableObject asset!) and select its icon in the Inspector. That will change that script’s icon as well as all icons of respective ScriptableObject assets!

Additionally, you can visit this thread for more info, it has lots of gold in it: https://forum.unity.com/threads/custom-scriptableobject-icons-thumbnail.256246/

if your current Unity version doesn’t publicly expose EditorGUIUtility.SetIconForObject

here’s a utility window that makes use of the same API using Reflection:

using System.Reflection;
using UnityEditor;
using UnityEngine;


internal class EditorGUIUtilityInternals : EditorWindow
{
    private MethodInfo setIcon, getIcon, copyIcon;
    private Texture2D icon;
    private Object target;
    private bool hasIcon;
    
    
    [MenuItem("EditorGUIUtility/Internals/SetIconForObject")]
    private static void GetWindow()
    {
        var rect = new Rect(Screen.width/2F, Screen.height/2F, 350F, 130F);
        const string title = "EditorGUIUtility Internals";
        
        GetWindowWithRect<EditorGUIUtilityInternals>(rect, true, title, true).ShowUtility();
    }
    
    
    private void OnEnable()
    {
        const BindingFlags bindings = BindingFlags.NonPublic | BindingFlags.Static;
        var utility = typeof(EditorGUIUtility);
        
        setIcon = utility.GetMethod("SetIconForObject", bindings);
        getIcon = utility.GetMethod("GetIconForObject", bindings);
        copyIcon = typeof(MonoImporter).GetMethod("CopyMonoScriptIconToImporters", bindings);
    }
    
    
    private void OnGUI()
    {
        EditorGUI.BeginChangeCheck();

        target = EditorGUILayout.ObjectField("Target Object", target, typeof(Object), true);

        if (target && EditorGUI.EndChangeCheck()) RefreshHasIcon();
        
        EditorGUILayout.Space(EditorGUIUtility.singleLineHeight);
        
        icon = (Texture2D)EditorGUILayout.ObjectField("Icon", icon, typeof(Texture2D), false);

        EditorGUILayout.BeginHorizontal();
        {
            GUI.enabled = target && icon;
            if (GUILayout.Button("Set Icon For Target"))
                SetOrRemoveIconForObject(icon); // Assign

            GUI.enabled = hasIcon;
            if (GUILayout.Button("Remove Icon From Target"))
                SetOrRemoveIconForObject(); // Unset
        }
        EditorGUILayout.EndHorizontal();
    }
    
    
    private void SetOrRemoveIconForObject(Texture2D texture = null)
    {
        var message = texture == null
            ? $"{target.name}'s Icon is About to be Unbound from"
            : $"{icon.name} is About to be Bound to {target.name}";
        
        if (EditorUtility.DisplayDialog(titleContent.text, message, "Gotcha!", "Nope..."))
        {
            setIcon?.Invoke(null, new[] { target, texture });
            
            var targetPath = AssetDatabase.GetAssetPath(target);
            
            var importer = (MonoImporter)AssetImporter.GetAtPath(targetPath);
            
            _ = copyIcon?.Invoke(null, new[] { importer.GetScript() });
            
            importer.SaveAndReimport();
            
            RefreshHasIcon();
        }
    }
    
    
    private void RefreshHasIcon()
    {
        var tempIcon = (Texture2D)getIcon?.Invoke(null, new[] { target });
        
        hasIcon = tempIcon != null;
    }
}

how to display the utility and a preview of:

how to display the utility and a preview of

Hi, i’m working on a cards game where there are different “roles”. Every scriptable object needs to have a custom image. How do i do it?

There is a new API for this in 2021.2 that might do what you are looking for. It even tells you how to make it persistent across script reloads :wink:

Edit: Okay so it turns out this is really really jank. As far as I can tell, Unity doesn’t want you to instance these icons. On compile/load, all of the assets will be set to the same icon, even if you properly set each one individually. You have to inspect them by clicking on them individually to get them to update to their true icons. If anyone has a solution to this please let me know :confused:

Use EditorGUIUtility.SetIconForObject is just working. Just do somethin like this:

[CreateAssetMenu(fileName = "NewCustomScriptableObject", menuName = "YourMenu/CustomScriptableObject")]
public class TestSO : ScriptableObject
{
    public int A;

    private void OnEnable()
    {
        Texture2D icon = AssetDatabase.LoadAssetAtPath<Texture2D>("Assets/Games/Gizmos/TestSO Icon.png");
        EditorGUIUtility.SetIconForObject(this, icon);
    }
}

and whenever you create a SO asset then it’ll show up with custom icon.