diff --git a/Editor/TmpSpriteAssetData.cs b/Editor/TmpSpriteAssetData.cs new file mode 100644 index 0000000..6370c2c --- /dev/null +++ b/Editor/TmpSpriteAssetData.cs @@ -0,0 +1,14 @@ +using System; + +namespace TMPro +{ + [Serializable] + public class TmpSpriteAssetData + { + public string atlasGuid = string.Empty; + public float bearingX = 0.0f; + public float bearingY = 0.0f; + public float advance = 0.0f; + public float scale = 1.0f; + } +} \ No newline at end of file diff --git a/Editor/TmpSpriteAssetData.cs.meta b/Editor/TmpSpriteAssetData.cs.meta new file mode 100644 index 0000000..56890a0 --- /dev/null +++ b/Editor/TmpSpriteAssetData.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9e7eaf86a85d4995a98502d4540f92a6 +timeCreated: 1697473643 \ No newline at end of file diff --git a/Editor/TmpSpriteAssetGenerator.cs b/Editor/TmpSpriteAssetGenerator.cs index 5677609..4220900 100644 --- a/Editor/TmpSpriteAssetGenerator.cs +++ b/Editor/TmpSpriteAssetGenerator.cs @@ -1,43 +1,20 @@ using System.Collections.Generic; using System.Linq; using UnityEditor; +using UnityEditor.AssetImporters; using UnityEngine; using UnityEngine.TextCore; +using UnityEngine.U2D; +using SpriteUtility = UnityEditor.Sprites.SpriteUtility; namespace TMPro { public static class TmpSpriteAssetGenerator { - public static void Update(TmpSpriteAtlasAsset rootAsset, bool updateSprites = true) + public static void Generate(AssetImportContext ctx, + TMP_SpriteAsset mainSpriteAsset, SpriteAtlas atlas, TmpSpriteAssetData data) { - var rootAssetPath = AssetDatabase.GetAssetPath(rootAsset); - - AddMainSpriteAsset(rootAsset); - - if (updateSprites) - { - UpdateSpriteAssets(rootAsset); - } - - EditorUtility.SetDirty(rootAsset); - AssetDatabase.SaveAssets(); - AssetDatabase.ImportAsset(rootAssetPath); - - foreach (var spriteAsset in AssetDatabase.LoadAllAssetsAtPath(rootAssetPath).OfType()) - { - TMPro_EventManager.ON_SPRITE_ASSET_PROPERTY_CHANGED(true, spriteAsset); - } - } - - private static void UpdateSpriteAssets(TmpSpriteAtlasAsset rootAsset) - { - var rootAssetPath = AssetDatabase.GetAssetPath(rootAsset); - var spriteAtlasPath = AssetDatabase.GetAssetPath(rootAsset.spriteAtlas); - - var subSpriteAssets = AssetDatabase.LoadAllAssetsAtPath(rootAssetPath) - .OfType() - .Where(it => it != rootAsset.mainSpriteAsset) - .ToList(); + var spriteAtlasPath = AssetDatabase.GetAssetPath(atlas); var atlasTextures = AssetDatabase.LoadAllAssetsAtPath(spriteAtlasPath) .OfType() @@ -45,23 +22,19 @@ private static void UpdateSpriteAssets(TmpSpriteAtlasAsset rootAsset) foreach (var atlasTex in atlasTextures) { - var spriteAsset = subSpriteAssets.FirstOrDefault(it => it.name == atlasTex.name); - if (spriteAsset == null) - { - spriteAsset = ScriptableObject.CreateInstance(); - AssetDatabase.AddObjectToAsset(spriteAsset, rootAsset); - } + var spriteAsset = ScriptableObject.CreateInstance(); + ctx.AddObjectToAsset(atlasTex.name, spriteAsset); spriteAsset.hideFlags = HideFlags.HideInHierarchy | HideFlags.NotEditable; spriteAsset.name = atlasTex.name; spriteAsset.version = "1.1.0"; spriteAsset.spriteSheet = atlasTex; - spriteAsset.hashCode = TMP_TextUtilities.GetSimpleHashCode(rootAsset.spriteAtlas.name); + spriteAsset.hashCode = TMP_TextUtilities.GetSimpleHashCode(atlasTex.name); var spriteGlyphTable = new List(); var spriteCharacterTable = new List(); - PopulateSpriteTables(rootAsset, atlasTex, spriteCharacterTable, spriteGlyphTable); + PopulateSpriteTables(ctx, atlas, data, atlasTex, spriteCharacterTable, spriteGlyphTable); spriteAsset.spriteCharacterTable = spriteCharacterTable; spriteAsset.spriteGlyphTable = spriteGlyphTable; @@ -71,87 +44,81 @@ private static void UpdateSpriteAssets(TmpSpriteAtlasAsset rootAsset) if (spriteAsset.material == null) { - AddDefaultMaterial(spriteAsset); - } - - rootAsset.mainSpriteAsset.fallbackSpriteAssets.Add(spriteAsset); - } - - foreach (var spriteAsset in subSpriteAssets) - { - if (atlasTextures.FirstOrDefault(it => it.name == spriteAsset.name) != null) - { - continue; - } - - if (spriteAsset.material != null) - { - Object.DestroyImmediate(spriteAsset.material, allowDestroyingAssets: true); + AddDefaultMaterial(ctx, spriteAsset); } - Object.DestroyImmediate(spriteAsset, allowDestroyingAssets: true); + mainSpriteAsset.fallbackSpriteAssets.Add(spriteAsset); } } - private static void AddMainSpriteAsset(TmpSpriteAtlasAsset rootAsset) - { - var mainSpriteAsset = rootAsset.mainSpriteAsset; - - if (mainSpriteAsset == null) - { - mainSpriteAsset = ScriptableObject.CreateInstance(); - AssetDatabase.AddObjectToAsset(mainSpriteAsset, rootAsset); - - rootAsset.mainSpriteAsset = mainSpriteAsset; - } - - mainSpriteAsset.name = rootAsset.spriteAtlas.name; - mainSpriteAsset.hideFlags = HideFlags.NotEditable; - mainSpriteAsset.fallbackSpriteAssets = new List(); - } - private static void PopulateSpriteTables( - TmpSpriteAtlasAsset rootAsset, + AssetImportContext ctx, + SpriteAtlas atlas, + TmpSpriteAssetData data, Texture2D texture, List spriteCharacterTable, List spriteGlyphTable) { - var spriteCount = rootAsset.spriteAtlas.spriteCount; - var sprites = new Sprite[spriteCount]; - - rootAsset.spriteAtlas.GetSprites(sprites); + var spritesCount = atlas.spriteCount; + var sprites = new Sprite[spritesCount]; + atlas.GetSprites(sprites); for (var i = 0; i < sprites.Length; i++) { - var sprite = sprites[i]; + var spriteAccess = sprites[i]; + + Texture2D spriteTex; + Vector2[] spriteUv; + + try + { + spriteTex = SpriteUtility.GetSpriteTexture(spriteAccess, getAtlasData: true); + spriteUv = SpriteUtility.GetSpriteUVs(spriteAccess, getAtlasData: true); + } + catch + { + // for non-packed sprites SpriteUtility throws exception + // this only happens when the atlas is not baked e.g. + // when new assets were added + // after atlas re-baking this function works as it should again + + spriteTex = null; + spriteUv = null; + + ctx.LogImportError($"Failed to process '{spriteAccess.name}' sprite because it is not packed"); + } - if (sprite.texture != texture) + if (spriteTex == null || spriteUv == null || spriteTex != texture) { continue; } - var spriteName = sprite.name; + var spriteName = spriteAccess.name; if (spriteName.EndsWith("(Clone)")) { spriteName = spriteName.Substring(0, spriteName.Length - "(Clone)".Length); } - var bearing = rootAsset.bearing; - var advance = rootAsset.advance; + var textureRect = new Rect( + x: spriteUv[0].x * spriteTex.width, + y: spriteUv[2].y * spriteTex.height, + width: (spriteUv[1].x - spriteUv[0].x) * spriteTex.width, + height: (spriteUv[1].y - spriteUv[2].y) * spriteTex.height + ); var spriteGlyph = new TMP_SpriteGlyph { index = (uint) i, metrics = new GlyphMetrics( - width: sprite.textureRect.width, - height: sprite.textureRect.height, - bearingX: bearing.x, - bearingY: sprite.textureRect.height - bearing.y, - advance: sprite.textureRect.width + advance), - glyphRect = new GlyphRect(sprite.textureRect), + width: textureRect.width, + height: textureRect.height, + bearingX: data.bearingX, + bearingY: textureRect.height - data.bearingY, + advance: textureRect.width + data.advance), + glyphRect = new GlyphRect(textureRect), scale = 1.0f, - sprite = sprite, + sprite = spriteAccess, }; spriteGlyphTable.Add(spriteGlyph); @@ -159,22 +126,24 @@ private static void PopulateSpriteTables( var spriteCharacter = new TMP_SpriteCharacter(0xFFFE, spriteGlyph) { name = spriteName, - scale = rootAsset.scale, + scale = data.scale, }; spriteCharacterTable.Add(spriteCharacter); } } - private static void AddDefaultMaterial(TMP_SpriteAsset spriteAsset) + private static void AddDefaultMaterial(AssetImportContext ctx, TMP_SpriteAsset spriteAsset) { + var name = $"{spriteAsset.spriteSheet.name} Material"; var shader = Shader.Find("TextMeshPro/Sprite"); var material = new Material(shader); material.SetTexture(ShaderUtilities.ID_MainTex, spriteAsset.spriteSheet); spriteAsset.material = material; + material.name = name; material.hideFlags = HideFlags.HideInHierarchy | HideFlags.NotEditable; - AssetDatabase.AddObjectToAsset(material, spriteAsset); + ctx.AddObjectToAsset(name, material); } } } \ No newline at end of file diff --git a/Editor/TmpSpriteAssetImporter.cs b/Editor/TmpSpriteAssetImporter.cs new file mode 100644 index 0000000..78d1d95 --- /dev/null +++ b/Editor/TmpSpriteAssetImporter.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; +using System.IO; +using UnityEditor; +using UnityEditor.AssetImporters; +using UnityEngine; +using UnityEngine.U2D; + +namespace TMPro +{ + [ScriptedImporter(1, FileExtension)] + public class TmpSpriteAssetImporter : ScriptedImporter + { + public const string FileExtension = "tmpspriteatlas"; + public const string FileExtensionWithDot = ".tmpspriteatlas"; + public const string FileVersion = "1.1.0"; + + private static string[] GatherDependenciesFromSourceFile(string path) + { + var data = JsonUtility.FromJson(File.ReadAllText(path)); + var spriteAtlasPath = AssetDatabase.GUIDToAssetPath(data.atlasGuid); + return new[] {spriteAtlasPath}; + } + + public override void OnImportAsset(AssetImportContext ctx) + { + var mainSpriteAsset = ScriptableObject.CreateInstance(); + + ctx.AddObjectToAsset("main", mainSpriteAsset); + ctx.SetMainObject(mainSpriteAsset); + + mainSpriteAsset.name = "main"; + mainSpriteAsset.hideFlags = HideFlags.NotEditable; + mainSpriteAsset.fallbackSpriteAssets = new List(); + + if (EditorSettings.spritePackerMode != SpritePackerMode.SpriteAtlasV2) + { + ctx.LogImportError("TmpSpriteAtlasAsset can be updates only when spritePackerMode is SpriteAtlasV2"); + return; + } + + var data = JsonUtility.FromJson(File.ReadAllText(ctx.assetPath)); + + if (!GUID.TryParse(data.atlasGuid, out var spriteAtlasGuid)) + { + ctx.LogImportError("Failed to import TmpSpriteAtlasAsset: atlas guid is invalid"); + return; + } + + var spriteAtlas = AssetDatabase.LoadMainAssetAtGUID(spriteAtlasGuid) as SpriteAtlas; + if (spriteAtlas == null) + { + return; + } + + mainSpriteAsset.version = FileVersion; + mainSpriteAsset.hashCode = TMP_TextUtilities.GetSimpleHashCode(spriteAtlas.name); + + TmpSpriteAssetGenerator.Generate(ctx, mainSpriteAsset, spriteAtlas, data); + + mainSpriteAsset.SortGlyphTable(); + mainSpriteAsset.UpdateLookupTables(); + } + } +} \ No newline at end of file diff --git a/Editor/TmpSpriteAssetImporter.cs.meta b/Editor/TmpSpriteAssetImporter.cs.meta new file mode 100644 index 0000000..def40ec --- /dev/null +++ b/Editor/TmpSpriteAssetImporter.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6727a94856ae4059b8b5a3ca938af621 +timeCreated: 1697473940 \ No newline at end of file diff --git a/Editor/TmpSpriteAssetMenu.cs b/Editor/TmpSpriteAssetMenu.cs index a7ab981..bc9c656 100644 --- a/Editor/TmpSpriteAssetMenu.cs +++ b/Editor/TmpSpriteAssetMenu.cs @@ -7,33 +7,6 @@ namespace TMPro { public static class TmpSpriteAssetMenu { - [MenuItem("CONTEXT/TmpSpriteAtlasAsset/Update Sprites", true, 2200)] - private static bool UpdateValidate(MenuCommand command) - { - return command.context is TmpSpriteAtlasAsset; - } - - [MenuItem("CONTEXT/TmpSpriteAtlasAsset/Update Sprites", false, 2200)] - private static void Update(MenuCommand command) - { - if (!Application.isPlaying) - { - Debug.LogError("TmpSpriteAtlasAsset can be updates only in play mode"); - return; - } - - if (EditorSettings.spritePackerMode != SpritePackerMode.SpriteAtlasV2) - { - Debug.LogError("TmpSpriteAtlasAsset can be updates only when spritePackerMode is SpriteAtlasV2"); - return; - } - - if (command.context is TmpSpriteAtlasAsset rootAsset) - { - TmpSpriteAssetGenerator.Update(rootAsset); - } - } - [MenuItem("Assets/Create/TextMeshPro/SpriteAtlas Asset", true, 2200)] private static bool PackValidate() { @@ -44,18 +17,19 @@ private static bool PackValidate() private static void Pack() { var spriteAtlas = (SpriteAtlas) Selection.activeObject; + var spriteAtlasPath = AssetDatabase.GetAssetPath(spriteAtlas); + var spriteAtlasGuid = AssetDatabase.AssetPathToGUID(spriteAtlasPath); + var rootAssetPath = GetSpriteAtlasPathFromSpriteAtlas(spriteAtlas); - var rootAsset = AssetDatabase.LoadAssetAtPath(rootAssetPath); - if (rootAsset == null) + var data = new TmpSpriteAssetData { - rootAsset = ScriptableObject.CreateInstance(); - AssetDatabase.CreateAsset(rootAsset, rootAssetPath); - } + atlasGuid = spriteAtlasGuid, + }; - rootAsset.spriteAtlas = spriteAtlas; + File.WriteAllText(rootAssetPath, JsonUtility.ToJson(data)); - TmpSpriteAssetGenerator.Update(rootAsset, updateSprites: false); + AssetDatabase.ImportAsset(rootAssetPath, ImportAssetOptions.ForceUpdate); } private static string GetSpriteAtlasPathFromSpriteAtlas(SpriteAtlas spriteAtlas) @@ -64,7 +38,7 @@ private static string GetSpriteAtlasPathFromSpriteAtlas(SpriteAtlas spriteAtlas) var fileNameWithExtension = Path.GetFileName(filePathWithName); var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(filePathWithName); var filePath = filePathWithName.Replace(fileNameWithExtension, ""); - return filePath + fileNameWithoutExtension + ".asset"; + return filePath + fileNameWithoutExtension + TmpSpriteAssetImporter.FileExtensionWithDot; } } } \ No newline at end of file diff --git a/Editor/TmpSpriteAssetPostProcessor.cs b/Editor/TmpSpriteAssetPostProcessor.cs new file mode 100644 index 0000000..7f6513d --- /dev/null +++ b/Editor/TmpSpriteAssetPostProcessor.cs @@ -0,0 +1,40 @@ +using UnityEditor; + +namespace TMPro +{ + public class TmpSpriteAssetPostProcessor : AssetPostprocessor + { + private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, + string[] movedAssets, string[] movedFromAssetPaths, bool didDomainReload) + { + foreach (var assetPath in importedAssets) + { + if (!assetPath.EndsWith(TmpSpriteAssetImporter.FileExtensionWithDot)) + { + continue; + } + + NotifyChanged(assetPath); + } + } + + private static void NotifyChanged(string assetPath) + { + var asset = AssetDatabase.LoadAssetAtPath(assetPath); + + if (asset.version != TmpSpriteAssetImporter.FileVersion) + { + return; + } + + foreach (var subAsset in asset.fallbackSpriteAssets) + { + subAsset.UpdateLookupTables(); + TMPro_EventManager.ON_SPRITE_ASSET_PROPERTY_CHANGED(true, subAsset); + } + + asset.UpdateLookupTables(); + TMPro_EventManager.ON_SPRITE_ASSET_PROPERTY_CHANGED(true, asset); + } + } +} \ No newline at end of file diff --git a/Editor/TmpSpriteAssetPostProcessor.cs.meta b/Editor/TmpSpriteAssetPostProcessor.cs.meta new file mode 100644 index 0000000..2997bd2 --- /dev/null +++ b/Editor/TmpSpriteAssetPostProcessor.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6a468da918c94476b8a0441ade844b4f +timeCreated: 1697476516 \ No newline at end of file diff --git a/README.md b/README.md index 079eb28..c4c5a3d 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,13 @@ -# TextMeshPro.SpriteAtlas.Support [![Github license](https://img.shields.io/github/license/codewriter-packages/textmeshpro-spriteatlas-support.svg?style=flat-square)](#) [![Unity 2021.3](https://img.shields.io/badge/Unity-2021.3+-2296F3.svg?style=flat-square)](#) ![GitHub package.json version](https://img.shields.io/github/package-json/v/codewriter-packages/textmeshpro-spriteatlas-support?style=flat-square) +# TextMeshPro.SpriteAtlas.Support [![Github license](https://img.shields.io/github/license/codewriter-packages/textmeshpro-spriteatlas-support.svg?style=flat-square)](#) [![Unity 2022.3](https://img.shields.io/badge/Unity-2022.3+-2296F3.svg?style=flat-square)](#) ![GitHub package.json version](https://img.shields.io/github/package-json/v/codewriter-packages/textmeshpro-spriteatlas-support?style=flat-square) > [!WARNING] > Package is EXPERIMENTAL and may not work. +> +> Sometimes does not update automatically if the atlas contains folders as a packable asset ## :heavy_exclamation_mark: Requirements +- Unity **2022.3** - SpriteAtlas **V2** - TextMeshPro package version **3.0.6** @@ -12,7 +15,7 @@ #### 1. Enable SpriteAtlas V2 in `ProjectSettings/Editor` -![SpriteAtlasV2](https://github.com/codewriter-packages/textmeshpro-spriteatlas-support/assets/26966368/70ca19c5-907e-4843-99e0-eafaa6f9e2cb) +![SpriteAtlasV2](https://github.com/codewriter-packages/textmeshpro-spriteatlas-support/assets/26966368/3f85a3a7-d694-48ea-b472-6e32d71d2e13) #### 2. Create `SpriteAtlas` @@ -22,17 +25,7 @@ ![SpriteAtlas Asset](https://github.com/codewriter-packages/textmeshpro-spriteatlas-support/assets/26966368/23a6cf75-2559-484e-b8df-2a107da98a63) -#### 4. Update generated assets with `UpdateSprites` context menu - -![UpdateSprites](https://github.com/codewriter-packages/textmeshpro-spriteatlas-support/assets/26966368/4b75f34f-e372-46e5-b8b2-8bf02b19a39b) - -> [!IMPORTANT] -> SpriteAtlas Asset can be updates only in play mode due to limitation of Unity - -> [!IMPORTANT] -> SpriteAtlas Asset must be updated manually every time source SpriteAtlas is modified - -#### 5. Done. Now you can use generated sprite asset in TextMeshPro +#### 4. Done. Now you can use generated sprite asset in TextMeshPro ![Asset](https://github.com/codewriter-packages/textmeshpro-spriteatlas-support/assets/26966368/a8fb3845-20f8-4f45-843a-047a0dcc8fea) diff --git a/Runtime.meta b/Runtime.meta deleted file mode 100644 index c43e8cb..0000000 --- a/Runtime.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 5d798f0f9002f804286dd91d3adef3ab -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/TextMeshPro.SpriteAtlas.Support.asmref b/Runtime/TextMeshPro.SpriteAtlas.Support.asmref deleted file mode 100644 index b243cbc..0000000 --- a/Runtime/TextMeshPro.SpriteAtlas.Support.asmref +++ /dev/null @@ -1,3 +0,0 @@ -{ - "reference": "Unity.TextMeshPro" -} \ No newline at end of file diff --git a/Runtime/TextMeshPro.SpriteAtlas.Support.asmref.meta b/Runtime/TextMeshPro.SpriteAtlas.Support.asmref.meta deleted file mode 100644 index 45e6ee3..0000000 --- a/Runtime/TextMeshPro.SpriteAtlas.Support.asmref.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: b7940fdda32ebde4ab0fa3735cd62b78 -AssemblyDefinitionReferenceImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/TmpSpriteAtlasAsset.cs b/Runtime/TmpSpriteAtlasAsset.cs deleted file mode 100644 index b82b892..0000000 --- a/Runtime/TmpSpriteAtlasAsset.cs +++ /dev/null @@ -1,15 +0,0 @@ -using UnityEngine; -using UnityEngine.U2D; - -namespace TMPro -{ - public class TmpSpriteAtlasAsset : ScriptableObject - { - public SpriteAtlas spriteAtlas; - public TMP_SpriteAsset mainSpriteAsset; - - public Vector2 bearing; - public float advance; - public float scale = 1.0f; - } -} \ No newline at end of file diff --git a/Runtime/TmpSpriteAtlasAsset.cs.meta b/Runtime/TmpSpriteAtlasAsset.cs.meta deleted file mode 100644 index 153107a..0000000 --- a/Runtime/TmpSpriteAtlasAsset.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 457e347fbc8f44008ec483166d936053 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {fileID: 2800000, guid: ec7c645d93308c04d8840982af12101e, type: 3} - userData: - assetBundleName: - assetBundleVariant: diff --git a/package.json b/package.json index db102fa..b6bcf44 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,8 @@ "name": "com.codewriter.textmeshpro-spriteatlas-support", "displayName": "TextMeshPro SpriteAtlas Support", "description": "TextMeshPro SpriteAtlas Support", - "version": "1.0.0", - "unity": "2021.3", + "version": "2.0.0", + "unity": "2022.3", "license": "MIT", "author": "CodeWriter (https://github.com/orgs/codewriter-packages)", "homepage": "https://github.com/codewriter-packages/textmeshpro-spriteatlas-support#readme",