Sprite Numbers

In the new edition of Glace you’ll collect lots of goodies as you cruise about. The original game had blue gems (ding). The Made Again edition has those too, but also more stuff. I got to working on this part recently, and I call it the treasure component. Let’s talk about showing numbers in the UI.

sprite-number-1.gif

In the last post I talked on object pooling and why it’s neat. It limits the number of objects you create as the player derps around the level. This can lead to smoother play for the humans. Well, there’s sometimes a similar story around updating the text in your game’s UI.

You found a gem! There’s another one! And another! Great! But that’s not all you picked up - you’ve also got three new objects on the managed heap. Say what? I thought we were pooling our gems. What’s up with the three new guys?

Alas, these objects came up when we updated the UI to show the latest number of gems. Every time we use the player’s gem count (an integer) to set the UI Text component, a new string object is created. If you pick up 100 gems, that’s 100 string objects on the managed heap (because you updated your UI 100 times).

Is this a big deal? I don’t know. I guess it depends. These short strings are pretty small in memory at around 25 bytes each. You’re probably not going to run out of memory from these little guys. Instead, I think any issues are more likely to involve the number of objects you’re creating instead of their size.

Remember those Friday nights spent watching the disk defragmenter as it transformed your scattered hard drive into a tidy stack of princely goodness? Well Unity doesn’t do the tidying thing with its managed heap. If you’ve got a bunch of small objects on the heap, it can make it hard to find a spot for the bigger guys, which can slow things down.

Another bit is that Unity’s garbage collector is non-generational, which means it scans the entire heap every time it wants to reclaim resources. Each object on the heap takes time to scan, even if it’s super small. Generational GC’s can be smarter on which objects are eligible for collection.

But again, does this really matter? Is your game gonna have any noticeable problems if you put 500,000 strings on the heap? You’ll have to profile your game to really know. But if I’m honest with you, I pretty much just WANT it to matter. And just because something ain’t broke doesn’t mean you can’t take it apart and fix it. I just don’t like knowing I’m putting stuff on the heap when it’s not totally necessary. And in this case it’s not totally necessary.

Here’s the plan, see?

We need to toss the idea of using a Text component to show gem count in your UI. Actually just forget any method that needs a string. If you need a string to update it, then you have to create that string, and that’s what we need to avoid. Side note: there are multiple ways of doing this kind of thing that you can find by searching around. The following is just the method I like the most.

Right so no Text component. Instead you’ll just use game objects to show numbers in the UI. One game object per digit to be more specific. These objects can represent any digit 0 to 9. We can write a simple component to handle this for us.

public class SpriteDigit : MonoBehaviour
{
    public SpriteDigitSpriteSource SpriteDigitSpriteSource;
    public RectTransform RectTransform;
    public Image Image;

    public void Set(int digit)
    {
        var sprite = SpriteDigitSpriteSource.Get(digit);            
        Image.overrideSprite = sprite;
        RectTransform.sizeDelta = new Vector2(sprite.rect.width, sprite.rect.height);
    }
}
SmallSpriteDigit.PNG

The SpriteDigit component has a reference to a RectTransform and Image component that exist on the same game object. Also, since this is a UI thing, the game object lives within the hierarchy of a Canvas. The SpriteDigitSpriteSource is a scriptable object that provides the sprites for each digit. It looks like this.

[CreateAssetMenu(menuName = "UI/Common/Sprite Digit Sprite Source")]
public class SpriteDigitSpriteSource : ScriptableObject
{
    public Sprite[] Sprites = new Sprite[10];

    public Sprite Get(int index)
    {
        return Sprites[index];
    }
}

We instantiate one of these scriptable objects as an asset in our project and set it up with sprites for each digit. Then connect the asset to the Sprite Digit Sprite Source field on the SpriteDigit component.

SmallDigitSpriteSource.png

So now we’ve got a game object that can represent any digit 0 to 9. We can call Set(…) on the SpriteDigit component and it’ll show that digit. Again this only shows 0 to 9. It can’t show 10, or 10,000. For this to be useful we need more. Side note: you might want to sanitize the input on the Set(…) call by clamping it or modding by 10 or something. Otherwise bad stuff happens if you try to set a digit to 10.

To show the a bigger number we’ll need more of these SpriteDigit objects and we’ll need them to work together. For that you you’ll need another component to orchestrate them. For this we’ve got SpriteNumber.

public class SpriteNumber : MonoBehaviour
{
    public SpriteDigit[] SpriteDigits;

    public void SetNumber(int number)
    {
        int workingNumber = number;
        int i = 0;
        
        while (i < SpriteDigits.Length && workingNumber > 0)
        {
            int power = (int)Mathf.Pow(10, i);
            int mod = (int)Mathf.Pow(10, i + 1);
            int digit = (workingNumber % mod) / power;
            SpriteDigits[i].Set(digit);

            workingNumber -= digit * power;
            i++;
        }

        for (; i < SpriteDigits.Length; i++)
        {
            SpriteDigits[i].Set(0);
        }
    }
}

SpriteNumber has an ordered array of SpriteDigit objects and sets each of them so together they show the given number. You can put this component on a game object that parents the SpriteDigit game objects. Then you can add a horizontal layout group to align the children.

SpriteNumberHierarchy.PNG
SpriteNumberScene.PNG
SpriteNumberInspector.PNG

This is just one way to organize this kind of thing. Another option is to instantiate the SpriteDigits from a prefab as you need them. That’d be the job of the SpriteNumber component if you wanted to go that way. Also, in this example we set any unused digits to zero. So if you set the SpriteNumber to 25, it’d set up the digits to read 0025. You could just as easily hide the unused digits (just don’t hide the first one or the number 0 won’t show). That way the first two digits wouldn’t show and it’d just read 25.

That’s it. Now we can update our gem count without putting stuff on the heap. This method also opens up some cool per-digit animation possibilities. The future is bright.

Let me know if I messed something up or if you’re interested in sharing another strategy around this. Thanks for reading!