質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

文字コード

文字コードとは、文字や記号をコンピュータ上で使用するために用いられるバイト表現を指します。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

2回答

3056閲覧

UnityEditor上のラベルやボタンに対してTextMeshPro独自のタグを適応した状態の文字を表示させたい

marron1990

総合スコア2

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

文字コード

文字コードとは、文字や記号をコンピュータ上で使用するために用いられるバイト表現を指します。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

0クリップ

投稿2020/10/30 08:57

前提・実現したいこと

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ページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答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」にチェックを入れることでプレハブを作らせ、フォントをセットして...

図1

シーン上にTMPGUITestをアタッチしたオブジェクトを置いてみたところ下図のように表示されました。インスペクターが町内会のチラシみたいになっています。

図2

一応描画されたものの、実際のところあまり自信がないです...
TextMesh Proのオブジェクトがシーン外にあるためAwakeOnEnableを手動で実行したり、サブテキストオブジェクト群に直接アクセスしたり...とTextMesh Proが想定していないであろうむちゃくちゃなことをやっており、TextMesh Proのバージョンが変われば動かないかもしれません。
また、TextMesh Proのコードはけっこう分量が多くて、一体何をやっているのか全貌がろくに分からないまま何とか動かそうと試行錯誤した結果ですので、不具合を見落としている可能性が大いにあります。
あくまでも「まったく不可能ではないかもしれない」といった程度の参考としてとらえていただけるとありがたいです。「どうしてもTextMesh Proを使いたい」というほどでなければ、IMGUIの標準機能で代替できないか、あるいは妥協してあきらめるか...といった選択肢の方が無難かと思います。

ですがTextMesh Proは抜きにして、単に「エディターUI上に追加描画を行う」ということ(TMPGUIGraphics.ExecuteCommandBuffer(renderingCommand);の部分だとか)に関しては、特に予想に反したトラブルもなく素直な結果が得られたように思われました。特殊なエディターUIが欲しい時に手段の選択肢になりそうで、個人的に収穫があったと感じます。

それと「自前で追加したタグ等も登録できるように」とおっしゃる件については、はたして可能かどうかはなんとも言いがたいですね...
おそらく「<foo>...</foo>というタグが出現したら<i><u><color=#F00>...</color></u></i>に置き換える」みたいな、文字列の前処理だけで実現できそうなものなら見込みはあるように感じます。ですが「ぼかしをかけるタグ」とか「跳ねるアニメーションをさせるタグ」みたいなものは困難そうな気がしますね...

投稿2020/11/02 03:09

Bongo

総合スコア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

Bongo

総合スコア10811

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問