Unity Troubleshooting II

Some more Unity 3D tips that I need to write down while I remember them. (You might be interested in the previous ones.)

TL;DR: Don’t forget to use Time.deltaTime for per-second change. Content Size Fitters update late, so read their properties at end of frame.

It Changes Too Quickly

When you’re pondering the deepest mysteries of your game, it’s easy to make the most obvious mistakes. Right? (Or is that just me?)

Suppose you want to change a quantity at a particular rate, in units per second. (Many of you are ahead of me now that I’ve been explicit about the units. Just make sure you’re thinking that way when you implement your rates.)

Your code might look like this:

void Update() {
    quantity += rate;
}

You should probably clamp quantity, and maybe wrap it in a condition (e.g. I suspect, but don’t know, that it’s faster to check whether quantity < cap, rather than going ahead and increasing against the clamp). But the general principle is there.

Then you find that quantity changes much¬†faster than you intended. That’s because you intended this:

void Update() {
    quantity += rate * Time.deltaTime;
}

Time.deltaTime is the number of seconds since the last frame. You may find it counter-intuitive to multiply something that is too large, but unless you have serious performance issues, Time.deltaTime will be small (in the region of 0.02, perhaps).

With this change you’ve gone from increasing quantity at rate-per-frame to increasing it at rate-per-second, not only making it a more manageable quantity, but also making it consistent (since frames aren’t of constant duration).

Content Size Fitter vs. Reading RectTransforms

I steered clear of the 4.6 Unity UI for some time, haunted by my experiences in different versions. I even chose HTML5 for one prototype, because I assumed that outputting HTML would be a nice short-cut for testing a UI-heavy concept.

Never again.

The layout support, including layout groups and content size fitters, does neat things, and generally the fact that it doesn’t show you how isn’t a problem. But one day I wanted to position tooltips properly; making sure they were entirely within the screen, that sort of frivolity.

I started out like this:

T InitTooltip<T>(T prefab) where T : MonoBehaviour {
    T tt = Instantiate(prefab);
    tt.transform.position = initialPosition;
    // ... Check bounds and reposition if necessary ...
    return tt;
}

void Show(signature specifies tooltip type) {
    SpecificTooltip stt = InitTooltip(specificPrefab);
    stt.Init(stuff); // Populate
    canvasGroup.alpha = 1;
}

Aside 1: It feels as though there should be better way of showing/hiding UI than canvasGroup.alpha, but until you can disable CanvasRenderer I don’t know what it is. You can set the GameObject inactive, but make sure you store a reference to it first, because finding inactive objects can be a pain.

Asside 2: You might guess at this stage that I like generics. You’ve be so very right.

It doesn’t work. I logged several properties of the RectTransform and generally found them to be empty, as though the rect was of zero size. And of course, it was, because the prefab tooltip is empty (with zero size) and specificTooltip.Init(stuff) is what gives it its content.

So I moved the repositioning into a new method, to be called after the tooltip content has been initialised. This was a pain, because I’d hoped for the individual Show methods (of which I have several) to minimise both code reuse and knowledge of the process of building tooltips. In order to get it to happen after Init(), each Show() had to include and explicit call to Reposition().

But even that didn’t work. I got annoyed, sighed a lot and probably annoyed my girlfriend (since by this stage I’ve worked well into the evening). This morning I had a brainwave: what if content size fitter needed¬†longer, and still wasn’t ready yet?

So I put the reposition in a coroutine, and now the solution looks like this:

T InitTooltip<T>(T prefab) {
    T tt = Instantiate(prefab);
    tt.transform.position = initialPosition;
    StartCoroutine(FinalisePosition());
    return tt;
}

IEnumerator FinalisePosition() {
    yield return new WaitForEndOfFrame();
    // ... Test bounds and reposition ...
    canvasGroup.alpha = 1;
}

void Show(signature specifies tooltip type) {
    SpecificTooltip stt = InitTooltip(specificPrefab);
    stt.Init(stuff); // Populate
}

If you haven’t met Coroutines before, read the manual page; they’re pretty handy. This one stops immediately, to be resumed at the end of the frame, which it turns out is long enough for the content size fitter to have decided how big it is (which I guess is done in LateUpdate(), or a dedicated GUI loop).

There’s a gotcha here, in that the tooltip now has time to appear in the wrong position before the deferred positioning happens. You’ll notice that I’ve left setting the canvasGroup.alpha until after the reposition, so nobody sees it in the old position.

It’s also tidied up my code, since InitTooltip() can invoke the coroutine, to happen after the specific Init(), without the Show() methods knowing anything about it.

Epilogue

I daresay I’ll make more Unity mistakes for you to avoid, soon. Perhaps I’ll have put in a proper code format plugin by then.