Why don't the text components share an interface?

Right now, there are several text components with no shared base class or interface and no good way to write reusable code that works with all of them:

//This component works with many kinds of text, but the code sure is messy!
public class ExampleReusableComponent : MonoBehaviour {
    protected Text text;
    protected TextMesh textMesh;
    protected TMP_Text textMeshPro;

    void Start() {
        text = GetComponent<Text>();
        textMesh = GetComponent<TextMesh>();
        textMeshPro = GetComponent<TMP_Text>();
    }

    private float value;
    public float Value {
        get => value;
        set {
            this.value = value;
            RefreshText();
        }
    }

    public void RefreshText() {
        if (text != null) text.text = value.ToString();
        if (textMesh != null) textMesh.text = value.ToString();
        if (textMeshPro != null) textMeshPro.text = value.ToString();
    }

}

It seems clear that there should be an interface that is shared by these unrelated components, to make our lives easier in writing reusable code:

public interface ITextComponent {
    string text { get; set; }
}

The we could make our code so much cleaner:

//So much better!
public class ExampleReusableComponent : MonoBehaviour {
    protected ITextComponent text;

    void Start() {
        text = GetComponent<ITextComponent>();
    }

    private float value;
    public float Value {
        get => value;
        set {
            this.value = value;
            RefreshText();
        }
    }

    public void RefreshText() {
        if (text != null) text.text = value.ToString();
    }
}

While I could understand someone finding a reason to need an interface between GameObject and Component, there is no reason for someone to need an interface between Text, Text Mesh, and Text Mext Pro because there are no advantages to the first two that I’m aware of while there are many disadvantages. If you’re not using TMP you’re doing something wrong.

2 Likes

The biggest disadvantages I’ve run into with TMP are:

  • Requires creating a texture atlas for each font, which increases the build size and can be problematic for web and mobile builds, especially when using a large variety of fonts or including Asian character sets. One game I worked on included a CJK font for localization and the atlas ended up being something like 35mb, which was a significant portion of the total build size.
  • TMP is difficult to manage with Git because the texture atlases are baked into the .asset files. I use “Force Text” serialization to make all my asset/meta files Git-friendly, and use Git LFS for binary files like images and 3D models. But the TMP .asset files come out as text files which can have 10mb+ of binary image data baked into them, at which point they don’t fit neatly into regular Git or LFS.
  • When I am designing an asset for sale on the Asset Store, I want to make it as flexible as possible, which ideally means not forcing people to use a specific text system.
  • TMP is significantly more complicated to set up than Unity’s built-in text components, which can be problematic when working on freelance projects with team members of varying skill levels.
3 Likes

Basically, Unity’s built-in text went more or less unloved for ages. TextMesh Pro was purchased and brought in house to be used as an improved replacement, but other than making it free it hasn’t been fully integrated yet. The impression I get is that they’re taking the time to get a bunch of the above right before they do that.

Until that’s finished… yeah… things are pretty weird right now.

@Stephan_B might be able to shed light on this.

The legacy text system also requires an atlas and material per font as seen in the image below. From a structural point of view these assets are identical.

6099588--663171--upload_2020-7-16_20-27-28.png

In terms of the atlas size, the legacy text system only supports bitmap textures which by their nature end up quickly growing to larger sizes than those used by TMP with SDF.

In order for bitmap text to render correctly, the glyphs (visual representation of characters) have to be rendered at 1:1. As such, you end up adding multiple copies of the same glyph (one for each point size used) in the atlas texture thus quickly growing the size of the atlas texture. By contrast, SDF rendering only requires one representation of each glyph for all point sizes and scale.

If you were to prepare a static font to support the same CJK text at multiple point size, the atlas texture of the legacy text system would be much larger and unlikely able to handle all the needed characters.

The only reason why it seems like the legacy text system uses smaller textures is because when you create a build, they are empty and get populated at runtime. Prior to the introduction of the dynamic system for TMP, you could only use static font assets where given these were already populated, resulted in larger build size as you were comparing shipping an empty texture vs a filled texture.

Since the introduction of the dynamic system for TMP, you can now use a mixture of static and dynamic font assets. Static font assets are as they have always been. Dynamic font assets are empty by default with their atlas at size zero. As such, they no longer add to build size in this empty state. The atlas texture of dynamic font assets gets resized to their defined size when the first glyph is added to them.

Dynamic font assets can be shipped partially filled. In the Editor, changed to dynamic font assets are persistent but not in build / runtime. As such, they are always reset to their default state for each play session thus ensuring they will never grow to some unmanageable size.

There is also a new feature called multi atlas texture where new atlas textures are created as need to allow the font asset to essentially handle every single character contained in a font file. This is not available with the legacy text system.

There are performance difference between static font assets vs. dynamic. Static offer the best performance as everything is baked in but given their atlas texture is initialized (not size = 0) they contribute to build size. Dynamic font assets have a higher performance overhead but potentially do not contribute to build size. This performance overhead is significant in terms of compute time but substantially faster than any human could ever type. As such, when used properly, this overhead is unnoticeable by users.

When shipping your project, I still recommend using a combination of static and dynamic font assets. Static font asset should contain all the known text for any given language or groups of language. Known text is everything contained in your UI, menus, etc. The dynamic font assets are used as fallback to handle unknown text which is typically coming from user input.

During the development process, I do recommend running full dynamic with multi atlas texture support enabled. Then as you get closer to shipping and have all the known text, you then prepare your static and dynamic font assets.

At anytime, a dynamic font asset can be switched to static and vice-a-versa. The above process is actually pretty simply once you have all the known text.

You can also create font asset at runtime from system fonts.

TMP offers a ton of functionality over the legacy text system. It is a much more powerful tool and certainly more complex in some ways.

However, creating a dynamic font asset to handle CJK take 10 seconds where this font asset can handle every single character available in the font file. Most of the time, the challenge is in find / picking the right font file.

I will stop here as I could go on … but I do understand that on the surface TMP appears much more complex. Part of that is due to a lack of documentation which is my fault. I have been focusing on support and functionality which has left me little time to work on documentation. The good news is new documentation is coming. We will also be adding more people to the text team which up until now has been me.

In the meantime, please take a look at the following two video as they cover all the core functionality related to font assets.

https://www.youtube.com/watch?v=NY1xKqCIj3c

https://www.youtube.com/watch?v=pLW2B98W5AU

I have covered this type of information several times on the forum. Should you have any questions, please be sure to search the forum / google as you may find the answer to your questions.

However, never hesitate to post on the forum in the TextMeshPro & UGUI section as I will be more than happy to provide assistance if other users have not answered before me.

6 Likes

Can you share anything about plans moving forward in regard to a shared base class and/or interface moving forward?

First, the legacy text system is no longer on active development while TMP remain very much.

In the 2021 cycle or perhaps sooner, the create menu items for legacy text components such as 3D Text (TextMesh) and UI - Text will get the (Legacy) label added to them. The TMP components will just be called Text. For compatibility reasons, the underlying classes names will not change at this time.

For a while now, I have been working on the new TextCore which is new and more powerful text parser and layout engine that will replace the TMP internal parser and layout system. This new layout engine will be used by the Editor, TMP and UI Toolkit, etc.

With regards to TMP, the transition to TextCore should be pretty seamless. Besides new functionality that will be added overtime, short term the will be a performance improvement with pretty significant memory overhead reduction.

In terms of text components, both TMP components derive from the same base class TMP_Text. For compatibility reasons, this will remain.

In terms of an interface, it makes sense between text components that live in a scene but not so much in my opinion with UI Toolkit text labels. What do you all think?

5 Likes

I can’t see any reason to or benefit from drawing a distinction between scene and non-scene labels. That’s not the interface’s job, it’s just describing that the implementing class is able to represent text. If it’s operating via an interface then the callng code should know as little about how the text is represented as possible. If the caller cares then it probably does need a more specific type.

Thinking aloud, the interface should be really simple, possibly to the point that it’s just got SetText(strint text) and GetText() methods in it. It probably shouldn’t be concenred with formatting or visibility or anything else, because that’s all implementation specific (and there’s no reason to expect that all implementations are letters on a screen, eg: maybe it’s going to text-to-speech device). Perhaps you could optionally include a style name or ID, which the implementing class can optionally support?

If an interface is added, could it be done in advance of TextCore being rolled out? That would give people an opportunity to move relevant code over to using the interface in advance, which for a lot of code may then mean that TextCore is supported transparently when it becomes available. And if anyone wants to implement non-traditional text systems they have a place they can also hook in transparently, rather than also requiring an extra code path for support.

I’m also mindful of Asset Store developers. If they have to support a 3rd set of text classes it could at least be designed to minimise the impact of when this happens again a few years down the track.

1 Like

@Stephan_B Thanks for jumping in with your input.

To be clear, I don’t personally struggle with using TMP, but I am a full-time freelancer and work with developers of varying skill levels across the various projects I work on. TMP is very powerful and flexible but that comes at the cost of being significantly more complicated to set up, which can overwhelm developers who are new to Unity or are overseas and don’t have the best grasp of English. This does remind me of another concern I have: too much reliance on videos for conveying how to use TMP and new features in TMP. I personally find videos to be a terrible substitute for documentation. You cannot ctrl-f in a video. You cannot skim a video. You cannot copy a relevant passage out of a video and paste it to a team member.

That’s still a significant benefit for web and mobile builds when you are trying to keep the build size as small as possible.

By the way, I would REALLY appreciate it if you moved the textures out of the .asset files and made them separate image files we can easily store in Git LFS.

Exactly. And again I want to stress the (at least short term) benefit of adding the interface to Unity’s legacy text components as well. It just makes things easier for designing modular code, which is something I try to do with Asset Store packages. I understand that the legacy text is not under active development but hopefully it’s not difficult to convince someone to spend the ten seconds it will take to add , ITextComponent to the class declaration.

2 Likes

If you’re having to deal with them on a regular basis and you speak their language why not create a very brief tutorial?

I don’t speak their language if it’s not English; we communicate in whatever they know of English. And again I’ve also had trouble with native English speakers that just don’t have much Unity experience. I guess I could put together a tutorial for them, but there are usually more important things to focus on. Anyway, the bullet points I listed are roughly in order of significance; “TMP is more complicated than legacy text” was at the bottom of the list because it’s the least important point in my mind; it was merely worth mentioning.

Anyway, this has gotten way off-topic. I didn’t create this thread to go bashing on TMP; I only brought up those points in response to your first post. I just want to see a shared interface added to the various Text components for greater flexibility.

1 Like

Here are the steps to create a font asset that will support every single character contained in the selected font file.

(1) Select and Import the font file that support the targeted language. Here let’s use NotoSansCJK-Regular.ttc
(2) Select the font file in Unity and use the context menu “Create - TextMeshPro - Font Asset” or CTRL-SHIFT-F12.
(3) Select newly created font asset and enable Multi Atlas Texture.
(4) Create a new TMP text object and assign this new font asset to it.

You can now start typing where every single character contained in this font file will be displayed.

The above process takes seconds to complete.

The step to create the font asset and enable Multi Atlas Texture are extra steps vs the Legacy system but provides users with a substantial amount of added functionality. An easy one here is proper line breaking for CJK which the legacy text system doesn’t support.

P.S. Although we can create font assets at runtime time from OS font files, I will be improving this functionality to provide better control over what potential OS fonts will be used.

I didn’t take you comments as bashing so no worries there. Your comments do reflect the poor documentation which results is a lack of understanding of the tool. We will be addressing the documentation issue.

Until then, I am here to try to provide as much information as possible and assistance to users while using this feedback to make additional improvements to the tool and try to make things as intuitive as possible without limiting functionality.

In terms of the Interface with the Legacy text components, since these components are no longer on active development, we will not be adding any new functionality to them. More importantly, we want to gradually shift over all users of the legacy text to TMP / the new text system.

Using dynamic font assets provides the same benefits here.

My concern with this is new users including the font asset in their build but forgetting to include the texture.

Just like a Font object does contain a Material and Texture as sub objects so does the Font Asset.

Note: Since it is already challenging to handle all the TMP related stuff in the TextMeshPro & UGUI section of the forum, I don’t have time to look at any other forum section. As such, please provide feedback / report issues in that section of the forum.

Always feel free to @Stephan_B as @angrypenguin did here otherwise I would not have been aware of this thread.

Bottom line is I am here to try to provide you with the best possible text system / tool for your game development needs. This is an ongoing process as game developer needs evolve over time. Please keep on using the tool and providing feedback. I am here for you all.

2 Likes

This part I don’t understand. In most cases the user doesn’t need to choose what to include in their build; the engine automatically includes assets that are referenced and excludes assets that aren’t referenced. The main exception I can think of is Addressables, but Addressables is an advanced system and so poorly documented that novice users probably won’t be trying to use it anyway.

Is there anything you can tell us about the chance of something like an ITextComponent interface getting added to the various text components?

Again, TMP Font Assets and Font assets have the same exact structure.

Atlas textures of Dynamic TMP Font Assets are at size zero / empty by default which is also similar the Font assets. However, TMP Font Asset Atlas texture can be partially filled or full.

Splitting Atlas Texture and Materials would require users tracking what atlas texture and material belong to what font asset. This would quickly become a nightmare to manager.

I will be exploring adding an interface between the TMP components.

Since the other text components are no longer on active development, an interface will not be added between them. Again, we want to transition users away from those.

1 Like

TMP i so easy to use currently. Is this a part of the Unity way versus enterprise way beef? The OP posted code he claims is cleaner. I don’t think so. It does not look straight forward. What the hell should it be in a protected class or function for I see no need. Second…what if i do not need an interface implemented on top of the monobehaviour and TMP is only a smaller part of a large UI class I am using. This garbage is what made ECS such a pain to work with. You always had to refer back to docs instead of intuitively pounding your application together as quick as the mind has flashes of insight.Then it is just a bunch of verbosity to cater to people who came from enterprise or MSDN methodologies. I suggest he should shift his headspace. I sincerely hope this kind of mission creep into Unity stops . It is a creativity killer.

I don’t know about the points concerning the text components that are soon to be out dated but based on his statement in the eleventh post I’m inclined to believe the difficulty aspect of this thread is a case of him hiring the cheapest help he can find rather than quality help. I haven’t seen anyone stumbling over TMP that wasn’t likewise a complete beginner.

I’d have thought that the suggestion here would help with that, because it allows people to start writing or updating code sooner which is compatible with the new system, in a way which can allow straightforward component swapouts later.

Without a shared interface the code and the components have to be switched out simultaneously, which might mean some of that “legacy” code sticks around longer.

I’ve never managed a transition like this one before, but I’d suspect that the lowest friction approach would be to allow code to work as seamlessly as possible between all related objects, but strongly discourage people from making new components of the old types.

The sticky part for developers will be cases where the code changes text and formatting. That’s pretty darn rare for me, but I wouldn’t assume that’s the same for everyone else.

But that’s not what this is about at all? It’s about the difference between this…

public void RefreshText() {
    if (text != null) text.text = value.ToString();
    if (textMesh != null) textMesh.text = value.ToString();
    if (textMeshPro != null) textMeshPro.text = value.ToString();
    if (textCore != null) textCore.text = value.ToString();
}

…and this…

public void RefreshText() {
    if (text != null) text.text = value.ToString();
}

… and that with one (seemingly) reasonably small change on Unity’s side the latter could replace the former.

I love TMP. It rocks. I don’t even have to think about using it. I just use it. The toughest part is remembering the “using TMPro”. In the OP example he uses this “=>” which is called a lambda AFAIK. This is not in line with Unity component based architecture for basic class components. That style would seem to not allow me to just declare the field in my UI classes or wherever else i used TMP.

Or an even smaller change would be to add TMP in front of his textfield declaration in his class. Use Find and Replace…bing bang zoom…done…Why complicate stuff unnecessarily in an attempt to make thing “simpler”. Maybe the OP oughta use the API the way it works instead or wanting the community which has been around 15+ years to adopt to his way. I can refresh any text with one conditional or less…one line of code…without implementing an Interface alongside the monobehaviour… I don’t get the issue except that it seems to be lack of understanding of how Unity does things across the board. If OP or anyone else want to play with Interface implementation and lambdas then implement it in their time and application and do not try to force this on others who care more about simplicity and the established Unity way of doing things. ECS suffers due to Interfaces and lambdas. If it worked like the rest of the API has it would be awesome. The amount of time I spent trolling through docs to get a physics sim working was like 25 to 1, where if it worked like the rest of the API it would have taken little time at all to accomplish the same. Like learn the names of properties and methods and went straight to town writing the sim.

What??? Complicated??? Put using TMPro in the header of the class then declare your fields.

using TMPro;

public class  MyUI  : Monobehaviour {
    
     public TMP_Text speedometerLabel = "MPH";
     public TMP_Text speedometerReadout;

     public void Speedometer (float speed) {
          speedometerReadout.text = speed.ToString();

}

I can now reference my textfields and update my Speedometer from anywhere I choose to as the framework develops into more complexity. If a dev can’t figure this out they should go back to WPF dev.