Games 101: The Semi-Rules of the Unity Life Cycle

One of the more problematic aspects of any programming is race conditions. Frequently code is dependent on other code that is dependent on other code. So what techniques can we use to make sure that our code initializes and runs without issue? These are my unwritten (no longer!) rules about the cycle of life of game objects.

circle_of_awful

While my Games 101 articles are usually based in Unity, I try not to be too specific about Unity, and instead focus on patterns and ideas. These rules are based on experience in similar systems but also focusedĀ on Unity-specific hiccups that I’ve found.

The Unity Life Cycle

There are various phases of events in Unity. These happen in a certain order, and only doing certain things in a certain phase is a Good Thing. It is also not a good idea to assume that specific calls will occur (don’t assume OnEnable is called, for example). Most of what I’ll be talking about is when in the life cycle you should reference/use other game objects, and when not to. And I’m only mentioning important phases.

Awake()

Awake is called right when an object is created. Awake exists to do self-initialization. Never, ever, touch any other game object in Awake. It is okĀ to grab a reference to another MonoBehaviour inside, but don’t get any information insideĀ it. Those other objects may not be initialized yet and you’ll get half-baked data. Be patient and let the other objects initialize.

  void Awake()
  {
    // this is ok
    myBehaviour = GetComponent<MyBehaviour>();

    // NOT OK, let myBehaviour initialize itself first!
    //myBehaviour.someValue = 10;
  }

OnEnable()

Similar to Awake(), touching other objects in OnEnable() is frequently problematic. I highly recommend never doing stuff with other objects in OnEnable, and instead waiting until Update() by using dirty flags (more below). The reason for this is because sometimes hierarchies of objects are being turned on or off, and object state is still in the middle of transitioning. I’ve had this happen most in UI, such as tabs that display views, but the view isn’t on yet, or hasn’t been fully instantiated, etc. Set a dirty flag and do your update in Update().

Start()

Ok, all the game objects, etc have initialized themselves via Awake. Now you can assume (for the most part) that other objects are initialized and if necessary get information, register with controllers, etc. This isn’t always the case so there may need to be a little bit more thought put into this. Perhaps. Or maybe its better to add a phase after this….

Init()

That’s right, I frequently create a phase after Start() I call Init() that essentially does what Start() does but I can pass parameters or set variables before calling it. If you don’t need this phase then use Start(). In the example below I’m instantiating a Baddy, then calling Init() because I want to update the speed with a modifier before applying force:

  void spawn(BaddyWaveItem item)
  {
    Baddy baddy = Instantiate(item.baddyPrefab);
    baddy.transform.position = transform.position + item.posDelta;
    baddy.transform.rotation = transform.rotation;
    baddy.speed += item.speedModifier;
    baddy.Init();
  }

Update()

Update() is the place where you can put stuff you want to call every frame….until you don’t want to call it every frame. Usually I do not use Update(), but instead create a coroutine like so:

  // When we are firing, this will not be null.
  IEnumerator fireRoutine;
  IEnumerator shootThings()
  {
    while(true)
    {
      fire();
      yield return new WaitForSeconds(fireRate);
    }
  }

And the point of Update() becomesĀ controlling state changes such as input:

  void Update()
  {
    if(Input.GetMouseButtonDown(0))
    {
      if(fireRoutine == null)
      {
        fireRoutine = shootThings();
        StartCoroutine(fireRoutine);
      }
    }
    if(Input.GetMouseButtonUp(0))
    {
      if(fireRoutine != null)
      {
        StopCoroutine(fireRoutine);
        fireRoutine = null;
      }
    }
  }

This has the benefit of allowing me to isolate specific behaviours into their own coroutines instead of bucketingĀ them in with everything else. Its self-documenting, and moreĀ obvious.

Dirty Flags

Since we’re talking about Update() let me be clear about what I mean by dirty flags. Dirty flags allow you to set properties immediately, but do something later. It is a very simple concept:

  bool _dirty = false;

  public GameObject someOtherObject;

  void OnEnable()
  {
    // We know someOtherObject is in some kind of weird state right now
    // let's wait until update to get it.
    markDirty();
  }

  void OnDisable()
  {
    someOtherObject = null;
  }

  void Update()
  {
    if(_dirty)
    {
      _dirty = false;
      someOtherObject = getSomeOtherObject();
    }
  }

  public void markDirty()
  {
    _dirty = true;
  }

Coroutines

Pretty much ‘anything goes’ in coroutines. This and Update() is where I like to execute most of my code. Everything is warmed up and ready to run.

OnDisable()

OnDisable() usually has less issues than OnEnable(). Usually shutting down coroutines, dereferencing other objects, and using other objects is fine. Starting coroutines on objects that are being disabled is probably not a good thing. šŸ™‚

OnDestroy()

OnDestroy() is an interesting one. Usually I am destroying other objects that I’ve created. But you may want to do something like tell a controller or other object ‘hey i’m dying so don’t pay attention to me anymore’. But when the game is shutting down that other object might have been destroyed! This means annoying null exceptions on shutdown. I hate these, and so I find that I need to wrap thingsĀ in a null check:

  void OnDestroy()
  {
    // myController may or may not exist on quit.
    if(myController != null)
    {
      myController.removeThing(this);
    }
  }

Unpleasant Surprises

Keeping a consistent order of operations is very useful in maintaining stability and avoiding bugs. There are certain programming patterns that can wreak havoc with the cycle of life and knock things out of equilibrium.

Eventyness

Events aren’t bad, but sometimes thingsĀ get pretty eventy. When you click on a button, an ‘event’ is sent. This event is sent before Update() so you know when its going to happen. But ifĀ I were to say, handle events from the network which may be happening on a different thread….when does that code execute? I could handle it right then and there, sending info to a controller that will update all my game objects. Or I could queue up the events until Update() or a coroutine and then I know exactly when the networking code will execute and can plan accordingly.

Dirty flags are a great way to handle issues with eventyness. ReceiveĀ the event ‘whenever’ and then update later. This can improve performance as well if you are receiving redundant events (like multiple collisions for example).

Singletons

Similar to events, Singletons aren’t bad, but things can get pretty, erm….Singleton-y. A Singleton is any class that has only one instance, and is usually initialized when first used. This second part is the problem:Ā initialized when first used. So this initialization happens…when? It could happen before or after something else. What happens if Singleton X has dependencies on Singleton Y? Maybe Y was initialized earlier, or maybe its not. Theoretically this should not matter. Singleton Y has nothing to do with X. But in practice, this is where hard to find bugs show up. A code change was made, and Y is now initialized immediately, but before it had time to pull data off disk and was always initialized. Bug!

I recommend making your Singletons MonoBehaviours….like so.

In Closing

If I know what state my objects are in I can safely make assumptions and avoid bugs. This also leads to less code because I don’t need to write code for edge cases where thing X happens before thing Y. I think the peace of mind is most useful.

Got some of your own tips on what to do (or not) at specific times in Unity? Post ’em on Reddit here.