DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations

How to Enable Auto Word Wrapping in Mixed Reality Apps

Want to learn how to enable automatic word wrapping on your Mixed Reality app? Check out this tutorial to learn how to enable auto word wrapping.

Joost van Schaik user avatar by
Joost van Schaik
·
Aug. 14, 18 · Tutorial
Like (1)
Save
Tweet
Share
4.77K Views

Join the DZone community and get the full member experience.

Join For Free

Introduction

As Mixed Reality and HoloLens apps, in particular, become more mainstream, they become even more complex, and this is a reflection of their interfaces. And if in those user interfaces, we need to communicate something more complex to a user, the text is, in most cases, still the most efficient way of doing that.

Now, a floating text by itself may not be very readable. You might want to use some kind of backdrop. I usually take a kind of blueish background with white text, as that turns out to be the most readable. I know, it feels a bit 2D Windows-ish — but until someone comes up with a better paradigm, it will have to do.

A TextMesh in Unity does not have a clue about word wrapping or fitting into a specific 'box' — you need to do that yourself. There is some of that in the Mixed Reality Toolkit dialog system – but that wraps based on the maximum of characters. If you change the font size or the size of the backdrop, you will need to start testing again to see if your message fits. And, there is no need for that here.

Actual Text Wrapping

public static class TextUtilities
{
    public static void WordWrap(this TextMesh mesh, float maxLength)
    {
        var oldQ = mesh.gameObject.transform.rotation;
        mesh.gameObject.transform.rotation = Quaternion.identity;
        var renderer = mesh.GetComponent<Renderer>();
        var parts = mesh.text.Split(' ');
        mesh.text = "";
        foreach (var t in parts)
        {
            var builder = new StringBuilder(mesh.text);

            mesh.text = string.Concat(mesh.text, t, " ");
            if (renderer.bounds.size.x > maxLength)
            {
                builder.AppendFormat("{0}{1} ", Environment.NewLine, t);
                mesh.text = builder.ToString();
            }
        }

        mesh.text = mesh.text.TrimEnd();
        mesh.gameObject.transform.rotation = oldQ;
    }
}


This sits in an extension method of the TextUtilities class. It assumes that the text has already been applied to the text mesh. What is basically does is:

  • Rotate the text mesh to Identity so it can measure the width in one plane
  • Split the text into words
  • For each word:
    • Make a StringBuilder for the text so far
    • Add the word to the mesh
    • Calculate the width of the resulting mesh
    • If the mesh is wider than allowed:
      • Add the word to the StringBuilder with a newline prepending it
      • Set mesh to the StringBuilder’s contents

Now, I did not make that up myself; I nicked it from here in the Unity Forums, but I kind of simplified and optimized it a bit.

Calculating the Size

As I have written in a previous post, you can calculate an object's size by getting the Render's size. But, I also show what you get is the size after rotation. So, what you need to do is to calculate the unrotated size. I put the same trick as used in the way to measure the text width in an extension method:

public static class GameObjectExtensions
{
    public static Vector3 GetRenderedSize( this GameObject obj)
    {
        var oldQ = obj.transform.rotation;
        obj.transform.rotation = Quaternion.identity;
        var result = obj.GetComponent<Renderer>().bounds.size;
        obj.transform.rotation = oldQ;
        return result;
    }
}


Rotate the object to identify, get it's render's size, return the object back, then return the result. While this is a rather crude way to get to the size,  it seems to work. I stuck this method into my GameObjectExtensionsclass.

Connecting All the Dots

Now, the only thing missing is a behavior that will use all of this:

public class SimpleTextDialogController : MonoBehaviour
{
    private TextMesh _textMesh;

    [SerializeField]
    public GameObject _backPlate;

    [SerializeField]
    public float _margin = 0.05f;

    void Start()
    {
        _textMesh = GetComponentInChildren<TextMesh>();
        gameObject.SetActive(false);
    }

    public void ShowDialog(string text)
    {
        gameObject.SetActive(true);
        StartCoroutine(SetTextDelayed(text));
    }

    private IEnumerator SetTextDelayed(string msg)
    {
        yield return new WaitForSeconds(0.05f);
        _textMesh.text = msg;
        var sizeBackplate = _backPlate.GetRenderedSize();
        var textWidth = sizeBackplate.x - _margin * 2f;
        _textMesh.WordWrap(textWidth);
        _textMesh.GetComponent<Transform>().position -= new Vector3(textWidth/2f, 0,0);
    }
}


The start method immediately renders the game object invisible. Calling the ShowDialog makes the dialog visible and actually sets the text by calling the ShowTextDelayed coroutine, where the stuff is actually happening. First, we get the size of the backplate. Then, we calculate the desired width of the text. After that, the text is word-wrapped, and then it's moved half the calculated width to the left.

So, why the delay? This is because the complete dialog looks like this:

image

I reused the AdvancedKeepInViewController from my previous post. But, if you use the AppearInView property (as I do), that places the dialog's center exactly on the gaze cursor when it's activated. And, you will see that it tends to appear on the left of the center, then quickly moves to the center.

That is because when the text is rendered without the word wrap, it looks like this:

image

Because of this, Unity calculates the horizontal component of the center of all the objects in the combined game object that end up a little left of the center of the text. But, what we want to see is this:

image

The text is nicely wrapped and fits inside the box. Hence, there is a little delay to allow the AdvancedKeepInViewController to calculate and place the object in the center before we start messing with the text.

Finally, there's a simple behavior called DialogLauncher,  but, basically, all that does is call the ShowDialogmethod with some text I got from "Startupsum."

The fun thing is that when you make the dialog bigger and run the app again, it will automatically word wrap to the new size:

image

And, when you increase the font size, it will look like this:

image

Requirements and Limitations

There are four requirements to the text:

  • The text needs to be placed in the horizontal center of the backdrop plate.
  • Vertically, it needs to be in the position where you want  the text to start to flow down from:image
  • Needs to have its anchor in the upper left
  • The alignment needs to be left

Limitations

  • If you have a single word in your text that's so long it takes up more space than available, you are out of luck. 
  • As you can see a little in the last image, if the text is so long it takes up more vertical space than available, Unity will render the rest "in thin air" under the backdrop.

It's actually possible to fix that too, but you might wonder how efficiently you are communicating if you need to write large texts in what is, in essence, an immersive experience. How many people read an entire six-paragraph text in a zoo or a museum? "Brevity is the soul of wit," as the Bard once said, so keep texts short and concise!

Conclusion

Thanks to Shopguy on the Unity Forums for the original word wrapping code. The code for this article can be found here.

app

Published at DZone with permission of Joost van Schaik, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • What Is JavaScript Slice? Practical Examples and Guide
  • Introduction to Container Orchestration
  • What Are the Benefits of Java Module With Example
  • Getting a Private SSL Certificate Free of Cost

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: