C#: Set width of 9-sliced sprite to width of TMP child object

Hi Unity community,

I’m facing a problem that I really don’t know how to solve. I have a square button sprite (512x512) which I 9-sliced, so the rounded corners are being preserved when scaling the sprite.
This sprite I turned into a game object and added another game object as its child with a “TextMeshPro - Text” component attached to it. I then have a C# script sitting on my sprite, that has should have the following logic:

set text of child object’s TMP component
grab new bounds of TMP
set sprite size to TMP bounds with an added size offset since the values the TMP bounds output seem way too small

By setting the size that way, I kind of get the button to encompass the text, but it’s a lot of guess work. If my text is very short, I don’t have a problem, but if the text becomes a very long word or sentence, then the size offset in the X-axis I’m applying is insufficient. I could LERP the value between a minimum and maximum offset, but again, it’s guess work, I’d like my logic to be more precise.

The other problem that I’m facing is that the bounds I’m receiving from the TextMeshPro component seem to be incorrect, much smaller than the actual text. In my scene I have a game object “whitepixel” which is a 1x1 pixel, that I scaled a bit in order to see it in my scene. In my C# script I’m setting this object’s position to the minimum position in X of the TMP bounds, and as you can see in my screenshot, the position is always inaccurate.

Would love to learn more about this and would really appreciate some pointers.
I attached a few screenshots, the png of my button, as well as my code.

By the way, I tried the “UI” approach by using a standard button with a TextMeshPro UI child, in combination with a “Horizontal Layout Group” and a “Content Size Fitter”, but that doesn’t give me an option to specify a minimum width on the button.

Thanks a lot!

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

//By default, MonoBehaviours are only executed in Play Mode. By adding this attribute, any instance of the MonoBehaviour
//will have its callback functions executed while the Editor is in Edit Mode too.
[ExecuteInEditMode]
public class ResizeTest2 : MonoBehaviour
{
    public GameObject line;
    public float sizeOffsetX;
    public float sizeOffsetY;
    public float sizeMinX;
    public float sizeMinY;
    public string text;
    private TMP_Text tmp;
    private SpriteRenderer sprRend;
    private GameObject childObj;
  

    // Start is called before the first frame update
    void Start()
    {
        Debug.Log("Start");
        childObj = transform.GetChild(0).gameObject;
        tmp = childObj.GetComponent<TextMeshPro>();

        tmp.SetText(text);
        tmp.ForceMeshUpdate();
        sprRend = gameObject.GetComponent<SpriteRenderer>();
        Bounds bounds = tmp.bounds;
        Debug.Log("Size" + bounds.size.x.ToString());
        float newSizeX = Mathf.Max(sizeMinX, bounds.size.x + sizeOffsetX);
        float newSizeY = Mathf.Max(sizeMinY, bounds.size.y + sizeOffsetY
                );
        Vector2 sprSize = new Vector2(newSizeX, newSizeY);
        sprRend.size = sprSize;
        line.transform.position = new Vector3(bounds.min.x, 0, 0);
    }

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




I would do this more by clever anchoring. It took me forever to nail this down so I keep a copy of it handy from years ago when I first cracked the seekrit code. See enclosed scene.

The important bits were in the layout group (vertical in my case) and the content size fitter.

This bubble is set to expand vertically but I imagine the same thing could be effected horizontally.

EDIT: I sorted out horizontal bubble… it was pretty straightforward. The package below has been updated to have both cartoon bubble and the single line of text bubble like you did. There’s padding in the horizontal layout thingy to make it center up the text pleasantly.

My Magic Unity Toolkit has expanded!

7098478–845848–ExpandingCartoonBubble.unitypackage (15.1 KB)

Hi Kurt-Dekker,

Thanks for your reply!
Layout Groups and Content Size Fitters were the first thing I looked at. It seems like a viable option (I cracked open your scene and it makes perfect sense), however when I reduce my word to a single letter, then I need to preserve a minimum width for my button (it needs to be square in that case), rather than shrinking down in width too much.
I attached to screenshots of my layout settings, as well as the outcome with the single letter.
Which begs the question, is it possible somehow to keep the sprite at a minimum width?

For anybody else reading this, I’m trying to learn a lot more about TextMeshPro, so I would love to learn why the bounds do not encompass the whole text, as described in my initial post.

Thanks!!!


7098505--845857--2021-05-01_letter.PNG

Cheap and cheerful would be to pad it with spaces. :slight_smile:

Maybe you can hack the ContentSizeFitter (source is in the UI package) to make it always have a minimum?

Maybe you can put the text and a “min width graphic” in as children under another new object with its own content size fitter, eg deepen the hierarchy by one??

At one point I hacked the AspectRatioFitter into an AspectRatioEnforcer, so that it wouldn’t hold a specific aspect ratio but rather constrain it to (say) no narrower than square. I don’t think I have that code anymore as I did it for a client, but it was pretty straightforward, and I just started from the AspectRatioFitter. But that might conflict with the Horizontal Layout, not sure.

Hey,

You’ve been a huge help today, I appreciate that a lot! I don’t think I’m at that level yet where I want to start hacking Unity modules, however I was able to attach a C# script to my button, and adjust the padding on the Horizontal Layout Group, depending if my TextMeshPro string is a single letter or multiple letters.
I tried your first suggestion with padding the letter with spaces, however only leading spaces are being taken into consideration, trailing spaces are being ignored. So that did not work for me, however I now have a solution that somewhat works.
Edit:
After testing this for a while, it seems that the width of my single letter buttons is still inconsistent, since every letter has a slightly different width. Ideally each single letter button would have precisely the same width.

Cheers!
P.s. Still interested in the whole TextMeshPro bounds issue, if anybody has some pointers, I’m eager to learn :slight_smile:

Could hack your font so that it is a monospaced font. Or maybe tweak TMPRo to support that on a non-monospaced font… or see if someone else has. Heck, there might even be a tickbox in the TMPro font utility.

Also you could always put the visible image on another peer GameObject than the layout one, then have a script that observes the layout one (which would be on an otherwise blank object, no image this time), and copies its size over to the real graphic… as long as it is wide enough.

Hi Kurt,

Thanks again for your help.
I investigated the monospaced option, turns out you can use rich text tags with TextMeshPro, namely the <mspace=x.xx> tag. That does however not solve the problem of having discrepancy in width unfortunately, although really good to know about this. You can check out the screenshot I attached.
More info about the tag can be found here:

As for your second option, I’m not exactly sure about the steps I have to take in order to make this work. My goal is to create a prefab that I can instantiate n numbers of time in my scene, without being reliant on other game objects.
Do you think this could work with your idea?

Cheers,
Manu

Personally I don’t like prefabs for UI because these prefabs are very intimately tied to whatever canvas they end up in. If you change your game canvas, bam, all your bubbles break or change.

Instead I would make a separate additively-loaded scene with its own Canvas in it, and one of those example text boxes.

That way the code would go in there to produce and position these things, and that would let you layer it properly.

You could make the canvas into a prefab I suppose (instead of a separate scene scene), but it would be just the canvas and the blank template, and contain code to make copies as needed.

You probably don’t want anything in that canvas blocking touches so make sure everything is not raycast target marked.

Hi Kurt,

Prefabbing the canvas is most likely the solution that I’m going for.
I finally found the solution to this problem, which is actually very easy.
Just attach a “Layout Element” to the child object, set the “Min Width” parameter to the desired value. Done!
Attached screenshots with the Horizontal Layout Group, Content Size Fitter and Layout Element settings for whoever runs into this question in the future!

Thanks for helping me debugging, that was very kind of you!

Cheers,
Manu


2 Likes