Search Unity

maxVisibleCharacters for typewriter effect -- any way to recreate this in UIToolkit?

Discussion in 'UI Toolkit' started by burningmime, Mar 1, 2021.

  1. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    With TextMeshPro + UGUI an effect like this is achievable easily by using the maxVisibleCharacters property. This lets you do a "typewriter effect" (eg Visual Novels, Final Fantasy text boxes, etc). However, in TextCore it seems like this isn't exposed. I could keep adding characters to the string, but this would cause words to jump between lines as soon as they got too wide (very awkward and noticeable).

    I looked in TextParams and it seems like it's not there. Before I start poking around more with reflector...

    1) Is this exposed somehow already?
    2) If not, is this on the roadmap as a planned feature for the near future?
    3) If not, is there a relatively simple way to do this? Perhaps override Label's mesh generation and then only take a subset of the vertices?
    4) If not, is there a straightforward way to bridge TextMeshPro into UIToolkit without invoking UGUI?
    5) If not, is there a sensible way to integrate UGUI that won't break a bunch of scaling and Z ordering?

    Thanks!
     
  2. HugoBD-Unity

    HugoBD-Unity

    Unity Technologies

    Joined:
    May 14, 2018
    Posts:
    499
    Hi @burningmime!

    The logic related to maxVisibleCharacters is already in TextCore, but it isn't exposed through USS yet. Thanks for bringing it up, I added a task to track this and hopefully have it available for 21.2.

    Unfortunately, I don't see an easy workaround here, but you should be able to mix UGUI, TextMeshPro and UI Toolkit for runtime in the same scene. Just be aware that having a lot of interleaved elements could impact performance.
     
    Kirsche and burningmime like this.
  3. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    Cool thanks!

    Mixing them works fine for now, although it's a shame having 2 of the same font resource. And scaling is going to be an issue, although hopefully by the time I have to worry about that, it'll be part of TextCore.

    For anyone interested, here's the codes (very messy): https://gitlab.com/burningmime/easybuilding/-/blob/master/Assets/src/ui/MessageBox.cs
     
  4. Bennidhamma2

    Bennidhamma2

    Joined:
    Jan 5, 2019
    Posts:
    23
    I had a little go at this with Yew, because that's what you do I guess, when you're trying something new.



    Code (CSharp):
    1.  
    2. public class TypeWriter : View
    3. {
    4.     public string Text { get; set; }
    5.  
    6.     class Component : YewLib.Component
    7.     {
    8.         private TypeWriter Props { get; set; }
    9.         public Component(TypeWriter props) => Props = props;
    10.  
    11.         public override View Render()
    12.         {
    13.             var len = UseState(0);
    14.             UseRaf(() =>
    15.             {
    16.                 if (len >= Props.Text.Length) return false;
    17.                 if (Random.value > 0.3) return true;
    18.                 len.Value++;
    19.                 return false; // no need to call another raf because render will do it for us.
    20.             });
    21.             string text = Props.Text;
    22.             if (len < text.Length)
    23.                 text = $"{text.Substring(0, len)}<alpha=#00>{text.Substring(len)}";
    24.             return Label(text, className: "typewriter");
    25.         }
    26.     }
    27. }
    28.  
    can be used like:

    Code (CSharp):
    1. Yew.Render(new TypeWriter { Text = "you walk into a dark room..." }, uiDoc.rootVisualElement);
    2.  
     
    Last edited: Mar 4, 2021
  5. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    Thanks! But this has the problem of words jumping between lines when they get too long.
     
  6. Bennidhamma2

    Bennidhamma2

    Joined:
    Jan 5, 2019
    Posts:
    23
    No, it doesn't jump around. I'm rendering the whole string each time, but with the 'unseen' part as fully transparent text. That's what the '<alpha=#00' bit is doing on line 23.

    I made it partially opaque and slowed it down a bit, you can see here:



    I got the idea from the TextMesh Pro guy in this video:

     
    burningmime likes this.
  7. Bennidhamma2

    Bennidhamma2

    Joined:
    Jan 5, 2019
    Posts:
    23
    OK. I really need to go to bed. But, this is too much fun.

    Here's a version that uses Unity's coroutines. This seems to be more like how unity likes to do things maybe? I added a button after the text appears, which feels very 'game-y'.



    and here's how the animation works:

    Code (CSharp):
    1.             public override View Render()
    2.             {
    3.                 var len = UseState(0);
    4.                 var buttonOpacity = UseState(0f);
    5.                 IEnumerator anim()
    6.                 {
    7.                     while (len < Props.Text.Length) {
    8.                         len.Value++;
    9.                         yield return new WaitForSeconds(0.1f);
    10.                     }
    11.                     while (buttonOpacity < 1) {
    12.                         buttonOpacity.Value += 0.03f;
    13.                         yield return new WaitForSeconds(0.025f);
    14.                     }
    15.                 }
    16.                 UseCoroutine(anim);
    17.                 string text = Props.Text;
    18.                 if (len < text.Length)
    19.                     text = $"{text.Substring(0, len)}<alpha=#44>{text.Substring(len)}";
    20.                 return new StackLayout()
    21.                 {
    22.                     Label(text, className: "typewriter"),
    23.                     new Button("Examine body", () => { }, opacity: buttonOpacity)
    24.                 };
    25.             }
     
  8. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    Ah; I see. Nice hack!
     
  9. stevphie123

    stevphie123

    Joined:
    Mar 24, 2021
    Posts:
    82
    Hi, any news if this landed already or?
     
  10. HugoBD-Unity

    HugoBD-Unity

    Unity Technologies

    Joined:
    May 14, 2018
    Posts:
    499
    It, unfortunately, hasn't been exposed yet. You would need to implement your own version using an approach similar to what's mentioned above. I am sorry for the false hope. I would be glad to give you a hand implementing your own version if need be.
     
  11. LoneGoat

    LoneGoat

    Joined:
    Dec 16, 2020
    Posts:
    21
    I'm curious if there is a reason the `maxVisibleCharacters` property is not implemented/exposed?

    I'm missing it right now in a UI Toolkit project. A shame this hasn't been exposed as it is a nice clean solution. Frankly what I wish for just now is to be able to use TextMeshPro in a VisualElement instead of the simple Label VisualElement.

    It seems text handling has taken a backwards step with UI Toolkit.
     
  12. stevphie123

    stevphie123

    Joined:
    Mar 24, 2021
    Posts:
    82
    Yeah, it would be really nice if this somehow make it in the 2022...
     
  13. Loofou

    Loofou

    Joined:
    Aug 22, 2012
    Posts:
    25
    I created a custom VisualElement that can help in providing the functionality, based on the "<alpha>" idea above. Feel free to adjust to your needs!


    Code (CSharp):
    1. public class TypewriterLabel : VisualElement
    2.     {
    3.         public new class UxmlFactory : UxmlFactory<TypewriterLabel, UxmlTraits> { }
    4.  
    5.         public new class UxmlTraits : VisualElement.UxmlTraits
    6.         {
    7.             readonly UxmlStringAttributeDescription text = new() { name = "text", defaultValue = "" };
    8.  
    9.             readonly UxmlIntAttributeDescription maxVisibleCharacter = new() { name = "maxVisibleCharacters", defaultValue = 0 };
    10.  
    11.             public override IEnumerable<UxmlChildElementDescription> uxmlChildElementsDescription
    12.             {
    13.                 get { yield break; }
    14.             }
    15.  
    16.             public override void Init(VisualElement element, IUxmlAttributes attr, CreationContext cc)
    17.             {
    18.                 base.Init(element, attr, cc);
    19.  
    20.                 if(element is not TypewriterLabel typewriterLabel)
    21.                 {
    22.                     return;
    23.                 }
    24.  
    25.                 typewriterLabel.Text = text.GetValueFromBag(attr, cc);
    26.                 typewriterLabel.MaxVisibleCharacters = maxVisibleCharacter.GetValueFromBag(attr, cc);
    27.             }
    28.         }
    29.  
    30.         public string Text
    31.         {
    32.             get => text;
    33.             set
    34.                 {
    35.                     text = value;
    36.                     SetChildText();
    37.                 }
    38.         }
    39.  
    40.         public int MaxVisibleCharacters
    41.         {
    42.             get => maxVisibleCharacters;
    43.             set
    44.                 {
    45.                     maxVisibleCharacters = value;
    46.                     SetChildText();
    47.                 }
    48.         }
    49.  
    50.         public bool IsTextFullyVisible => maxVisibleCharacters >= text?.Length;
    51.  
    52.         readonly Label childLabel;
    53.         string text;
    54.         int maxVisibleCharacters;
    55.  
    56.         public TypewriterLabel()
    57.         {
    58.             childLabel = new Label
    59.                 {
    60.                     enableRichText = true
    61.                   , style =
    62.                         {
    63.                             fontSize = style.fontSize
    64.                           , unityFont = style.unityFont
    65.                           , unityFontDefinition = style.unityFontDefinition
    66.                           , color = style.color
    67.                           //, whiteSpace = style.whiteSpace <- this doesn't seem to work
    68.                         , whiteSpace = new StyleEnum<WhiteSpace>(WhiteSpace.Normal)
    69.                           , unityFontStyleAndWeight = style.unityFontStyleAndWeight
    70.                         }
    71.                 };
    72.             Add(childLabel);
    73.         }
    74.  
    75.         void SetChildText()
    76.         {
    77.             if(text?.Length > 0)
    78.             {
    79.                 int len = math.min(maxVisibleCharacters, text.Length);
    80.                 childLabel.text = $"{text[..len]}<alpha=#00>{text[len..]}";
    81.             }
    82.             else
    83.             {
    84.                 childLabel.text = string.Empty;
    85.             }
    86.         }
    87.     }
     
    flaquero, spakment and Kirsche like this.
  14. spakment

    spakment

    Joined:
    Dec 19, 2017
    Posts:
    96
    @HugoBD-Unity here's my vote +1 for maxVisibleCharacters getting exposed in USS, I'm using the above workaround but support would be really helpful for the Yarn Spinner community, thanks.
     
    antoine-unity and HugoBD-Unity like this.
  15. DavidNLN

    DavidNLN

    Joined:
    Sep 27, 2018
    Posts:
    90
    Any news on this? almost a three years old post.
     
    Gasimo and spakment like this.
  16. vertxxyz

    vertxxyz

    Joined:
    Oct 29, 2014
    Posts:
    109
    I don't really find any of these heavily-allocating workarounds very satisfying, and I can't seem to make the internal code that actually implements maxVisibleCharacters in TextCore work without it also affecting other text elements, (presumably because the maxVisibleCharacters isn't being reset in the UITKTextHandle). Very frustrating!
     
  17. Kandy_Man

    Kandy_Man

    Joined:
    Mar 8, 2014
    Posts:
    67
    @HugoBD-Unity Is there any progress on this? I'm using the method mentioned above

    label.text = $"{text[..charactersToShow]}<alpha=#00>{text[charactersToShow..]}";

    This works fine, however as soon as I want to style individual words in the text to highlight important information to the player, the typewriter effect shows the inline <color> tags as it is writing the text which is obviously not usable.

    I genuinely can't believe after 3 years and "hoping to have it in by 21.2", that we've made it to the Unity 6 beta and it still hasn't been made available to us. It doesn't even need to be exposed via USS, if it can just be made settable via C# that would be more than enough as a step 1 towards getting this task done.
     
  18. HugoBD-Unity

    HugoBD-Unity

    Unity Technologies

    Joined:
    May 14, 2018
    Posts:
    499
    I'm sorry for the disappointment. To be 100% transparent, one of our interns started working on the feature, but it was unfortunately not finished in time. The main remaining blocker was support for the USS transition.
     
  19. vertxxyz

    vertxxyz

    Joined:
    Oct 29, 2014
    Posts:
    109
    It's probably clear already, but as Kandy_Man said, this feels like it could be first implemented without USS support, which would mean that it's even possible to implement your own.

    In future can't features be broken down to separate the C# and USS implementations? So if stuff like this happens it can be broken down over multiple releases instead of presumably sitting on a branch as a whole with a large usable portion waiting for the USS capability.
     
    Last edited: Mar 28, 2024
    HugoBD-Unity and Kandy_Man like this.
  20. stevphie123

    stevphie123

    Joined:
    Mar 24, 2021
    Posts:
    82
    +1 to leave out uss support for it, for now at least. People mostly be doing this stuff on the c# side anyways

    At least it shows there's some progress that's been a hanging fruit for years now.
     
    HugoBD-Unity and Kandy_Man like this.
  21. Kandy_Man

    Kandy_Man

    Joined:
    Mar 8, 2014
    Posts:
    67
    Glad to see there has been progress, but as the posts above me have stated, the task of implementing this feature should have been broken down between the C# side and the USS side. Most of us using this feature are doing it on the C# side so why not ship that as a first pass, with the USS part coming in the following release?

    It's not like Unity has never shipped the MVP of a feature and then added to it over time. SRP and UI Toolkit are prime examples of tools meant to replace older tools of the engine but were shipped without being at feature parity of the tool it is replacing, then adding to it over multiple years to bring it up to parity.

    I just think shipping little and often, instead of nothing for years then a big drop, will get much more enthusiasm out of Unity's users to show that work is being done on things the community is saying they care about, and help with stability as Unity can never test a feature in the way that pushing it live can
     
    HugoBD-Unity likes this.
  22. HugoBD-Unity

    HugoBD-Unity

    Unity Technologies

    Joined:
    May 14, 2018
    Posts:
    499
    Thanks for the engagement and the feedback! I can't provide an ETA, but I'll have a look at the old PR and see if it can be revived and only expose the C# property.
     
    stevphie123, vertxxyz and Kandy_Man like this.
  23. Kandy_Man

    Kandy_Man

    Joined:
    Mar 8, 2014
    Posts:
    67