前提・実現したいこと
UnityEditor上に表示するラベルやボタンに対して文字装飾用のタグが適応された状態で表示させたいと思って調査しているのですが、
TextMeshPro内で独自に定義されたタグについてはそのまま表示しまいます。
こちらの解決方法について知っている方はいらっしゃらないでしょうか。
UnityEditor上のボタンとは下記のようなもののことを指しています。
C#
1GUILayout.Button("ボタン");
結果的に「<sprite=1>こんにちは<sprite=1>」と記述した場合、
「????こんにちは????」とボタン名に表示されるようにしたいです。
現時点で把握していること
GUIStype.richText をTrueにすると<size>や<color>と言ったタグが適応されるところまでたどり着きました。
しかし、TextMeshProで実装されているタグについてはそのまま表示されてしまいます。
C#
1GUIStyle style = new GUIStyle(EditorStyles.miniButton); 2style.richText = true; 3if (GUILayout.Button("これは<b>太字</b>です", style)) 4{ 5 // 押したときの処理 6}
これは太字です
UnityのTextコンポーネント標準のタグについては効果があるようです。
上記のようにTextMeshProのタグに対しても表示側で適応する方法はないでしょうか?
欲を出せば自前で追加したタグ等も登録できるようにもしたいと考えています。
よろしくお願いいたします。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答2件
0
パート2
Editorフォルダには下記スクリプトを入れました。
C#
1using System.Reflection; 2using TMPro; 3using UnityEditor; 4using UnityEngine; 5 6public class TMPGUIWindow : EditorWindow 7{ 8 private GameObject helperObject; 9 private TextMeshPro textMeshPro; 10 private UnityEditor.Editor textMeshProEditor; 11 private Vector2 scrollPosition; 12 private string helperAssetPath; 13 14 private string HelperAssetPath 15 { 16 get 17 { 18 if (string.IsNullOrEmpty(this.helperAssetPath)) 19 { 20 this.helperAssetPath = $"Assets/Resources/{TMPGUI.HelperAssetName}.prefab"; 21 } 22 23 return this.helperAssetPath; 24 } 25 } 26 27 [MenuItem("Window/TMPGUI")] 28 public static void ShowWindow() 29 { 30 GetWindow(typeof(TMPGUIWindow), false, "TMPGUI"); 31 } 32 33 private void CreateResourcesFolderIfNeeded() 34 { 35 if (AssetDatabase.IsValidFolder("Assets/Resources")) 36 { 37 return; 38 } 39 40 AssetDatabase.CreateFolder("Assets", "Resources"); 41 } 42 43 private void OnGUI() 44 { 45 if (this.helperObject == null) 46 { 47 this.helperObject = AssetDatabase.LoadAssetAtPath<GameObject>(this.HelperAssetPath); 48 } 49 50 var currentEnabledState = this.helperObject != null; 51 var newEnabledState = EditorGUILayout.Toggle("Enabled", currentEnabledState); 52 if (newEnabledState != currentEnabledState) 53 { 54 if (newEnabledState) 55 { 56 this.CreateHelper(); 57 } 58 else 59 { 60 this.DeleteHelper(); 61 } 62 63 return; 64 } 65 66 if (!currentEnabledState) 67 { 68 return; 69 } 70 71 if (this.textMeshPro == null) 72 { 73 this.textMeshPro = this.helperObject.GetComponentInChildren<TextMeshPro>(); 74 } 75 76 UnityEditor.Editor.CreateCachedEditor( 77 this.textMeshPro, 78 null, 79 ref this.textMeshProEditor); 80 EditorGUILayout.Space(); 81 using (var scrollView = new EditorGUILayout.ScrollViewScope(this.scrollPosition)) 82 { 83 this.scrollPosition = scrollView.scrollPosition; 84 this.textMeshProEditor.OnInspectorGUI(); 85 } 86 } 87 88 private void CreateHelper() 89 { 90 var rootObject = new GameObject("TMPGUIHelper"); 91 92 var tmpObject = new GameObject("TextMesh Pro"); 93 tmpObject.transform.SetParent(rootObject.transform, false); 94 95 var tmp = tmpObject.AddComponent<TextMeshPro>(); 96 tmp.color = TMPGUI.DefaultTextColor; 97 tmp.isOrthographic = true; 98 99 var subTextObjectsField = typeof(TextMeshPro).GetField( 100 "m_subTextObjects", 101 BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic); 102 var subTextObjects = (TMP_SubMesh[])subTextObjectsField.GetValue(tmp); 103 104 var materialReference = new MaterialReference(0, tmp.font, null, tmp.fontMaterial, 0.0f); 105 106 for (var i = 1; i < 8; i++) 107 { 108 subTextObjects[i] = TMP_SubMesh.AddSubTextObject(tmp, materialReference); 109 } 110 111 this.CreateResourcesFolderIfNeeded(); 112 PrefabUtility.SaveAsPrefabAsset(rootObject, this.HelperAssetPath); 113 DestroyImmediate(rootObject); 114 this.helperObject = AssetDatabase.LoadAssetAtPath<GameObject>(this.HelperAssetPath); 115 } 116 117 private void DeleteHelper() 118 { 119 DestroyImmediate(this.helperObject, true); 120 AssetDatabase.DeleteAsset(this.HelperAssetPath); 121 this.helperObject = null; 122 this.textMeshPro = null; 123 this.textMeshProEditor = null; 124 } 125}
ゲームビュー上での動作確認用に下記スクリプトを用意しました。普通のラベル、TextMesh Proラベル、普通のボタン、TextMesh Proボタンを並べてみました。
C#
1using UnityEngine; 2 3public class TMPGUITest : MonoBehaviour 4{ 5 private void OnGUI() 6 { 7 GUILayout.Label("これは<b>太字</b>です"); 8 TMPGUILayout.Label("12345<sprite=0><color=\"red\">あいうえお</color><b>Bold</b><i>Italic</i><s>Strike</s><u>Underline</u>"); 9 GUILayout.Button("ボタン"); 10 TMPGUILayout.Button("<sprite=1>こんにちは<sprite=1>"); 11 } 12}
インスペクター上での動作確認用に下記スクリプトをEditorフォルダに入れました。ボタンを押すと下にラベルが表示されるようにしてみました。
C#
1using UnityEditor; 2using UnityEngine; 3 4[CustomEditor(typeof(TMPGUITest))] 5public class TMPGUITestEditor : UnityEditor.Editor 6{ 7 private bool showMessage; 8 9 /// <inheritdoc /> 10 public override void OnInspectorGUI() 11 { 12 base.OnInspectorGUI(); 13 14 var buttonStyle = new GUIStyle(GUI.skin.button) {richText = true}; 15 var labelStyle = new GUIStyle(GUI.skin.label) {richText = true}; 16 17 const string buttonRichText = 18 "<u><voffset=-0.25em><rotate=30>あ</rotate></voffset><voffset=0.25em><rotate=10>い</rotate></voffset><voffset=0.25em><rotate=-10>さ</rotate></voffset><voffset=-0.25em><rotate=-30>つ</rotate></voffset></u>"; 19 const string labelRichText = 20 "<sprite=2><color=#F00>こ</color><color=#FF0>ん</color><color=#0F0>に</color><color=#0FF>ち</color><color=#00F>は</color><sprite=3>"; 21 22 if (TMPGUILayout.Button(buttonRichText, buttonStyle, GUILayout.Width(144.0f), GUILayout.Height(36.0f))) 23 { 24 this.showMessage = !this.showMessage; 25 } 26 27 if (this.showMessage) 28 { 29 TMPGUILayout.Label(labelRichText, labelStyle); 30 } 31 } 32}
「Window」→「TMPGUI」の「Enabled」にチェックを入れることでプレハブを作らせ、フォントをセットして...
シーン上にTMPGUITest
をアタッチしたオブジェクトを置いてみたところ下図のように表示されました。インスペクターが町内会のチラシみたいになっています。
一応描画されたものの、実際のところあまり自信がないです...
TextMesh Proのオブジェクトがシーン外にあるためAwake
やOnEnable
を手動で実行したり、サブテキストオブジェクト群に直接アクセスしたり...とTextMesh Proが想定していないであろうむちゃくちゃなことをやっており、TextMesh Proのバージョンが変われば動かないかもしれません。
また、TextMesh Proのコードはけっこう分量が多くて、一体何をやっているのか全貌がろくに分からないまま何とか動かそうと試行錯誤した結果ですので、不具合を見落としている可能性が大いにあります。
あくまでも「まったく不可能ではないかもしれない」といった程度の参考としてとらえていただけるとありがたいです。「どうしてもTextMesh Proを使いたい」というほどでなければ、IMGUIの標準機能で代替できないか、あるいは妥協してあきらめるか...といった選択肢の方が無難かと思います。
ですがTextMesh Proは抜きにして、単に「エディターUI上に追加描画を行う」ということ(TMPGUI
のGraphics.ExecuteCommandBuffer(renderingCommand);
の部分だとか)に関しては、特に予想に反したトラブルもなく素直な結果が得られたように思われました。特殊なエディターUIが欲しい時に手段の選択肢になりそうで、個人的に収穫があったと感じます。
それと「自前で追加したタグ等も登録できるように」とおっしゃる件については、はたして可能かどうかはなんとも言いがたいですね...
おそらく「<foo>...</foo>
というタグが出現したら<i><u><color=#F00>...</color></u></i>
に置き換える」みたいな、文字列の前処理だけで実現できそうなものなら見込みはあるように感じます。ですが「ぼかしをかけるタグ」とか「跳ねるアニメーションをさせるタグ」みたいなものは困難そうな気がしますね...
投稿2020/11/02 03:09
総合スコア10811
0
ベストアンサー
これはなかなかやっかいですね...
ご提示のコードでEditorStyles.miniButtonを使っているということは、タイトルの「UnityEditor上の」とおっしゃるのは、インスペクターだとかのUI上でTextMesh Proを使いたいということでしょうか?
TextMesh Proの主要部分はTMP_Textが担当しているようですが、これがすでにMaskableGraphicから派生しており、ゲーム内のUIとして使うことが前提になっているみたいでした。IMGUIでの使用は考慮されていないような感じですね。
苦肉の策として...
- あらかじめTextMesh Proのテキストを配置したオブジェクトを作り、プレハブ化してプロジェクト内に置いておく。
- IMGUIでの描画の際には、このプレハブ内のTextMesh Proに対して文章をセットし、それをDrawRendererで描画する。
といった風にできないか試してみました。
描画用メソッドをまとめたスクリプトとして下記2つを作り(手抜きしてラベルとボタンしかありませんがご容赦ください)...
C#
1using System; 2using System.Reflection; 3using TMPro; 4using UnityEngine; 5using UnityEngine.Rendering; 6 7public static class TMPGUI 8{ 9 public static readonly string HelperAssetName = "TMPGUIHelper"; 10 public static readonly Color DefaultTextColor = new Color(50f / 255f, 50f / 255f, 50f / 255f, 1f); 11 12 private static GameObject helperObject; 13 private static TextMeshPro textMeshPro; 14 private static FieldInfo subTextObjectsField; 15 private static TMP_SubMesh[] subTextObjects; 16 private static RectTransform tmpRectTransform; 17 private static CommandBuffer renderingCommand; 18 19 public static void Label(Rect position, string text, GUIStyle style, GUIStyleState alternativeState = null) 20 { 21 if (!HelperExists()) 22 { 23 GUI.Label(position, text, style); 24 return; 25 } 26 27 if (Event.current.type != EventType.Repaint) 28 { 29 return; 30 } 31 32 TextAlignmentOptions alignment; 33 switch (style.alignment) 34 { 35 case TextAnchor.UpperLeft: 36 alignment = TextAlignmentOptions.TopLeft; 37 break; 38 case TextAnchor.UpperCenter: 39 alignment = TextAlignmentOptions.Top; 40 break; 41 case TextAnchor.UpperRight: 42 alignment = TextAlignmentOptions.TopRight; 43 break; 44 case TextAnchor.MiddleLeft: 45 alignment = TextAlignmentOptions.Left; 46 break; 47 case TextAnchor.MiddleCenter: 48 alignment = TextAlignmentOptions.Center; 49 break; 50 case TextAnchor.MiddleRight: 51 alignment = TextAlignmentOptions.Right; 52 break; 53 case TextAnchor.LowerLeft: 54 alignment = TextAlignmentOptions.BottomLeft; 55 break; 56 case TextAnchor.LowerCenter: 57 alignment = TextAlignmentOptions.Bottom; 58 break; 59 case TextAnchor.LowerRight: 60 alignment = TextAlignmentOptions.BottomRight; 61 break; 62 default: 63 throw new ArgumentOutOfRangeException(); 64 } 65 66 tmpRectTransform.pivot = Vector2.up; 67 tmpRectTransform.localScale = new Vector3(1, -1, 1); 68 tmpRectTransform.anchoredPosition = position.min; 69 tmpRectTransform.sizeDelta = position.size; 70 71 textMeshPro.text = text; 72 textMeshPro.color = (alternativeState ?? style.normal)?.textColor ?? DefaultTextColor; 73 textMeshPro.fontSize = style.fontSize > 0 ? style.fontSize : style.lineHeight; 74 textMeshPro.richText = style.richText; 75 textMeshPro.alignment = alignment; 76 77 ForceMeshUpdate(textMeshPro); 78 79 renderingCommand.Clear(); 80 renderingCommand.ClearRenderTarget(true, false, Color.clear); 81 renderingCommand.DrawRenderer(textMeshPro.renderer, textMeshPro.fontSharedMaterial); 82 for (var i = 1; i < textMeshPro.textInfo.materialCount; i++) 83 { 84 var subTextObject = subTextObjects[i]; 85 renderingCommand.DrawRenderer(subTextObject.renderer, subTextObject.sharedMaterial); 86 } 87 88 Graphics.ExecuteCommandBuffer(renderingCommand); 89 90 textMeshPro.text = string.Empty; 91 } 92 93 public static bool Button(Rect position, string text) 94 { 95 return Button(position, text, GUI.skin.button); 96 } 97 98 public static bool Button(Rect position, string text, GUIStyle style) 99 { 100 if (!HelperExists()) 101 { 102 return GUI.Button(position, text, style); 103 } 104 105 var result = GUI.Button(position, string.Empty, style); 106 Label(position, text, style, position.Contains(Event.current.mousePosition) ? style.hover : null); 107 return result; 108 } 109 110 private static void ForceMeshUpdate(TMP_Text tmp) 111 { 112 tmp.ForceMeshUpdate(true); 113 114 if (tmp.renderMode != TextRenderFlags.Render) 115 { 116 return; 117 } 118 119 var geometrySortingOrder = tmp.geometrySortingOrder; 120 var textInfo = tmp.textInfo; 121 122 if (geometrySortingOrder != VertexSortingOrder.Normal) 123 { 124 textInfo.meshInfo[0].SortGeometry(VertexSortingOrder.Reverse); 125 } 126 127 var mesh = tmp.mesh; 128 mesh.MarkDynamic(); 129 mesh.vertices = textInfo.meshInfo[0].vertices; 130 mesh.uv = textInfo.meshInfo[0].uvs0; 131 mesh.uv2 = textInfo.meshInfo[0].uvs2; 132 mesh.colors32 = textInfo.meshInfo[0].colors32; 133 mesh.RecalculateBounds(); 134 135 if (subTextObjectsField == null) 136 { 137 subTextObjectsField = typeof(TextMeshPro).GetField( 138 "m_subTextObjects", 139 BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic); 140 } 141 142 subTextObjects = subTextObjectsField.GetValue(tmp) as TMP_SubMesh[]; 143 144 for (var i = 1; i < textInfo.materialCount; i++) 145 { 146 var subTextObject = subTextObjects[i]; 147 var meshInfo = textInfo.meshInfo[i]; 148 meshInfo.ClearUnusedVertices(); 149 150 if (subTextObject == null) 151 { 152 continue; 153 } 154 155 if (geometrySortingOrder != VertexSortingOrder.Normal) 156 { 157 meshInfo.SortGeometry(VertexSortingOrder.Reverse); 158 } 159 160 var subMesh = subTextObject.mesh; 161 subMesh.vertices = meshInfo.vertices; 162 subMesh.uv = meshInfo.uvs0; 163 subMesh.uv2 = meshInfo.uvs2; 164 subMesh.colors32 = meshInfo.colors32; 165 subMesh.RecalculateBounds(); 166 } 167 168 TMPro_EventManager.ON_TEXT_CHANGED(tmp); 169 } 170 171 private static bool HelperExists() 172 { 173 var needsInit = false; 174 175 if (helperObject == null) 176 { 177 helperObject = Resources.Load<GameObject>(HelperAssetName); 178 needsInit = true; 179 } 180 181 if (helperObject == null) 182 { 183 return false; 184 } 185 186 if (textMeshPro != null) 187 { 188 return true; 189 } 190 191 textMeshPro = helperObject.GetComponentInChildren<TextMeshPro>(); 192 tmpRectTransform = textMeshPro.rectTransform; 193 194 if (needsInit) 195 { 196 typeof(TextMeshPro).GetMethod( 197 "Awake", 198 BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic) 199 .Invoke(textMeshPro, null); 200 typeof(TextMeshPro).GetMethod( 201 "OnEnable", 202 BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic) 203 .Invoke(textMeshPro, null); 204 } 205 206 renderingCommand = new CommandBuffer {name = "TMPGUI"}; 207 208 return true; 209 } 210}
C#
1using UnityEngine; 2 3public static class TMPGUILayout 4{ 5 private static readonly GUIContent TempContent = new GUIContent 6 { 7 text = string.Empty, 8 image = null, 9 tooltip = string.Empty 10 }; 11 12 public static void Label(string text, params GUILayoutOption[] options) 13 { 14 Label(text, GUI.skin.label, options); 15 } 16 17 public static void Label(string text, GUIStyle style, params GUILayoutOption[] options) 18 { 19 TMPGUI.Label(GUILayoutUtility.GetRect(Temp(text), style, options), text, style); 20 } 21 22 public static bool Button(string text, params GUILayoutOption[] options) 23 { 24 return Button(text, GUI.skin.button, options); 25 } 26 27 public static bool Button(string text, GUIStyle style, params GUILayoutOption[] options) 28 { 29 return TMPGUI.Button(GUILayoutUtility.GetRect(Temp(text), style, options), text, style); 30 } 31 32 private static GUIContent Temp(string text) 33 { 34 TempContent.text = text; 35 return TempContent; 36 } 37}
(文字数がかさんだためパート2に移ります...)
投稿2020/11/02 03:08
総合スコア10811
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。