Hi! I wanted to share the approach I’m currently taking to include Editor textures for components in the Inspector and Gizmos.
Motivation
What’s driving my motivation is that I’m a tool-scripting Asset Store developer, so all textures included in my packages are not supposed to be used by the end-user.
If I include textures as standard objects, these can be seen in the floating window of any Texture object picker, which pollutes their project and messes their organisation.
Our approach
So, instead of dropping in a PNG texture inside the Unity folder and using the path to that texture in the Editor script, I create a byte[ ]
array that contains the raw data of the PNG and reconstruct it whenever it’s needed, using the Texture2D.LoadRawTextureData
.
Then, I can easily create a script that inherits from a custom class called TextureRaw and use it to cache the values in a static dictionary, in case they are later needed.
public abstract class TextureRaw
{
private static readonly Dictionary<int, Texture2D> Cache = new Dictionary<int, Texture2D>();
protected abstract byte[] Raw { get; }
public Texture2D Texture
{
get
{
// uses the Cache dictionary to check if the texture has been loaded.
// If so, return the cached value. If not, load it from Raw, save
// it in the Cache dictionary using its hashcode as the key and return it.
// Note: So far hashcode doesn't give collisions, but I guess it could
// return a false collision. This may need some further thought.
}
}
}
Once we have this class defined, it’s very easy to define a new texture. For example, let’s say we want to display a small “chevron” icon for our custom Inspector. We can create a new class ChevronTexture that inherits from TextureRaw and fill the Raw field with the de raw data obtained using the Texture2D.GetRawTextureData(…) method.
public class ChevronTexture : TextureRaw
{
protected override Raw = new byte[]
{
0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,
0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,
0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,
0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,
0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,
0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,
0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,
...
};
}
Now, any time I need to use the Chevron icon in my custom scripts, all I need to do is to reference a ChevronTextue instance and get the Texture property:
public class MyScriptEditor : UnityEditor.Editor
{
private static readonly ChevronTexture chevron = new ChevronTexture();
public override VisualElement CreateInspectorGUI()
{
Image myImage = new Image()
{
image = chevron.Texture // <--- this is where the magic happens
};
Label label = new Label("Hello World");
VisualElement container = new VisualElement();
container.Add(image);
container.Add(label);
return container;
}
}
Further thoughts
Once I had this built I quickly realised that I could easily feed a white texture as the source image and tint it with the color I needed for different cases, allowing me to have a wide variety of colors using the same texture source.
In order to save the different cached textures inside the Cache
dictionary with different colors, all I need to do is to create a key that, instead of being the hashcode of the texture, it’s the XOR of the hascode of the texture and the hashcode of the color. This returns a unique (note to self: Does it? There might be collisions) but predictable key that can be used to store the texture inside the dictionary.
Moreover, and this is a bit our of the scope of what I wanted to share, but one could even create layers of textures that have different colors and generate a new texture. For example, you could have a “Mouse” texture as well as an “Arrow pointing to the right” texture and an “Arrow pointing to the left” texture.
With these, one could easily create new “textures” by overlaying them one on top of the other, with different colors. There’s a bit of black magic that allows to set the outline of the arrow as transparent so there’s a clear distinction between the lower and the upper texture.
One last thought
While writing this, it has occurred to me that it would be even better if I created an editor script that would generate the new texture class by having a custom [ContextMenuItem] attached to the PNG texture. That way, in order to create new textures all I would need to do is to drop in the texture I want in Unity, right click it, choose “Convert to Texture Script” (need to think of a better name ) and it could automatically generate the script based on the name of the selected texture and raw data. I could then delete the PNG, as it’s no longer needed.
Last thought, now for real
I remember reading Lazlo from Ludiq did something similar for Bolt. Though I believe he compiled all textures inside the Resources of the DLL. To be honest, I have no idea how that works or how then these can be accessed. If anyone has a different idea or want to further discuss this, I’m all ears!