I’ve been working with Prefab Variants, which let you create prefabs which “extend” other prefabs, allowing the changes on the base prefab to automatically trickle down to the variant. Extremely useful feature but Unity unfortunately makes it quite hard to tell what’s a base prefab and what’s a variant.

I decided to try to fix this by adding the Prefab Variant icon to the thumbnail of the asset so I can tell what’s what at a glance.

Before

img

After

img

Below is a tutorial for how you can do this too.

Bind to EditorApplication.projectWindowItemOnGUI

Every time an item in the project window is drawn, the projectWindowItemOnGUI callback is triggered, giving us metadata about what has been drawn. We want to bind to this callback so we can draw an icon on the assets which are prefab variants.

To do this, let’s make a new class which takes advantage of the InitializeOnLoad attribute.

[InitializeOnLoad]
public class AddPrefabVariantThumbnail
{
}

The InitalizeOnLoad tells Unity to call the static constructors for this class when this script is loaded. So let’s write a static constructor which binds to projectWindowItemOnGUI and a static function which is called when the callback is triggered.

[InitializeOnLoad]
public class AddPrefabVariantThumbnail
{
    static AddPrefabVariantThumbnail()
    {
        // Make sure we unbind first since we could bind twice when scripts are reloaded
        EditorApplication.projectWindowItemOnGUI -= ProjectWindowItemOnGUICallback;
        EditorApplication.projectWindowItemOnGUI += ProjectWindowItemOnGUICallback;
    }

    static void ProjectWindowItemOnGUICallback(string guid, Rect selectionRect)
    {
    }
}

Filter out assets we don’t care about

This callback is going to be called for every single asset which is displayed in the project folder, which can be either a file or directory. In my case I only care about prefab variants, so let’s filter everything else out. First, get the filename of this asset:

static void ProjectWindowItemOnGUICallback(string guid, Rect selectionRect)
{
    string fileName = AssetDatabase.GUIDToAssetPath(guid);
}

We need to check to see if we’re a directory. Since we don’t want to add any icons to directories, return if we find one.


// If we're a directory, ignore it.
if (fileName.LastIndexOf('.') == -1)
{
    return;
}

Now, let’s try loading the asset as a GameObject. All other assets besides GameObjects (sprites, animations, etc.) will fail to load.

GameObject go = AssetDatabase.LoadAssetAtPath<GameObject>(fileName);
if (go == null)
{
    return;
}

If we successfully loaded the prefab, we can use PrefabUtility to check the type of the prefab to make sure it’s a variant.

// If we're not a variant, ignore it.
if (PrefabUtility.GetPrefabAssetType(go) != PrefabAssetType.Variant)
{
    return;
}

At this point, we know we have a variant, now we can try adding the icon.

Add the Prefab Variant Icon to the asset thumbnail

One of the parameters into ProjectWindowItemOnGUICallback is Rect selectionRect. This represents the rectangle that the asset thumbnail is drawn in. We can use this to position the prefab variant icon ontop of the thumnail.

First, create an instance of the icon we want to set.

GUIContent icon = EditorGUIUtility.IconContent("PrefabVariant Icon");

The "Prefab Variant" string comes from this page which lists many of the built in editor icons Unity comes with.

The icon needs an associated Rect that defines the position and size of the icons on the screen. I’ve chosen to place the icon in the bottom right of the thumbnail. Since the icon’s origin is its upper left corner, we need to position the rect based on that.

Vector2 selectionPosition = selectionRect.position;
Vector2 selectionSize = selectionRect.size;

// The x position should be a little left of the right edge.
float xPosition = selectionPosition.x + selectionSize.x - selectionSize.x / 3.5f;

// The y position should be a little above of the bottom edge.
float yPosition = selectionPosition.y + selectionSize.y - selectionSize.y / 2;

Vector2 position = new Vector2(xPosition, yPosition);

Rect newRect = selectionRect;
newRect.position = position;

// This is a nice number to reduce the size of the rect by to make the icon smaller
newRect.size /= 3.5f;

With all this in place, we just need to make a Label with the icon and the rect.

GUI.Label(newRect, icon);

And that’ll place the icon right on top of the asset!

One small detail is that when the project window is in “list” mode:

img

We don’t want to show our icons since there’s no room for it. We know we’re in this mode when the selection rect height is way longer than the width. So we can add this condition to the top of the function

// If the width is too wide, we don't need to put the icon.
if (selectionRect.width / selectionRect.height >= 2)
{
    return;
}

Putting it all together

After all the steps above, our class looks like this:

    [InitializeOnLoad]
    public class PrefabVariantThumbnailModification
    {
        static PrefabVariantThumbnailModification()
        {
            EditorApplication.projectWindowItemOnGUI -= ProjectWindowItemOnGUICallback;
            EditorApplication.projectWindowItemOnGUI += ProjectWindowItemOnGUICallback;
        }

        static void ProjectWindowItemOnGUICallback(string guid, Rect selectionRect)
        {
            // If the width is too wide, we don't need to put the icon.
            if (selectionRect.width / selectionRect.height >= 2)
            {
                return;
            }

            string fileName = AssetDatabase.GUIDToAssetPath(guid);

            // If we're a directory, ignore it.
            if (fileName.LastIndexOf('.') == -1)
            {
                return;
            }

            // If we're not a prefab, ignore it
            GameObject go = AssetDatabase.LoadAssetAtPath<GameObject>(fileName);
            if (go == null)
            {
                return;
            }

            // If we're not a variant, ignore it.
            if (PrefabUtility.GetPrefabAssetType(go) != PrefabAssetType.Variant)
            {
                return;
            }

            GUIContent icon = EditorGUIUtility.IconContent("PrefabVariant Icon");

            Vector2 selectionPosition = selectionRect.position;
            Vector2 selectionSize = selectionRect.size;

            // The x position should be a little left of the right edge.
            float xPosition = selectionPosition.x + selectionSize.x - selectionSize.x / 3.5f;

            // The y position should be a little above of the bottom edge.
            float yPosition = selectionPosition.y + selectionSize.y - selectionSize.y / 2;

            Vector2 position = new Vector2(xPosition, yPosition);

            Rect newRect = selectionRect;
            newRect.position = position;

            // This is a nice number to reduce the size of the rect by to make the icon smaller
            newRect.size /= 3.5f;

            GUI.Label(newRect, icon);
        }
    }

And you’re done! You can use this technique with any asset to make thumnails more descriptive at a glance, like sprites or custom scriptable objects.