loading...

Unity's Monobehaviours by Example

oldmancharles profile image Carlos Eduardo Pérez Villanueva ・5 min read

Hello users of DEV!

Last week, I wrote my first post to this platform, called C# Collections by Example, where I shared some small tips on using methods such as First(), and its difference with FirstOrDefault(), and adding real examples of how I use them on apps I build on my job.

Today I want to talk about another topic, and share real examples from games I've worked on using the Unity Game Engine. Although I work as a programmer at an hospital here in Nicaragua, I spend most of my free evenings playing or developing videogames. I have plenty of experience using Unity, I even got to publish a game for iOS and Android; and some on itch.io! So here I'll share some useful tips regarding monobehaviours that you might find useful when developing games!

Let's Start() with some coroutines!

Coroutines are pretty useful in Unity to execute code over time, or delays. Now, it's a standard practice to declare coroutines as a separate function, to finally execute it like StartCoroutine(MyCoroutine());

This is fine and it's simply the best approach to it. I don't like to write much code to be honest. I like to use inline functions whenever I can, because long, vertical walls of text make me feel uncomfortable. Sometimes, I used GameObjects that called a Coroutine as their only function, and they did it on spawn.

It looked more or less like this:

[SerializeField]
private float timeToWait;

private void Start() 
{
    StartCoroutine(Explode);
}

private IEnumerator Explode() 
{
   yield return new WaitForSeconds(timeToWait);

   // ... do some bomb stuff
}
Enter fullscreen mode Exit fullscreen mode

Oh my God! Just look at all that code! Two entire blocks just to make a simple bomb! I have enough with the explosion logic itself! Well, fortunately, someone at Unity Technologies might have thought the same, so you can use this little trick to save some lines of code:

[SerializeField]
private float timeToWait;

private IEnumerator Start() 
{
    yield return new WaitForSeconds(timeToWait);

   // ... do some bomb stuff
}
Enter fullscreen mode Exit fullscreen mode

Beautiful, is it not? Yeah, yeah, you can still write the explosion logic on a separate function, and, in theory it would be the same two blocks of code again. But my point is, that you don't have to declare a new coroutine. Unity allows for declaring Start() as a coroutine! Go figure! I don't know about you, but I find this very useful. And as soon as I discovered this trick, which I have not found documented on Unity's official documentation, I never stopped using it. I hope it turns out to be useful to you!

Why this happens? Beats me ¯\(ツ)/¯ Unity isn't open source so I don't know for sure. But, basically, if Unity finds the Start() method declared to return an IEnumerator(), it will automatically call it as we call coroutines, so it will behave accordingly.

Use what you need

I used to be one of those guys who often left their Update() methods empty when I didn't need it for anything. Well, when you declare these so called "magic methods" in Unity. They are added to the game loop and are being called, even when they're empty. Which means that Update() method you're leaving empty is causing overhead. Yes, it's small. But if you are like I used to be and leave every unused Update() method empty, then eventually you will feel the difference, especially on low-end systems.

My point is, just use the magic methods you actually need for your game behaviour to be the intended one. If you won't use it, just delete it. Because if not, it will definitely be called, even when empty.

Don't animate with strings, use hash instead!

We all have hardcoded strings more than once when working on games. A common example in the games I've made is with animations. Unity has an animation system I'm not too fond of. Especially because you trigger animations by name on a string. I'm always afraid of declaring stuff that way because, what happens if I miss a letter and write "isWaling" instead of "isWalking"? Disasters will happen!

Thankfully (and honestly, it should be the norm), animations can not only be declared in an intellisense-friendly manner, but it can also be less expensive to call them! So it not only makes your life as a programmer easier, but it also helps you with some small, free performance gains!

Unity's own Animator class has a cool, little method called StringToHash(), which will generate the right ID for our animation, which then we can use to call our animations just like we always do. What's so different, then?

StringToHash() 101

What does Unity do when we use something like this, for example?

private Animator _animator;
private void Start()
{
    _animator = GetComponent<Animator>();
}

public void AnimateFruitCatch()
{
   // called by hard-coded string. Also, it produces garbage which may cause stutters in our game if done too much
   _animator.SetTrigger("CatchedFruit");
}
Enter fullscreen mode Exit fullscreen mode

Well, for the uninitiated, Unity will first look for a match between the animation you want to call, and the ones you've assigned to your Animator component. This will be done through string matching, which, is slow compared to number matching. When it finds a match, then it will generate a hash value from animation's name. This numeric value is the actual ID for the animation you want to play, then it will look for the animation that matches this generated hash.

Unity basically performs two searches, for little actual gain. Since, on the first place, typos may happen, and you don't want to suffer the consequences of misspelling in gaming.

Ok, so what can we do to reduce this overhead and also save ourselves from an Alien: Colonial Marines situation? Well, it's as simple as pre-computing the animation's hash code! This can be easily done with the StringToHash() method I mentioned earlier.

This is a snippet from a game I developed for mobile devices called Eden:

private Animator _animator;

// here, we pre-compute the hash and use the animation's name JUST ONCE
private static readonly int CatchedFruit = Animator.StringToHash("Catched_Fruit");

private void Start()
{
    _animator = GetComponent<Animator>();
}

public void AnimateFruitCatch()
{
   // since we call the variable directly, it becomes intellisense-friendly!
   _animator.SetTrigger(CatchedFruit);
}
Enter fullscreen mode Exit fullscreen mode

By animating our GameObjects this way, we significantly reduce the overhead caused for looking up the animation we want to use by string matching, which like I said earlier, it is very slow compared to int matching. This will help you save some precious processing cycles and also make your life a little bit easier.

This is it for now, folks!

I don't have anything more to share about the subject at the moment. I hope any of these are useful for you, fellow game developer!

I'll post more of these regularly, so make sure to follow me and check out when I post more articles like this one!

Discussion

pic
Editor guide
Collapse
sagex profile image
Sagex

Thanks for the tip \o/