New invention of writing hide-able text to 3D cubes.

Hello,
Where should I go to share some code I wrote to make it possible to write hide-able text to 3D cubes through a simple attachment of a C sharp script placed on the cube? What the user can do is attach this script to any cube, and since there is no “Font.texture” property (unfortunately), you have to put the texture on a separate object and assign that object to textObj.

direction can either be “Front”, “Right”, “Back”, “Left”, “Up” or “Down” (to tell what side of the cube to put the text on). offset tells how far off the cube (away from the center) to put the text and should be a small positive number. A negative value places the text inside the cube (toward the center). message tells what message to put on the cube. fontName is where in the Resource folder your downloaded font name is. shaderName is where in the Resource folder your shader name is. matName is where in the Resource folder your font material’s name is. Do not use extensions in these 3 variables. characterSize and fontSize controls the numbers in the TextMesh of a newly created object called object_name+“_sheet”. The newly created object makes itself a child object under the cube with the script attached to it. The ‘sheet’ object is what contains the text (written on the cube). MaxWidth (by default) should always be 1, (which is 1 times the size of the cube) in THIS circumstance, so that it does perfect word wrapping within the cube…

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Writing : MonoBehaviour {

    public GameObject textObj;

    public string direction;
    public float offset = 0.001f;

    public string message;
    public string fontName = "Fonts/magnificent/Magnificent";
    public string shaderName = "Shaders/TextOneSided";
    public string matName = "Materials/Font Materials/FontMaterial";

    public float characterSize = 0.01f;
    public int fontSize = 1;

    public Color color;

    private GameObject sheetObj;

    #region public members.
    public float m_maxWidth = 1.0f;
    public bool m_disableWhilePlaying = true;
    #endregion

    #region private members.
    private TextMesh m_textMesh = null;
    private float m_lastMaxWidth = 0.0f;
    private string m_wrappedText = "";
    #endregion

    #region getters/setters.
    public float TextWidth
    {
        get {
            if (m_textMesh != null)
            {
                return m_textMesh.GetComponent<Renderer>().bounds.extents.x * 2.0f;
            } else
            {
                return -1;
            }
        }
    }
    public float TextHeight
    {
        get {
            if (m_textMesh != null)
            {
                return m_textMesh.GetComponent<Renderer>().bounds.extents.y * 2.0f;
            } else
            {
                return -1;
            }
        }
    }
    public float MaxWidth
    {
        get { return m_maxWidth * 2; }
    }
    #endregion

    #region monobehavior methods.
    public void Awake()
    {
        if (m_textMesh == null && sheetObj != null)
        {
            m_textMesh = sheetObj.GetComponent<TextMesh>();
        }
        //bool shouldDisable = m_disableWhilePlaying && !(Application.isEditor && !Application.isPlaying);
        //this.enabled = !shouldDisable;
    }

    public void OnDrawGizmos()
    {
        Gizmos.color = Color.blue;
        Gizmos.DrawRay(GetInitPosition() + transform.up * 0.05f, transform.right * TextWidth);
        Gizmos.color = Color.red;
        Gizmos.DrawRay(GetInitPosition(), transform.right * MaxWidth);
    }

    #endregion

    #region private methods.
    private float GetSpaceWidth()
    {
        float totalWidth = -1;
        if (m_textMesh != null)
        {
            string textTemp = m_textMesh.text;
            m_textMesh.text = "x x";
            totalWidth = m_textMesh.GetComponent<Renderer>().bounds.extents.x * 2.0f;
            totalWidth -= GetWordWidth("xx");
            m_textMesh.text = textTemp;
        }
        return totalWidth;
    }

    private float GetWordWidth(string word)
    {
        float totalWidth = -1;
        if (m_textMesh != null)
        {
            string textTemp = m_textMesh.text;
            m_textMesh.text = word;
            totalWidth = m_textMesh.GetComponent<Renderer>().bounds.extents.x * 2.0f;
            m_textMesh.text = textTemp;
        }
        return totalWidth;
    }

    private Vector3 GetInitPosition()
    {
        if (m_textMesh == null)
        {
            return new Vector3(-1, -1, -1);
        }

        // Find X.
        float x = 0.0f;
        switch (m_textMesh.anchor)
        {
            case TextAnchor.LowerLeft:
            case TextAnchor.MiddleLeft:
            case TextAnchor.UpperLeft:
                x = transform.position.x;
                break;
            case TextAnchor.LowerRight:
            case TextAnchor.MiddleRight:
            case TextAnchor.UpperRight:
                x = transform.position.x - TextWidth;
                break;
            case TextAnchor.LowerCenter:
            case TextAnchor.MiddleCenter:
            case TextAnchor.UpperCenter:
                x = transform.position.x - TextWidth / 2.0f;
                break;
        }

        // Find Y.
        float y = 0.0f;
        switch (m_textMesh.anchor)
        {
            case TextAnchor.LowerLeft:
            case TextAnchor.LowerRight:
            case TextAnchor.LowerCenter:
                y = transform.position.y - TextHeight / 2.0f;
                break;
            case TextAnchor.MiddleRight:
            case TextAnchor.MiddleLeft:
            case TextAnchor.MiddleCenter:
                y = transform.position.y + TextHeight / 2.0f;
                break;
            case TextAnchor.UpperLeft:
            case TextAnchor.UpperRight:
            case TextAnchor.UpperCenter:
                y = transform.position.y;
                break;
        }

        GameObject g = new GameObject();
        g.transform.position = new Vector3(x, y, transform.position.z);
        g.transform.eulerAngles = transform.eulerAngles;
        g.transform.Translate(transform.localScale.x * -0.5f, 0, transform.localScale.z * 0.5f);

        Vector3 pos = g.transform.position;

        Destroy(g);

        return pos;
    }
    #endregion

    // Update is called once per frame
    void Update () {

        Vector3 rotation = transform.eulerAngles;

        transform.eulerAngles = new Vector3(0, 0, 0);

        if (m_textMesh == null && sheetObj != null)
        {
            m_textMesh = sheetObj.GetComponent<TextMesh>();
        }

        GameObject obj = null;

        int cnt = 0;

        foreach (Transform child in transform)
        {
            cnt++;
        }

        if (cnt == 0)
        {
            obj = new GameObject();
            obj.transform.parent = transform;

            obj.name = obj.transform.parent.name + "_writing";

            obj.AddComponent<MeshRenderer>();

            TextMesh objMesh = obj.AddComponent<TextMesh>();

            sheetObj = obj;
        }

        if (cnt != 0 && sheetObj == null)
        {
            sheetObj = transform.Find(name+"_writing").gameObject;
        }

        if (direction == "Front")
        {
            if (obj != null)
            {
                obj.transform.localPosition = new Vector3(0, 0, 0.5f + (offset * (1.0f / transform.localScale.y)));
            }
        }
        if (direction == "Right")
        {
            if (obj != null)
            {
                obj.transform.localPosition = new Vector3(-0.5f - (offset * (1.0f / transform.localScale.y)), 0, 0);
            }
        }
        if (direction == "Back")
        {
            if (obj != null)
            {
                obj.transform.localPosition = new Vector3(0, 0, -0.5f - (offset * (1.0f / transform.localScale.y)));
            }
        }
        if (direction == "Left")
        {
            if (obj != null)
            {
                obj.transform.localPosition = new Vector3(0.5f + (offset * (1.0f / transform.localScale.y)), 0, 0);
            }
        }
        if (direction == "Up")
        {
            if (obj != null)
            {
                obj.transform.localPosition = new Vector3(0, 0.5f + (offset * (1.0f / transform.localScale.y)), 0);
            }
        }
        if (direction == "Down")
        {
            if (obj != null)
            {
                obj.transform.localPosition = new Vector3(0, -0.5f - (offset * (1.0f / transform.localScale.y)), 0);
            }
        }

        if (obj != null) {
     
            obj.transform.LookAt(transform.position);

            if (direction == "Front")
            {
                obj.transform.localPosition = new Vector3(0.5f, 0.5f, obj.transform.localPosition.z);
            }

            if (direction == "Right")
            {
                obj.transform.localPosition = new Vector3(obj.transform.localPosition.x, 0.5f, 0.5f);
            }

            if (direction == "Back")
            {
                obj.transform.localPosition = new Vector3(-0.5f, 0.5f, obj.transform.localPosition.z);
            }

            if (direction == "Left")
            {
                obj.transform.localPosition = new Vector3(obj.transform.localPosition.x, 0.5f, -0.5f);
            }

            if (direction == "Up")
            {
                obj.transform.localPosition = new Vector3(-0.5f, obj.transform.localPosition.y, 0.5f);
            }

            if (direction == "Down")
            {
                obj.transform.localPosition = new Vector3(-0.5f, obj.transform.localPosition.y, -0.5f);
            }

            obj.transform.localScale = new Vector3(1, 1, 0.0001f);

            //obj.transform.Translate(-new Vector3(0.5f, -0.5f, 0));

            Material mat = Resources.Load(matName) as Material;

            mat.SetColor("_Color", color);

            mat.shader = Resources.Load(shaderName) as Shader;

            Font font = Resources.Load(fontName) as Font;

            mat.SetTexture("_MainTex", textObj.GetComponent<MeshRenderer>().material.GetTexture("_MainTex"));

            obj.GetComponent<TextMesh>().font = font;

            obj.GetComponent<TextMesh>().text = message;

            obj.GetComponent<TextMesh>().color = color;

            obj.GetComponent<TextMesh>().characterSize = characterSize;
            obj.GetComponent<TextMesh>().fontSize = fontSize;

            obj.GetComponent<MeshRenderer>().material = mat;

        }

        if (sheetObj != null)
        {
            sheetObj.GetComponent<TextMesh>().characterSize = characterSize;
            sheetObj.GetComponent<TextMesh>().fontSize = fontSize;
            sheetObj.GetComponent<TextMesh>().text = message;
        }

        if (m_textMesh != null && (m_textMesh.text != m_wrappedText || MaxWidth != m_lastMaxWidth))
        {
            float widthSum = 0.0f;
            m_wrappedText = "";
            string line = "";

            char[] separators = new char[] { ' ', '\n', '\t' };
            string[] words = message.Split(separators);

            for (int i = 0; i < words.Length; i++)
            {
                string word = words[i].Trim();

                if (i == 0)
                {
                    line = word;
                    widthSum = GetWordWidth(word);
                }
                else
                {
                    // Concatenate the new word if there is still enough space.
                    if (widthSum + GetWordWidth(word) <= MaxWidth)
                    {
                        if (widthSum > 0.0f)
                        {
                            line += ' ';
                            widthSum += GetSpaceWidth();
                        }

                        line += word;
                        widthSum += GetWordWidth(word);
                    }
                    // New line.
                    else
                    {
                        if (m_wrappedText.Length > 0)
                            m_wrappedText += '\n';

                        m_wrappedText += line;

                        line = word;
                        widthSum = GetWordWidth(word);
                    }
                }
            }

            if (widthSum > 0.0f)
            {
                if (m_wrappedText.Length > 0)
                    m_wrappedText += '\n';

                m_wrappedText += line;
            }

            m_textMesh.text = m_wrappedText;
            m_lastMaxWidth = MaxWidth;
        }

        transform.eulerAngles = rotation;

    }
}

As I commented on the other thread where this was posted, there is absolutely no reason in 2017 to be writing new code that uses TextMesh. This script could be about 20 lines long if it used a Worldspace Canvas to place the text on the cube, and it’d look better and be easy to use. That almost 400 lines of code in this script are filled with workarounds and caveats of TextMesh and serves as a pretty good example of why TextMesh shouldn’t be used in the first place.

There are a number of other issues relating to bad coding practices. It doesn’t seem like you’ve posted this looking for constructive criticism, but I’ve got a bunch of it to offer that will drastically improve your code for the future, if you’d like to hear it.

Ok.

I’ll start with the stuff you wouldn’t need if you’d used a worldspace Canvas + UI.Text component, instead of TextMesh. The Text component handles word wrapping on its own, so your GetWordWidth, GetSpaceWidth, etc become redundant - just set the text to the desired text and it handles the rest. It also handles all the material, shader, etc on its own.

Those algorithms, by the way, are really inefficient - you’re changing the text many times per frame just to calculate word widths. That means it has to generate the mesh over and over - multiple times for every single word. If you really wanted to do something like that, you should calculate each letter at the start, store those calculations, and then calculate the word width by adding the letters together that make up the word. Again, UI.Text does this anyway so that’s not necessary, but it’d be a big improvement in speed.

UI.Text uses a Vector2 for its anchor point, so lines 124-161 would be unnecessary. In fact, you wouldn’t have to set its position based on the anchor point at all, because UI.Text uses a RectTransform: if you size the RectTransform to match the side of the cube, the text will always be set to fill that rectangle. So the entire GetInitPosition function unnecessary.

So far, lines 88 - 175 are rendered obsolete.

Now let’s talk about Update().

Your script does a lot of work each frame. I see that it does check to see if the text has changed before doing that part of the work, which is good, but it still does a lot more work than it should be doing on a frame-by-frame basis. The majority of that stuff is initialization, and should go in Start().

direction should NOT be a string. The script expects the dev to type out that string, perfectly, including capitalization? Yikes. If you use an enum, it’ll show up as a dropdown in the inspector:

public enum Direction {Forward, Left, Up, Down, Back, Right};
public Direction direction = Direction.Forward;

void Update() {
if (direction == Direction.Forward) {
//etc

You’ve got two redundant, and very unnecessarily long sections of code, from 217-258, and then 260-292. My best guess is that you have these two sections there so that you can use transform.LookAt and set the rotation, is that correct? If so, you should definitely check out the LookRotation function. (I see that you also use Euler angles in a few places - though I honestly can’t see what it’s being used for - but, don’t use Euler angles.)

Also, on the first one of these, you have an “if (obj !=null)” for every single one of those - since that’s the same, it should be on the outside of those if statements, the way it is on the second block. (but then, we should eliminate that first block anyway, so don’t worry too much about that)

From 308-316, you repeatedly call GetComponent, which is really wasteful. You should call that once, and store the result. What’s crazy is that you do call it once and store the result (m_textMesh), but then ignore that stored value and just do the hard work every time.