Workflow for TextMesh Pro sprite glyphs?
I have a folder with sprites which I want to use as glyphs in TextMesh Pro textboxes. From time to time, new sprites are added to that folder. I want to organize a workflow in such way that every time I add a sprite called xxx, I could use it as <sprite name="xxx"> in TextMesh.
I tried to create a Sprite Atlas from folder, but I cannot find any way to export the Sprite Atlas into a texture. Currently, I need to place all new sprites manually onto a big texture and re-create the entire TextMesh Sprite Asset file. How can I improve my workflow? (1 - automatically convert an atlas into a texture [Sprite Asset is not created if an atlas is selected, it wants specifically a texture], 2 - refresh the Sprite Asset file so it reflects changes in an underlying texture)
Answer by androniq · May 09, 2020 at 06:57 AM
Finally came out with this solution.
using System.IO;
using System.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.TextCore;
namespace Assets.Scripts.Utilities
{
public class Glypher : MonoBehaviour
{
public int GlyphWidth = 32;
public int GlyphHeight = 32;
public int GlyphsInRow = 8;
public TMP_SpriteAsset Asset;
public void RepackGlyphs()
{
Debug.Log("Repacking glyphs...");
var path = Path.Combine(Directory.GetCurrentDirectory(), @"Assets\Resources\Glyphs");
var outputPath = Path.Combine(Directory.GetCurrentDirectory(), @"Assets\Resources\GlyphBuild\texture.png");
var files = Directory.EnumerateFiles(path, "*.png").ToList();
var rowNumber = (files.Count - 1) / GlyphsInRow + 1;
var completeWidth = GlyphsInRow * GlyphWidth;
var completeHeight = GlyphHeight * rowNumber;
var image = new Texture2D(completeWidth, completeHeight, TextureFormat.BGRA32, false);
var yIndex = rowNumber - 1;
var xIndex = 0;
Asset?.spriteGlyphTable.Clear();
Asset?.spriteCharacterTable.Clear();
uint index = 0;
foreach (var glyph in files)
{
var texture = new Texture2D(GlyphWidth, GlyphHeight);
using (var file = new FileStream(glyph, FileMode.Open))
{
using (var memstream = new MemoryStream())
{
file.CopyTo(memstream);
var tbytes = memstream.ToArray();
texture.LoadImage(tbytes);
}
}
var pixels = texture.GetPixels32();
image.SetPixels32(xIndex * GlyphWidth, yIndex * GlyphHeight, GlyphWidth, GlyphHeight, pixels);
var spriteGlyph = new TMP_SpriteGlyph
{
glyphRect = new GlyphRect { width = GlyphWidth, height = GlyphHeight - 1, x = xIndex * GlyphWidth, y = yIndex * GlyphHeight },
metrics = new GlyphMetrics { width = GlyphWidth, height = GlyphHeight - 1, horizontalBearingY = 28, horizontalBearingX = 0, horizontalAdvance = 32 },
index = index
};
Asset?.spriteGlyphTable.Add(spriteGlyph);
var spriteCharacter = new TMP_SpriteCharacter(0, spriteGlyph)
{
name = Path.GetFileNameWithoutExtension(glyph),
glyphIndex = index++
};
Asset?.spriteCharacterTable.Add(spriteCharacter);
xIndex++;
if (xIndex >= GlyphsInRow)
{
xIndex = 0;
yIndex--;
}
}
var bytes = image.EncodeToPNG();
using (var stream = new FileStream(outputPath, FileMode.Create))
{
stream.Write(bytes, 0, bytes.Length);
}
if (Asset == null)
Debug.LogWarning("Right-click the created texture, Create > TextMeshPro > Sprite Asset and specify the resulting asset in Glypher and in TMP Settings, then repack glyphs again");
else
Debug.Log("Repacking glyphs done!");
}
}
}
Then, I have a custom editor to run this script from a button click:
using UnityEditor;
using UnityEngine;
namespace Assets.Scripts.Utilities
{
[CustomEditor(typeof(Glypher))]
public class GlypherEditor : Editor
{
public override void OnInspectorGUI()
{
DrawDefaultInspector();
var glypher = (Glypher)target;
if (GUILayout.Button("Repack glyphs"))
{
glypher.RepackGlyphs();
}
}
}
}
Finally, I create the sprite asset manually - but only for the first time. Then I specify this asset in Glypher component properties (I have a separate GameObject for devtime utilities, and this GameObject can be safely removed on game startup) AND in TMP Settings (possibly there's a way to do that programmatically also, but since it's a one-time action, I didn't investigate this possibility). Every time new pngs are added to Assets\Resources\Glyphs folder, I then click Repack Glyphs on Glypher inspector, and my glyph table gets updated. Names of the glyphs are taken from file names. A few notes:
This code relies completely on assumption that all your glyphs are the same size. If it's not 32*32, you have to specify it in GlyphWidth&GlyphHeight properties of the Glypher.
Possibly a SpriteAtlas can be used to pack textures more efficiently, but again - I didn't run into limitations yet, so didn't check this possibility.
For the first run you may leave texture property unset - it will create the texture and instruct you of further actions in the log.
Hope it's helpful for anyone else.