How do I transform text in world-space like normal game objects?

I have a TextMesh showing up in the scene. I need to transform it in global space as if I were sticking notes on the surfaces of objects. First of all, the GameObject myText doesn’t respond to any transforms, which is weird.

Besides that, the transform values for textMesh are not what I would expect. I’ve learned that Unity treats text differently, and it is somehow related to screen resolution (and I very much do not want that) I can’t find much discussion on how people get around this problem. I’m looking for a simple solution - is there a single Unity transformation function that would do the trick?

Note: localScale and eulerAngles [appear] to be consistent with world-space, but not position or fontSize.

Here’s my code:

GameObject myText = new GameObject();

myText.transform.position    = new Vector3(  0.0f, 10.0f, 0.0f );
myText.transform.localScale  = new Vector3( 10.0f,  0.0f, 0.0f );
myText.transform.eulerAngles = new Vector3(  0.0f, 60.0f, 0.0f );

TextMesh textMesh = myText.AddComponent<TextMesh>();

textMesh.text = "myText";		
textMesh.fontSize = 30;

textMesh.transform.position    = new Vector3( 0.0f, 0.0f, 3.0f );
textMesh.transform.localScale  = new Vector3( 0.1f, 0.1f, 0.1f );
textMesh.transform.eulerAngles = new Vector3( 0.0f, 0.0f, 0.0f );

Any insights would be greatly appreciated.

Thanks!

@anon21877998 Here’s a code I put together for you. It works on my end. Attach it to any gameObject or the camera. Then from any script, all you need to create a text is :

UIManager.Instance.AddText("text","textid",fontSize,globalPosition, eulersAngles );

Also I added a little function so you can remove the text if you want but you will need the textid you used when you created it:

UIManager.Instance.RemoveText("textid");

I added a text demo, you can delete it if you like. I put DELETE comments on it.

using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class UIManager : MonoBehaviour
{
    private RectTransform panel;  // Reference to the RectTransform of the UI Panel.
    public List<TMP_Text> textList;  // List to store TMP_Text components.
    public static UIManager Instance;  // Singleton instance of the UIManager.

    // Awake is used to initialize any variables or game state before the game starts.
    private void Awake()
    {
        // Ensure only one instance of UIManager exists.
        if (Instance == null)
            Instance = this;
        else
            DestroyImmediate(this);
    }

    // Start is called before the first frame update.
    void Start()
    {
        SetupUI();
    }

    // Sets up the UI components.
    void SetupUI()
    {
        // Create a new Canvas object and add required components.
        GameObject canvasObject = new GameObject("UICanvas");
        Canvas canvas = canvasObject.AddComponent<Canvas>();
        canvas.renderMode = RenderMode.WorldSpace;
        canvas.worldCamera = Camera.main;
        canvasObject.AddComponent<CanvasScaler>();
        canvasObject.AddComponent<GraphicRaycaster>();

        // Add EventSystem for interaction, if it doesn't already exist.
        if (GameObject.FindObjectOfType<EventSystem>().IsUnityNull())
            canvasObject.AddComponent<EventSystem>();
        canvasObject.AddComponent<StandaloneInputModule>();

        // Create a Panel which will act as the parent for all text components.
        panel = new GameObject("Panel").AddComponent<RectTransform>();
        panel.transform.SetParent(canvasObject.transform);
        panel.AddComponent<CanvasRenderer>();

        // Initialize the list for text components.
        textList = new List<TMP_Text>();

        // DELETE - Test text spawning (this can be removed once testing is complete).
        StartCoroutine(TextSpawner(20));
    }

    // DELETE - Test method to spawn random texts (intended for visualization and can be deleted after testing).
    private IEnumerator TextSpawner(int spawnAmount)
    {
        int counter = 0;
        List<string> texts = new List<string>()
        {
            "Hi there!",
            "I am cool!",
            "Weeeeeeeeee!",
            "I love AI",
            "Blah Blah",
        };

        while (counter < spawnAmount)
        {
            yield return new WaitForSeconds(1f);

            // Generate random positions, rotations, and pick a random text.
            int randomInt = UnityEngine.Random.Range(0, texts.Count);
            float posX = UnityEngine.Random.Range(-10f, 20);
            float posY = UnityEngine.Random.Range(-10f, 20);
            float posZ = UnityEngine.Random.Range(1f, 60f);
            float rotX = UnityEngine.Random.Range(-90f, 90f);
            float rotY = UnityEngine.Random.Range(-90f, 90f);
            float rotZ = UnityEngine.Random.Range(-90f, 90f);
            Vector3 position = new Vector3(posX, posY, posZ);
            Vector3 rotation = new Vector3(rotX, rotY, rotZ);

            // Add the text to the UI.
            AddText(texts[randomInt], $"id{counter}", 64, position, rotation);

            counter++;
        }

        Debug.Log($"All {textList.Count} texts are on screen.");

        while(counter > 0)
        {
            yield return new WaitForSeconds(1f);
            counter--;
            RemoveText($"id{counter}");

        }

        Debug.Log($"All texts have been removed from the screen.");
    }

    // Method to add a text to the UI with specified attributes.
    public void AddText(string text, string textid, float fontSize, Vector3 position, Vector3 rotation)
    {
        // Create a new GameObject for the text and set its attributes.
        GameObject newGameObject = new GameObject(textid);
        RectTransform rect = newGameObject.AddComponent<RectTransform>();
        rect.position = position;
        rect.Rotate(rotation);
        newGameObject.transform.SetParent(panel);
        TextMeshPro newText = newGameObject.AddComponent<TextMeshPro>();

        // Set specific TextMeshPro settings, extend this as you see fit.
        newText.enableWordWrapping = false;
        newText.text = text;
        newText.fontSize = fontSize;

        // Add the new text to our list.
        textList.Add(newText);
    }

    // Method to remove the text from the UI based on its ID (name).
    public void RemoveText(string textid)
    {
        TMP_Text textFound = null;
        foreach (TMP_Text text in textList)
        {
            if (text.gameObject.name.Equals(textid))
            {
                textFound = text;
                break;
            }
                
        }
        
        // Check if we found the text and if so lets remove it from the list and destroy it.
        if(textFound != null)
        {
            textList.Remove(textFound);
            Destroy(textFound.gameObject);
        }
        else
        {
            Debug.Log($"Could not find textid: {textid}");
        }
    }
}

Sorry for the late reply. I think if you turn the label into a prefab and also attach a text component you can instatiate it on start and then get the text component and pass the text string. You’ll still need a canvas though or it won’t render. I think this is probably the most easiest way.

Is there any reason why you are not using TextMeshPro? . I ask because it is a little easier to work with in world space. You can set the canvas to Render Mode World Space and move it around the scene like any other gameobject.

I will share some ideas with you in a bit, not give up yet!

Hello RedRoad,

It was a very nice gesture to write all this code, but you have added more complexity. I am looking for the absolute minimum code required to get the result that I need. I am fully able to build all the capabilities around this basic need, and I have a specific way that I will be doing that.

If you or anyone can answer my simple question with a simple answer without any extraneous code I will become fully convinced that it is an easy matter to display a text on a canvas in world-space with a full transform that is consistent with a normal gameObject. But as it stands I remain unconvinced.

Thanks,
-j

Hi RedRoad,

to give a bit of context, please see the attached screenshot. I am setting it up so I can place dynamic numerical labels on the edges of a heightfield. I will also be adding labels on the peaks of the heightfield. It’s a scientific application that uses VR. The screenshot shows 10 flat gameObjects that I am procedurally generating. The user will be able to adjust the number of labels, the numerical min/max, etc., so it is very dynamic.

I’ve created a class called “Labels” which allows me to create new labels on the fly with specified text strings and a full transform. Once I have the required code to render texts I will replace these gameObjects (or make them invisible?) and then add the canvas, textMesh, etc. All the Unity-specific code that handles text display will be encapsulated within the class.

Hope that gives you a bit of context. Thanks again!

Hello RedRoad!

I finally got around to implementing your code, and it worked from the get-go! Thank you!

I was initially hesitant because I was looking for a simple bare-bones solution, but indeed it’s not easy to whittle this kind of thing down. Your method for creating random texts and then removing them is actually quite helpful. And I am now starting to carve it up to my own purposes.

I would recommend this example script to other lost souls looking for a solution. All I did was add this script to my project and…the texts started appearing.

Thanks!
-j

Hi RedRoad,

Okay - I’m having the same problem as before:

You set the font size to 64. I need it to be very small (like 0.05f). But when I make the font smaller, the text goes far left of the position I set it to (0.0f, 1.4f, 0.1f).

This is such an annoying behavior. Do you know how to get around this problem?

Thanks!
-j

My plan is to find this solution and then whittle the code down to only the essential bits to make a text string appear in the world. I will then share it so that others can use it. Why is this so hard? Why can’t anyone provide a C# example that does this? The example provided by RedRoad was very helpful, but it has the same problem as I had before - where the world position is totally wrong (see prev. notes)

The Unity manual: Unity - Manual: Text Mesh says it can be used for “rendering road signs…” etc. Great. How? RedRoad’s code almost does this. I hope it’s just a mater of adding the right tweaks to fix this problem.

I’m very motivated to share the solution with others. No one should have to go through such rabbitholio to put a sign into the world.

I’ll take any punishment laid upon me for not being better at searching. My goal is to get a pure c# solution and then to share it.

Thanks
-j