Search Unity

TextMesh Pro Typewriter, jitter and wave effects at once?

Discussion in 'UGUI & TextMesh Pro' started by Meorge, May 19, 2018.

  1. Meorge

    Meorge

    Joined:
    Feb 10, 2017
    Posts:
    21
    Hello,

    For our game, we want to allow dialogue to have various text effects at different points. For example, if a character is scared some of their dialogue may jitter, or if they're taunting the text might wave. To accomplish this, I wrote a custom parser that would take formatted text and get letter indices from it.

    For example:
    If I type the text
    <shake>These words shake.</shake> These words don't do anything. <wave>And these words wave.</wave>
    , the script would recognize the starting and ending indices of each group of tags. (In this case, there would be a "ShakeText" object starting at 0 and ending around 18 or so.)

    Next, I want to push this information to the TextMesh Pro object itself so the relevant vertices can be modified. For the sake of simplicity here's only the part of the coroutine that runs for the shaking letters (worked out using the TextMesh Pro example code):

    Code (CSharp):
    1.     public IEnumerator ShakeText(List<int> shakeIndices, List<int> waveIndices) {
    2.         textMesh.ForceMeshUpdate();
    3.  
    4.         TMP_TextInfo textInfo = textMesh.textInfo;
    5.         Vector3[][] copyOfVertices = new Vector3[0][];
    6.         hasTextChanged = true;
    7.  
    8.         TMP_MeshInfo[] cachedMeshInfo = textInfo.CopyMeshInfoVertexData();
    9.  
    10.         Vector3[] cachedVertexInfo = cachedMeshInfo[textMesh.textInfo.characterInfo[0].materialReferenceIndex].vertices;
    11.  
    12.         float g = 0f; // used for the sinewave
    13.         while (true) {
    14.             foreach (int index in shakeIndices) {
    15.  
    16.                 if (hasTextChanged)
    17.                 {
    18.                     if (copyOfVertices.Length < textInfo.meshInfo.Length)
    19.                         copyOfVertices = new Vector3[textInfo.meshInfo.Length][];
    20.                        
    21.  
    22.                     for (int i = 0; i < textInfo.meshInfo.Length; i++)
    23.                     {
    24.                         int length = textInfo.meshInfo[i].vertices.Length;
    25.                         copyOfVertices[i] = new Vector3[length];
    26.                     }
    27.  
    28.                     hasTextChanged = false;
    29.                 }
    30.  
    31.  
    32.  
    33.                 if (!textMesh.textInfo.characterInfo[index].isVisible) {
    34.                     continue; // dont need to shake it if its not visible!
    35.                 }
    36.                 Debug.Log("shake: " + textMesh.textInfo.characterInfo[index].character.ToString());
    37.                 float modX = Random.Range(-shakeAmount,shakeAmount);
    38.                 float modY = Random.Range(-shakeAmount, shakeAmount);
    39.  
    40.                 Vector3 modifier = new Vector3(modX, modY, 0f);
    41.                 int materialIndex = textMesh.textInfo.characterInfo[index].materialReferenceIndex;
    42.                 Vector3[] sourceVertices = cachedMeshInfo[materialIndex].vertices;
    43.                 int vertexIndex = textInfo.characterInfo[index].vertexIndex;
    44.  
    45.                 for (int i = 0; i < textMesh.textInfo.meshInfo.Length; i++) {
    46.                     copyOfVertices[materialIndex] = textMesh.textInfo.meshInfo[i].mesh.vertices;
    47.                 }
    48.  
    49.                 copyOfVertices[materialIndex][vertexIndex + 0] = sourceVertices[vertexIndex + 0] + modifier;
    50.                 copyOfVertices[materialIndex][vertexIndex + 1] = sourceVertices[vertexIndex + 1] + modifier;
    51.                 copyOfVertices[materialIndex][vertexIndex + 2] = sourceVertices[vertexIndex + 2] + modifier;
    52.                 copyOfVertices[materialIndex][vertexIndex + 3] = sourceVertices[vertexIndex + 3] + modifier;
    53.                
    54.                
    55.                 Debug.Log(textMesh.textInfo.meshInfo[materialIndex].mesh.vertices[0]);
    56.                 Debug.Log(textMesh.textInfo.meshInfo[materialIndex].mesh.vertices[1]);
    57.                 Debug.Log(textMesh.textInfo.meshInfo[materialIndex].mesh.vertices[2]);
    58.                 Debug.Log(textMesh.textInfo.meshInfo[materialIndex].mesh.vertices[3]);
    59.                 Debug.Log("-------");
    60.  
    61.                 for (int i = 0; i < textMesh.textInfo.meshInfo.Length; i++) {
    62.                     textMesh.textInfo.meshInfo[i].mesh.vertices = copyOfVertices[i];
    63.                     textMesh.UpdateGeometry(textInfo.meshInfo[i].mesh, i);
    64.                 }
    65.             }
    66.             yield return new WaitForSeconds(0.1f);
    67.  
    68.         }
    69.     }
    By itself, this code works okay - there is a notable frame rate drop, but the text animation itself looks good. I have run into a bigger issue, however, when attempting to do the "typewriter" effect at the same time as this. The shake animation will not run fully until the "typing" is complete.


    The typing effect itself I accomplish by incrementing the
    textMesh.maxVisibleCharacters
    property by one every few fractions of a second, via a function in my script DialogueParse.cs (same one that transforms the vertices) that is called from OverworldDialogueEngine.cs (one that regulates dialogue flow).

    Does anyone know why this is happening, and how I can fix it? (Additionally, if there is a more performant way of accomplishing this it would be very helpful to hear.)

    In case the information I provided above wasn't enough, here's the full code of both scripts that play a part in the dialogue process:

    DialogueParse.cs - used for parsing raw dialogue text and transforming the TextMesh Pro object's vertices: https://drive.google.com/open?id=1dQjH-va8Hked9c_x3vuNckyMvVhGtijL

    OverworldDialogueEngine.cs - used for regulating the flow of dialogue, calling for the "typewriter" effect: https://drive.google.com/open?id=1EX1IKSfif63V-tk8zKqTHwf3AEd3F0vh

    Please let me know if there's any more information you need. I'm happy to supply it!

    Thanks,
    Malcolm
     
    assertor likes this.
  2. NebulaVerse

    NebulaVerse

    Joined:
    May 11, 2014
    Posts:
    1
    Hi there! I'm working on a very similar project, and came across your question hoping to find a solution. No such luck, so I continued to think about it myself, and the solution I came up with is to animate the characters appearing by setting their alpha value instead of using maxVisibleCharacters.

    I based my code off of the VertexColorCycler example. So far, at least in my project, this hasn't seemed to hurt performance much, if at all. Best of luck to you!
     
    flashframe likes this.
  3. DearUnityPleaseAddSerializableDictionaries

    DearUnityPleaseAddSerializableDictionaries

    Joined:
    Sep 12, 2014
    Posts:
    135
  4. Meorge

    Meorge

    Joined:
    Feb 10, 2017
    Posts:
    21
    Not on my end, unfortunately. :(
     
  5. DearUnityPleaseAddSerializableDictionaries

    DearUnityPleaseAddSerializableDictionaries

    Joined:
    Sep 12, 2014
    Posts:
    135
    Update: Just tried your code, it makes my game lag a lot with a shakeAmount of 5. I'll see what I can do.

    Update2: Okay, commented out the Debug.log that was in the code, so now it's no longer lagging. My bad!
     
  6. DearUnityPleaseAddSerializableDictionaries

    DearUnityPleaseAddSerializableDictionaries

    Joined:
    Sep 12, 2014
    Posts:
    135
    ADD this line at the beginning of the for loop:
    if (index >= textMesh.maxVisibleCharacters) continue;

    And REMOVE the:
    if (!textMesh.textInfo.characterInfo[index].isVisible) { continue; }

    The fix sometimes works, sometimes doesn't. Not sure why...

    Edit: now the fix stopped working. What a strange bug. I understand your code but don't know what's wrong. Maybe incrementing maxVisibleCharacters resets the character position?

    Edit2:
    As I suspected. If you increase the wait time for the yield return WaitForSeconds in the for loop that increments maxVisibleCharacters, then it is fixed. The reason why is because incrementing maxVisibleCharacters does really seem to reset the position of the characters, and because it does it at a high frequency, then it overwrites your shake offset before rendering the frame. See https://forum.unity.com/threads/tmp-and-vertex-changes.514662/

    Thus, a hacky fix is to "yield return new WaitForEndOfFrame();" after yielding WaitForSeconds in the for loop where you increment the maxVisibleChars. It shakes a bit less until the characters are all visible. Incrementing maxVisibleCharacters re-renders.

    To fix properly, you need to implement your own maxVisibleChars. Maybe by moving the characters offscreen or something and then putting it where it belongs. If you do, would be nice if you could share with us here.

    Another way, is in the same loop as you are incrementing maxVisChars, get the vertices before incrementing, and then revert them back after incrementing.
     
    Last edited: Aug 21, 2018
  7. Daahrien

    Daahrien

    Joined:
    Dec 5, 2016
    Posts:
    100
    the parser code is not the parser code lol. Can you share it :D
     
  8. DearUnityPleaseAddSerializableDictionaries

    DearUnityPleaseAddSerializableDictionaries

    Joined:
    Sep 12, 2014
    Posts:
    135
    Any luck?
     
  9. Meorge

    Meorge

    Joined:
    Feb 10, 2017
    Posts:
    21
    One of the programmers on our team actually was able to make a system that does this... unfortunately I'm not really sure how it works exactly, haha. I'll ask him if he's cool with me posting it online for public use.

    EDIT: He said he's cool with it! Here's the link to the repo: https://github.com/Meorge/TextMeshProAnimator
    There's an HTML file in the About folder that explains how to use it, but unfortunately I don't have time at the moment to write up/rewrite the documentation for Markdown. Hope it's helpful - and let me know if you have any questions!
     
    Last edited: Apr 22, 2019
    AryShirazi and Alverik like this.
  10. AryShirazi

    AryShirazi

    Joined:
    Mar 25, 2015
    Posts:
    4
    Hi Meorge, this is really comprehensive stuff! Unfortunately, as it doesn't support '<' or '>' you end up losing the awesome Rich Text tags that make Textmesh Pro so brilliant. Is there something I'm missing here, or does the TextMeshProAnimator have a way of utilising all the Rich Text tags?
     
  11. edwardrowe

    edwardrowe

    Joined:
    Feb 11, 2014
    Posts:
    52
    We've also spun up our own library that does this. Figured I'd share it, in case you want to adapt anything for use. Feel free to use it if you want, but I'm not trying to plug it. Just trying to provide some more solutions. https://github.com/redbluegames/unity-text-typer
     
    crafTDev and m4d like this.
  12. Meorge

    Meorge

    Joined:
    Feb 10, 2017
    Posts:
    21
    I just pushed an update to it that should add support for rich text color and size tags. If there are other tags you want to add support for you can add them in pretty easily (currently line 172).
     
  13. AryShirazi

    AryShirazi

    Joined:
    Mar 25, 2015
    Posts:
    4
    @edwardrowe

    Thank you so much for sharing your work! That curve editor functionality is truly awesome!

    @Meorge

    Thanks to you for adding additional support for rich text tags! After trialling this out, although it is receiving the rich text, it is suffering from applying the animation correctly across the entire word.

    For example:

    What's going on <color=red><b><shake>HERE</color></b></shake> then!?

    The shake animation is being applied on the final E in HERE and carrying on to the 'then' characters too.

    If I switch the position of the <shake> tags as follows:

    What's going on <shake><color=red><b>HERE</shake></color></b> then!?

    Now the shake is being applied from the beginning of HERE and encapsulating all of 'then' also.

    -----------

    Thanks again to you both, you've been a great help!

    Ary
     
    edwardrowe likes this.
  14. Meorge

    Meorge

    Joined:
    Feb 10, 2017
    Posts:
    21
    The examples you posted have incorrectly nested tags; I have a feeling that's what's causing the issues.
    Try this:
    What's going on <color=red><b><shake>HERE</shake></b></color> then!?
     
  15. AryShirazi

    AryShirazi

    Joined:
    Mar 25, 2015
    Posts:
    4
    Giving this a go, it makes the final E of ‘here’ shake and the ‘the’ of then, which are outside of all the rich text.

    Apologies for the delay in getting back to you mate.

    Ary
     
  16. Juliken

    Juliken

    Joined:
    Jan 30, 2017
    Posts:
    17
    Hello Meorge, is your script still supposed to work ? I tried to use it but everytime it makes my TMP text invisible.

    If I change the geometry of the TMP window on runtime the text appear briefly but disapear when the window size is fixed.

    Maybe I use the script in wrong way but I simply followed the readme document.

    I'm using Unity 2019.1.4f1 and TMP 2.0.1.

    EDIT : I managed to make it visible. I just had to uncomment the TMProGUI.ForceMeshUpdate() on line 661. But the shaking (or other effects) is still not working. It could be really usefull as I need to apply effect on single words.

    EDIT2 : Finaly the text is shaking (I forget to increase the "Chars Visible" value and the line 661 need to stay in comment) but I have the same problem has Ary. The closing tag </shake> doesn't work properly. The shaking is applied to 5 more characters (same number of character in the word "shake" ?) And the intensity doesn't work with decimal value.
     
    Last edited: Jun 26, 2019
  17. Meorge

    Meorge

    Joined:
    Feb 10, 2017
    Posts:
    21
    Hi all,

    It's strange to hear that it's not working for you - last I was using it it seemed to be working perfectly for me. I haven't been working on the project using that tool for a while, but hopefully I'll get a chance to go back to it and look into these issues soon.
     
  18. Crouching-Tuna

    Crouching-Tuna

    Joined:
    Apr 4, 2014
    Posts:
    82
    @Meorge
    Hi. It's working fine for me.
    But i do have a problem when using the charsVisible = i, for a typewriter effect.
    It seems when there's a ContentSizeFitter, changing TMP text (or the color), will mark it to be rebuilt at the end of frame.
    So, when i change text, then start the Typewriter coroutine, it'll display the whole text for 1 frame (due to the rebuild), THEN the typewriter starts from 0 correctly in the next frame.

    Even in this order:
    1. Change text
    2. charsVisible = 0
    3. Manual TMPAnimator.Update(), since it contains all the vertex modifying, set Vector3.zero for any char > charsVisible
    4. Start typewriter (incrementing charsVisible per frame)

    The full text flicker for 1 frame still happens.
    Even when changing TMPAnimator.Update to LateUpdate, it still happens (shouldn't matter since #3 already manually calls it)

    @Stephan_B
    I wonder if there's a way to manually rebuild after a text change.
    Something like:
    1. Change text
    2. tmp.ManualRebuild() -> i assume it also set isDirty = false; so it doesn't get called again
    3. I can do the vertex modifying here in the same frame
    Or something similar already exists?

    Cheers
     
    Kolalamonkeys likes this.
  19. ShiackHeron

    ShiackHeron

    Joined:
    Oct 24, 2017
    Posts:
    13
    @Crouching-Tuna
    I know this is an old post, but what I do is cover the text so it doesn't show the flicker and when it starts to appear I disable the cover and no one notices, right now I'm just testing the code , just seem to have problems mixing tags
     
  20. Kolalamonkeys

    Kolalamonkeys

    Joined:
    Aug 16, 2020
    Posts:
    1
    Did you ever figure this out, @Crouching-Tuna?
     
  21. LatifY

    LatifY

    Joined:
    Aug 26, 2018
    Posts:
    2
    I tried LateUpdate and it worked!
     
    kocoguzhan697 likes this.