using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; #if TMP_PRESENT using System.Collections.Generic; using TMPro; #if UNITY_EDITOR using UnityEditor; using System.IO; using System.Linq; #endif #endif namespace Coffee.UIExtensions { /// /// Base class for effects that modify the generated Mesh. /// It works well not only for standard Graphic components (Image, RawImage, Text, etc.) but also for TextMeshPro and TextMeshProUGUI. /// [ExecuteInEditMode] public abstract class BaseMeshEffect : UIBehaviour, IMeshModifier { //################################ // Constant or Static Members. //################################ #if TMP_PRESENT static readonly List s_Uv0 = new List (4096); static readonly List s_Uv1 = new List (4096); #if UNITY_2017_1_OR_NEWER static readonly List s_Uv2 = new List (4096); static readonly List s_Uv3 = new List (4096); #endif static readonly List s_Vertices = new List (4096); static readonly List s_Indices = new List (4096); static readonly List s_Normals = new List (4096); static readonly List s_Tangents = new List (4096); static readonly List s_Colors = new List (4096); static readonly VertexHelper s_VertexHelper = new VertexHelper (); static readonly List s_SubMeshUIs = new List (4096); static readonly List s_Meshes = new List (4096); static readonly List s_UIVertices = new List (4096); static readonly List s_TmpEffects = new List(4); #endif static readonly Material [] s_EmptyMaterials = new Material [0]; //################################ // Public Members. //################################ /// /// The Graphic attached to this GameObject. /// public Graphic graphic { get { Initialize (); return _graphic; } } /// /// The CanvasRenderer attached to this GameObject. /// public CanvasRenderer canvasRenderer { get { Initialize (); return _canvasRenderer; } } #if TMP_PRESENT /// /// The TMP_Text attached to this GameObject. /// public TMP_Text textMeshPro { get { Initialize (); return _textMeshPro; } } #endif /// /// The RectTransform attached to this GameObject. /// public RectTransform rectTransform { get { Initialize (); return _rectTransform; } } #if UNITY_5_6_OR_NEWER /// /// Additional canvas shader channels to use this component. /// public virtual AdditionalCanvasShaderChannels requiredChannels { get { return AdditionalCanvasShaderChannels.None; } } #endif /// /// Is TextMeshPro or TextMeshProUGUI attached to this GameObject? /// public bool isTMPro { get { #if TMP_PRESENT return textMeshPro != null; #else return false; #endif } } /// /// The material for rendering. /// public virtual Material material { get { #if TMP_PRESENT if (textMeshPro) { return textMeshPro.fontSharedMaterial; } else #endif if (graphic) { return graphic.material; } else { return null; } } set { #if TMP_PRESENT if (textMeshPro) { textMeshPro.fontSharedMaterial = value; } else #endif if (graphic) { graphic.material = value; } } } public virtual Material[] materials { get { #if TMP_PRESENT if (textMeshPro) { return textMeshPro.fontSharedMaterials ?? s_EmptyMaterials; } else #endif if (graphic) { _materials [0] = graphic.material; return _materials; } else { return s_EmptyMaterials; } } } /// /// Call used to modify mesh. (legacy) /// /// Mesh. public virtual void ModifyMesh (Mesh mesh) { } /// /// Call used to modify mesh. /// /// VertexHelper. public virtual void ModifyMesh (VertexHelper vh) { } /// /// Mark the vertices as dirty. /// public virtual void SetVerticesDirty () { #if TMP_PRESENT if (textMeshPro) { foreach (var info in textMeshPro.textInfo.meshInfo) { var mesh = info.mesh; if (mesh) { mesh.Clear (); mesh.vertices = info.vertices; mesh.uv = info.uvs0; mesh.uv2 = info.uvs2; mesh.colors32 = info.colors32; mesh.normals = info.normals; mesh.tangents = info.tangents; mesh.triangles = info.triangles; } } if (canvasRenderer) { canvasRenderer.SetMesh (textMeshPro.mesh); GetComponentsInChildren (false, s_SubMeshUIs); foreach (var sm in s_SubMeshUIs) { sm.canvasRenderer.SetMesh (sm.mesh); } s_SubMeshUIs.Clear (); } textMeshPro.havePropertiesChanged = true; } else #endif if (graphic) { graphic.SetVerticesDirty (); } } public void ShowTMProWarning (Shader shader, Shader mobileShader, Shader spriteShader, System.Action onCreatedMaterial) { #if UNITY_EDITOR && TMP_PRESENT if(!textMeshPro || !textMeshPro.fontSharedMaterial) { return; } // Is the material preset for dissolve? Material m = textMeshPro.fontSharedMaterial; if (m.shader != shader && m.shader != mobileShader) { EditorGUILayout.BeginHorizontal (); EditorGUILayout.HelpBox (string.Format("{0} requires '{1}' or '{2}' as a shader for material preset.", GetType().Name, shader.name, mobileShader.name), MessageType.Warning); if(GUILayout.Button ("Fix")) { var correctShader = m.shader.name.Contains ("Mobile") ? mobileShader : shader; textMeshPro.fontSharedMaterial = ModifyTMProMaterialPreset (m, correctShader, onCreatedMaterial); } EditorGUILayout.EndHorizontal (); return; } // Is the sprite asset for dissolve? TMP_SpriteAsset spriteAsset = textMeshPro.spriteAsset ?? TMP_Settings.GetSpriteAsset (); // Sprite asset might not exist at all if(spriteAsset == null) { return; } m = spriteAsset.material; if (m && m.shader != spriteShader && textMeshPro.richText && textMeshPro.text.Contains(" ().Select (x => x.gameObject).ToList ().ForEach (DestroyImmediate); GetComponentsInChildren ().Select (x => x.gameObject).ToList ().ForEach (DestroyImmediate); textMeshPro.spriteAsset = ModifyTMProSpriteAsset (m, spriteShader, onCreatedMaterial); } EditorGUILayout.EndHorizontal (); return; } } Material ModifyTMProMaterialPreset (Material baseMaterial, Shader shader, System.Action onCreatedMaterial) { string path = AssetDatabase.GetAssetPath (baseMaterial); string filename = Path.GetFileNameWithoutExtension (path) + " (" + GetType ().Name + ")"; Material mat = Resources.Load (TMP_Settings.defaultFontAssetPath + filename); if (!mat) { mat = new Material (baseMaterial) { shaderKeywords = baseMaterial.shaderKeywords, shader = shader, }; onCreatedMaterial (mat); AssetDatabase.CreateAsset (mat, Path.GetDirectoryName (path) + "/" + filename + ".mat"); EditorUtility.FocusProjectWindow (); EditorGUIUtility.PingObject (mat); } else { mat.shader = shader; } EditorUtility.SetDirty (mat); return mat; } TMP_SpriteAsset ModifyTMProSpriteAsset (Material baseMaterial, Shader shader, System.Action onCreatedMaterial) { string path = AssetDatabase.GetAssetPath (baseMaterial); string filename = Path.GetFileNameWithoutExtension (path) + " (" + this.GetType ().Name + ")"; TMP_SpriteAsset spriteAsset = Resources.Load (TMP_Settings.defaultSpriteAssetPath + filename); if (!spriteAsset) { AssetDatabase.CopyAsset (path, Path.GetDirectoryName (path) + "/" + filename + ".mat"); spriteAsset = Resources.Load (TMP_Settings.defaultSpriteAssetPath + filename); spriteAsset.material.shader = shader; spriteAsset.material.name = shader.name; onCreatedMaterial (spriteAsset.material); EditorUtility.FocusProjectWindow (); EditorGUIUtility.PingObject (spriteAsset); } else { spriteAsset.material.shader = shader; } EditorUtility.SetDirty (spriteAsset); return spriteAsset; #endif } //################################ // Protected Members. //################################ /// /// Should the effect modify the mesh directly for TMPro? /// protected virtual bool isLegacyMeshModifier { get { return false; } } protected virtual void Initialize () { if (!_initialized) { _initialized = true; _graphic = _graphic ?? GetComponent (); _canvasRenderer = _canvasRenderer ?? GetComponent (); _rectTransform = _rectTransform ?? GetComponent (); #if TMP_PRESENT _textMeshPro = _textMeshPro ?? GetComponent (); #endif } } /// /// This function is called when the object becomes enabled and active. /// protected override void OnEnable () { _initialized = false; SetVerticesDirty (); #if TMP_PRESENT if (textMeshPro) { TMPro_EventManager.TEXT_CHANGED_EVENT.Add (OnTextChanged); } #endif #if UNITY_EDITOR && TMP_PRESENT if (graphic && textMeshPro) { GraphicRebuildTracker.TrackGraphic (graphic); } #endif #if UNITY_5_6_OR_NEWER if (graphic) { AdditionalCanvasShaderChannels channels = requiredChannels; var canvas = graphic.canvas; if (canvas && (canvas.additionalShaderChannels & channels) != channels) { Debug.LogWarningFormat (this, "Enable {1} of Canvas.additionalShaderChannels to use {0}.", GetType ().Name, channels); } } #endif } /// /// This function is called when the behaviour becomes disabled () or inactive. /// protected override void OnDisable () { #if TMP_PRESENT TMPro_EventManager.TEXT_CHANGED_EVENT.Remove (OnTextChanged); #endif SetVerticesDirty (); #if UNITY_EDITOR && TMP_PRESENT if (graphic && textMeshPro) { GraphicRebuildTracker.UnTrackGraphic (graphic); } #endif } /// /// LateUpdate is called every frame, if the Behaviour is enabled. /// protected virtual void LateUpdate () { #if TMP_PRESENT if (textMeshPro) { if (textMeshPro.havePropertiesChanged || _isTextMeshProActive != textMeshPro.isActiveAndEnabled) { SetVerticesDirty (); } _isTextMeshProActive = textMeshPro.isActiveAndEnabled; } #endif } /// /// Callback for when properties have been changed by animation. /// protected override void OnDidApplyAnimationProperties () { SetVerticesDirty (); } #if UNITY_EDITOR /// /// This function is called when the script is loaded or a value is changed in the inspector (Called in the editor only). /// protected override void OnValidate () { SetVerticesDirty (); } #endif //################################ // Private Members. //################################ bool _initialized; CanvasRenderer _canvasRenderer; RectTransform _rectTransform; Graphic _graphic; Material [] _materials = new Material [1]; #if TMP_PRESENT bool _isTextMeshProActive; TMP_Text _textMeshPro; /// /// Called when any TextMeshPro generated the mesh. /// /// TextMeshPro object. void OnTextChanged (Object obj) { // Skip if the object is different from the current object or the text is empty. var textInfo = textMeshPro.textInfo; if (textMeshPro != obj || textInfo.characterCount - textInfo.spaceCount <= 0) { return; } GetComponents(s_TmpEffects); for (int i = 0; i < s_TmpEffects.Count; i++) { if (s_TmpEffects[i].enabled) { if (s_TmpEffects[i] == this) break; else return; } } // Collect the meshes. s_Meshes.Clear (); foreach (var info in textInfo.meshInfo) { s_Meshes.Add (info.mesh); } // Modify the meshes. foreach (var e in s_TmpEffects) { if (!e.enabled) continue; if (e.isLegacyMeshModifier) { // Legacy mode: Modify the meshes directly. foreach (var m in s_Meshes) { if (m) { e.ModifyMesh(m); } } } else { // Convert meshes to VertexHelpers and modify them. foreach (var m in s_Meshes) { if (m) { FillVertexHelper(s_VertexHelper, m); e.ModifyMesh(s_VertexHelper); s_VertexHelper.FillMesh(m); } } } } // Set the modified meshes to the CanvasRenderers (for UI only). if (canvasRenderer) { canvasRenderer.SetMesh (textMeshPro.mesh); GetComponentsInChildren (false, s_SubMeshUIs); foreach (var sm in s_SubMeshUIs) { sm.canvasRenderer.SetMesh (sm.mesh); } s_SubMeshUIs.Clear (); } // Clear. s_Meshes.Clear (); } void FillVertexHelper (VertexHelper vh, Mesh mesh) { vh.Clear (); mesh.GetVertices (s_Vertices); mesh.GetColors (s_Colors); mesh.GetUVs (0, s_Uv0); mesh.GetUVs (1, s_Uv1); mesh.GetNormals (s_Normals); mesh.GetTangents (s_Tangents); mesh.GetIndices (s_Indices, 0); #if UNITY_2017_1_OR_NEWER mesh.GetUVs (2, s_Uv2); mesh.GetUVs (3, s_Uv3); bool useUv2 = 0 < s_Uv2.Count; bool useUv3 = 0 < s_Uv3.Count; #endif s_UIVertices.Clear(); UIVertex v = default(UIVertex); for (int i = 0; i < s_Vertices.Count; i++) { v.position = s_Vertices[i]; v.color = s_Colors[i]; v.uv0 = s_Uv0[i]; v.uv1 = s_Uv1[i]; #if UNITY_2017_1_OR_NEWER if (useUv2 && i < s_Uv2.Count) v.uv2 = s_Uv2[i]; if (useUv3 && i < s_Uv3.Count) v.uv3 = s_Uv3[i]; #endif v.normal = s_Normals[i]; v.tangent = s_Tangents[i]; s_UIVertices.Add(v); } s_VertexHelper.AddUIVertexStream(s_UIVertices, s_Indices); } #endif } }