♪ [MUSIC] ♪
[KRISTIN] Hello, and welcome to this Unite Now session
on understanding the order of execution for event functions.
Understanding the order in which your scripts
are executed within Unity can be crucial when you want to avoid
running into performance issues.
In this session, I want to walk you through the execution order
in hopes of making your life a little bit easier,
at least when it comes to coding in Unity.
I'll also explain what happens under the hood when you use
the new Configurable Enter Play Mode feature,
how it helps the iteration times,
and what to keep in mind when you're using this option.
Now, why is this important?
When you open a MonoBehaviour in Unity for the first time,
you're instantly greeted with these two functions,
and you quickly become aware of how they work.
Start is called only once,
and Update is being called once per frame.
Once your project grows in complexity,
you can run into problems.
In the Unity Editor here, I created a small example
to explain the importance of knowing this order a bit better.
What you see here is our Karting Microgame template.
In here, I created the script
that spawns a bunch of gummy bears as obstacles.
I want them to be randomly colored so I set the color of the Material
from a gradient right after I instantiated them.
Pretty standard stuff.
To get a reference of the Material,
I do a GetComponent call in the obstacle's Start function.
If I try this right now, exactly one white gummy bear
is being spawned, and I receive a NullReferenceException
in the Console.
This is because the Start function where I called GetComponent
has not been called at the time I tried to access
the Material to change the color.
I can solve this by simply changing my Start into an Awake function.
Now, if I try this one more time, and then start the game again,
my bears will spawn without any issues,
and there will be no more errors in my Console.
Of course, this is just one small example,
but there are countless scenarios where this knowledge
can come in handy. So let's jump right into it.
We have three functions you could use for any type of visualization.
This could be things like populating your pooling systems,
setting up enemies, or just simply getting components
you need to reference later in the code.
All of our initialization functions have one thing in common,
which is that they are called exactly one time.
The first function to be called is Awake,
and this is called right after the GameObject has instantiated.
This is followed by OnEnable which is called, as the name suggests,
just after an object has been enabled.
And after that, we have our Start,
which is called right before the first update.
Once we are done with the initialization functions,
we are jumping right into our Physics loop.
I'm calling this a loop because the physics updates
actually run at their own speed,
which is why they can be called multiple times per frame.
The first thing that is being called from the physics side
is FixedUpdate, which is where you should do
all of your physics calculations.
Fun fact, if you do them inside FixedUpdate,
you don't need to multiply your values by Time.deltaTime
because FixedUpdate is called on a timer
that is independent of the frame rate.
Within this loop, we are also calling our OnTrigger functions.
So this would be your OnTriggerEnter, OnTriggerStay,
and OnTriggerExit. And of course, we're calling
the OnCollision equivalent of them as well.
The trigger and collision functions are used to detect when RigidBody
comes in contact with a Collider.
Once the physics loop is complete,
we are calling all of our input functions.
This includes our OnMouse functions and equally, of course,
our touch inputs and mobile devices.
At this point in the execution order,
we are calling our trusty Update, which is where we do
most of our game logic in.
The Update function is called once every frame
and is therefore perfect for doing things
like repetitive checks and timers.
Right after that, we are dealing with coroutine updates,
which means we're calling our yield functions.
So this would call functions like yield null
and yield WaitForSeconds.
There's actually one exception to this
which is yield WaitForEndOfFrame, which is of course
waiting for the end of frame, and is therefore executed later.
Finally, for game logic, we are calling a LateUpdate function.
Once all of our game logic calls went through, we are calling
all of our rendering functions, which are responsible
for drawing our Scene for us.
The first one of the rendering calls is OnPreCull which is called,
as the name suggests, just before the camera is culled.
This is followed by OnBecameVisible and OnBecameInvisible,
which is called when an object enters
or leaves the camera frustum.
Similarly, we have OnWillRenderObject,
which is called once for each camera
if an object is visible, and then OnPreRender,
which is called before the camera starts rendering.
Then there's OnRenderObject,
which is called after all regular Scene rendering is done,
and OnPostRender, which is called after a camera finishes rendering.
We also have OnRenderImage, which is called
after Scene rendering is complete
to allow for post-processing of the image.
And of course, the last thing that is being called
in terms of rendering is our user interface with OnGUI.
Actually, that was a little bit of a lie
because the very last thing is OnDrawGizmos,
but that is only called when you are in the Editor,
so it wasn't really a lie.
Lastly, we need to discuss pausing and quitting.
As for pausing, we have OnApplicationPause,
which is called at the end of the frame
where the pause is detected. This effectively means
that one extra frame will be issued after it is called
to allow the application to show graphics
that indicate the paused state.
When quitting the application, OnApplicationQuit is called
on every GameObject before the application has quit.
This is useful to, for example, use this call to save a score
or any other data before exiting.
If you're in the Editor, OnApplicationQuit is called
when you are exiting Play Mode.
Lastly, we are calling OnDisable and OnDestroy
to clean up at the end of the frame.
Now that you know about the execution order,
it's time to jump into a Configurable Enter Play Mode.
Now, entering Play Mode doesn't take a long time
when you are working in a relatively empty project.
But the more you expand your projects,
you might have realized that entering Play Mode
can actually get pretty slow.
This is mostly due to Domain and Scene Reloading.
Domain Reloading basically cleans up your scripting state,
resets all static fields and registered handlers
to provide you with a clean, fresh setup
every time you enter Play Mode.
Scene Reloading, on the other hand, cleans up your Scene
by destroying all existing GameObjects
and reloading the Scene from disk.
These two tasks get increasingly slower
when the project grows in complexity.
This is why we created Configurable Enter Play Mode.
It allows you, the developer, to increase the iteration time
by letting you skip the very time-consuming parts.
To enable Configurable Enter Play Mode,
you need to go into your Unity Editor,
navigate to: File > Project Settings > Editor,
then you scroll down all the way to Enter Play Mode Settings.
Alternatively, you can also set them through code,
by using "EditorSettings.enterPlayModeOptionsEnabled".
So what happens when you are using Configurable Enter Play Mode?
Generally, when entering Play Mode,
we are resetting the scripting state,
which is our Domain Reload, then resetting the Scene state,
which is our Scene Reload, and then we update the Scene two times.
Once with the rendering and once without.
Disabling Domain and Scene Reload means that a lot of the processes
that normally happen when you enter Play Mode
aren't taking place anymore.
This means faster iteration time,
but it also has significant side effects.
So let's discuss some of these side effects.
Skipping Domain Reload.
The biggest downside of using Configurable Enter Play Mode
is that the scripting state isn't globally reinitialized.
This means that fields that are marked
with NonSerialized attribute will keep their value.
Similarly, static variables will keep their values as well.
Static events will keep all of their subscribers.
And there's no OnDisable or OnEnable calls
for executing Edit Mode scripts.
This means that you have to decide what actually needs to be reset.
Avoiding Scene Reload, on the other hand,
is rather complex to implement,
but should only have minimal side effects, really.
Of course, it's in tight connection with the Domain Reload
so there are a few downsides that you actually should be aware of.
The biggest downside is that when you disable Scene Reloading,
the time it takes to start the application in the Editor
is no longer representative at all
of the startup time in the built version.
So if you wanted to debug or profile exactly what happens
during your project's startup time, you should enable Scene Reloading.
That way you get the true loading time for your project.
Understanding how Unity orders and repeats event functions
over a script's lifetime can help you see where your scripts fit in
and avoid potential issues.
It's also super important to be aware of this
if you want to use the new Configurable Enter Play Mode feature.
We believe that if your project is currently slow to enter Play Mode,
this feature will speed things up significantly for you,
but you need to be aware of what happens under the hood
when you use it.
♪ [MUSIC] ♪