Search Unity

TextMesh Pro Creating Custom Rich Text Tag

Discussion in 'UGUI & TextMesh Pro' started by MattDahEpic, Mar 26, 2018.

  1. MattDahEpic

    MattDahEpic

    Joined:
    Apr 22, 2014
    Posts:
    11
    I'm working on creating a custom "rich text" tag so I can do emotions in my text boxes. I've gotten the actual effect that the tag does to work, but I'm currently having trouble hiding the custom tags, both in visuals and for not taking time to scroll using the text console example.

    I'm using the version of TMP in Unity 2018.1.0b11.

    I have attempted to use the `TMP_TextInfo.isVisible` field to literally no effect. Now I'm manually hiding the tag, but the characters still take up space and time when being drawn by the text console example.

    (sub issue which will need resolving, none of the effects I make happen while the text is being unhidden by the console example like the built in tags do)

    My current code, attached to the TMP in my ui:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using System.Text.RegularExpressions;
    5. using TMPro;
    6. using UnityEngine;
    7.  
    8. //Adapted from the TMP vertex jitter example
    9. public class TMPSquiggleTag : MonoBehaviour {
    10.     private TMP_Text m_TextComponent;
    11.     private bool hasTextChanged;
    12.  
    13.     void Awake() {
    14.         m_TextComponent = GetComponent<TMP_Text>();
    15.     }
    16.  
    17.     void OnEnable() {
    18.         // Subscribe to event fired when text object has been regenerated.
    19.         TMPro_EventManager.TEXT_CHANGED_EVENT.Add(ON_TEXT_CHANGED);
    20.     }
    21.  
    22.     void OnDisable() {
    23.         TMPro_EventManager.TEXT_CHANGED_EVENT.Remove(ON_TEXT_CHANGED);
    24.     }
    25.  
    26.  
    27.     void Start() {
    28.         StartCoroutine(DoSquiggle());
    29.     }
    30.  
    31.  
    32.     void ON_TEXT_CHANGED(Object obj) {
    33.         if (obj == m_TextComponent)
    34.             hasTextChanged = true;
    35.     }
    36.  
    37.     IEnumerator DoSquiggle () {
    38.         m_TextComponent.ForceMeshUpdate(); //force the mesh to be generated before the end of the frame
    39.        
    40.         TMP_TextInfo textInfo = m_TextComponent.textInfo;
    41.         TMP_MeshInfo[] cachedMeshInfo = textInfo.CopyMeshInfoVertexData();
    42.         Matrix4x4 matrix;
    43.         hasTextChanged = true;
    44.         List<int> squiggleChars = new List<int>();
    45.         List<int> hideChars = new List<int>();
    46.  
    47.         while (true) {
    48.             if (hasTextChanged) {
    49.                 cachedMeshInfo = textInfo.CopyMeshInfoVertexData(); // Update the copy of the vertex data for the text object.
    50.                 //make the squiggle tag characters invisible and record what characters should wiggle
    51.                 squiggleChars.Clear();
    52.                 hideChars.Clear();
    53.                 if (m_TextComponent.text.Contains(@"<squiggle>") && m_TextComponent.text.Contains(@"</squiggle>")) {
    54.                     int beginSearch = 0;
    55.                     for (int i = 0; i < Regex.Matches(m_TextComponent.text,@"<squiggle>").Count; i++) {
    56.                         int beginBracket = m_TextComponent.text.IndexOf(@"<squiggle>",beginSearch);
    57.                         int endBracket = m_TextComponent.text.IndexOf(@"</squiggle>",beginSearch);
    58.                         //hide squiggle tags
    59.                         for (int j = beginBracket; j < beginBracket + 10; j++) hideChars.Add(j);
    60.                         for (int j = endBracket; j < endBracket + 11; j++) hideChars.Add(j);
    61.                         //put down the correct characters to be squiggled
    62.                         for (int c = beginBracket+10; c < endBracket; c++) {
    63.                             squiggleChars.Add(c);
    64.                         }
    65.                         //make sure you dont just keep doing this pair of squiggle tags if there's others to do
    66.                         beginSearch = endBracket;
    67.                     }
    68.                 }
    69.                 hasTextChanged = false;
    70.             }
    71.            
    72.             int characterCount = textInfo.characterCount;
    73.             if (characterCount == 0) { yield return new WaitForSeconds(0.25f); continue; } //wait for text to be added, if its not already
    74.  
    75.             for (int i = 0; i < characterCount; i++) {
    76.                 //hide the squiggle tags
    77.                 if (hideChars.Contains(i)) {
    78.                     //get stuff needed for doing things
    79.                     int materialIndex = textInfo.characterInfo[i].materialReferenceIndex;
    80.                     int vertexIndex = textInfo.characterInfo[i].vertexIndex;
    81.  
    82.                     Vector3[] destinationVertices = textInfo.meshInfo[materialIndex].vertices;
    83.  
    84.                     destinationVertices[vertexIndex + 0] = Vector3.zero;
    85.                     destinationVertices[vertexIndex + 1] = Vector3.zero;
    86.                     destinationVertices[vertexIndex + 2] = Vector3.zero;
    87.                     destinationVertices[vertexIndex + 3] = Vector3.zero;
    88.  
    89.                     m_TextComponent.UpdateVertexData(TMP_VertexDataUpdateFlags.Vertices);
    90.                 }
    91.                
    92.                 //offset the vertical position by sine(character index in string)
    93.                 if (squiggleChars.Contains(i)) {
    94.  
    95.                     // Get the index of the material used by the current character.
    96.                     int materialIndex = textInfo.characterInfo[i].materialReferenceIndex;
    97.  
    98.                     // Get the index of the first vertex used by this text element.
    99.                     int vertexIndex = textInfo.characterInfo[i].vertexIndex;
    100.  
    101.                     // Get the cached vertices of the mesh used by this text element (character or sprite).
    102.                     Vector3[] sourceVertices = cachedMeshInfo[materialIndex].vertices;
    103.  
    104.                     // Determine the center point of each character.
    105.                     Vector2 charMidBasline = (sourceVertices[vertexIndex + 0] + sourceVertices[vertexIndex + 2]) / 2;
    106.  
    107.                     // Need to translate all 4 vertices of each quad to aligned with middle of character / baseline.
    108.                     // This is needed so the matrix TRS is applied at the origin for each character.
    109.                     Vector3 offset = charMidBasline;
    110.  
    111.                     Vector3[] destinationVertices = textInfo.meshInfo[materialIndex].vertices;
    112.  
    113.                     destinationVertices[vertexIndex + 0] = sourceVertices[vertexIndex + 0] - offset;
    114.                     destinationVertices[vertexIndex + 1] = sourceVertices[vertexIndex + 1] - offset;
    115.                     destinationVertices[vertexIndex + 2] = sourceVertices[vertexIndex + 2] - offset;
    116.                     destinationVertices[vertexIndex + 3] = sourceVertices[vertexIndex + 3] - offset;
    117.  
    118.                     matrix = Matrix4x4.TRS(new Vector3(0,5*Mathf.Sin(Time.realtimeSinceStartup*i),0),Quaternion.identity, Vector3.one);
    119.  
    120.                     destinationVertices[vertexIndex + 0] = matrix.MultiplyPoint3x4(destinationVertices[vertexIndex + 0]);
    121.                     destinationVertices[vertexIndex + 1] = matrix.MultiplyPoint3x4(destinationVertices[vertexIndex + 1]);
    122.                     destinationVertices[vertexIndex + 2] = matrix.MultiplyPoint3x4(destinationVertices[vertexIndex + 2]);
    123.                     destinationVertices[vertexIndex + 3] = matrix.MultiplyPoint3x4(destinationVertices[vertexIndex + 3]);
    124.  
    125.                     destinationVertices[vertexIndex + 0] += offset;
    126.                     destinationVertices[vertexIndex + 1] += offset;
    127.                     destinationVertices[vertexIndex + 2] += offset;
    128.                     destinationVertices[vertexIndex + 3] += offset;
    129.  
    130.                     m_TextComponent.UpdateVertexData(TMP_VertexDataUpdateFlags.Vertices);
    131.                 }
    132.             }
    133.             /*// Push changes into meshes
    134.             for (int i = 0; i < textInfo.meshInfo.Length; i++)
    135.             {
    136.                 textInfo.meshInfo[i].mesh.vertices = textInfo.meshInfo[i].vertices;
    137.                 m_TextComponent.UpdateGeometry(textInfo.meshInfo[i].mesh, i);
    138.             }*/
    139.            
    140.             yield return new WaitForSeconds(0.05f);
    141.         }
    142.     }
    143. }
    144.  
    Thank you for your help!
     
  2. BlaiseRoth

    BlaiseRoth

    Joined:
    Oct 11, 2016
    Posts:
    8
    I haven't tried this, but you could try using the <link> rich text tag that is already supported. You can see it in action here:

    The idea is, the link tag can be used to mark some text that you want to do custom styling on. So instead of
    <squiggle></squiggle>
    you would do
    <link=squiggle></link>
    . Then you would just need to look in
    textInfo.linkInfo
    to check which character your links start and end, and what ID they're using to style it a certain way.

    Hope this helps. I'd be interested to see your code if you get it to work, because I'm thinking of doing something similar.
     
    DaseinPhaos likes this.
  3. hoperin

    hoperin

    Joined:
    Feb 9, 2014
    Posts:
    52
    Posting here because in trying to figure out how to make custom tags this showed up first and wasnt really answered, for anyone else trying to figure this out -- See this post that i found later by the maker of TMP! -- you have to include the hashcode in the tags switch statement in TMP_Text.cs to have it processed as a tag, and then you can put your functionality or a call to yopur functionality directly in there!
     
  4. gamesproi

    gamesproi

    Joined:
    Mar 30, 2018
    Posts:
    4
    In case anyone is still interested, I made a fully functional C# script in this thread that can apply new color and vertex data by using link tags, this method doesn't require any modification of the TMpro source code!



    However, a somewhat limitation of using this technique is that link tags have this long syntax:<link="TAG HERE"></link>, so yeah.
     
    Last edited: Nov 22, 2021
  5. sunfl0wr

    sunfl0wr

    Joined:
    Mar 11, 2015
    Posts:
    26
    A small bump here...

    was trying to find a way of adding custom tags but the best I find is (ab)using link tag like above or modifying the package source, neither seem very optimal imo.

    At the moment I've implemented this using link, but it's. far from smooth:

    * I'd like to be able to have xml attributes on my tags which makes link even more a hack
    * It seems (from my tests) you can't have a link tag inside another link tag meaning I can't apply multiple text effects on my text (example: I want some text to shake and one of the shaking words should also have some color effect)

    Is there any plans from you at Unity to add a way to register custom tags to TMP? I've seen this asked multiple time but never seen anyone working on the package saying anything about solving this
     
    pbb33 likes this.