Why does activation phase of a scene being loaded in Unity take too much time?

1.6k Views Asked by At

I'm new to Unity3D. I am trying to make my own FPS game in Unity 2020.3. I have to make a lot of maps (scenes). So far I discovered a problem with LoadSceneAsync.

What I know from Unity docs: When AsyncOperation.progress variable is between 0 and 0.9, the engine is loading resources and stuff for the new scene. When it reaches 0.9, it starts activating the new scene (deleting old objects and adding new ones + running Awake and Start methods?).

The problem is that the activation phase takes A LOT of time. For example: when the loading phase takes only a few frames, the activation phase takes about 10 or a 100 times more frames to do its thing.

I tried running the game on another devices and I saw no comparable change in loading speed. Is this a Unity problem or am I using it wrong? Or are those device that I tested the game on just not good enough?

I have no idea why this is happening. I pulled up profiler but I can't understand what exactly is going on. Can somebody please explain what makes this so slow and how to improve its performance?

Screenshot from profilerFrame count when loading

2

There are 2 best solutions below

1
On

What version of Unity are you using? When I look at your profiler log I see Prefabs.MergePrefabs (834,62ms):

enter image description here

When I google that I find an answer here that seems to imply that this has something to do with nested prefabs, or prefabs used in a GameObject hierarchy.

I honestly am not sure what the answer is saying to do to resolve the issue, but search results also turned up this Unity issue that says a slow Prefabs.MergePrefabs is:

Reproducible with: 2019.3.0a1, 2019.3.12f1, 2020.1.0b8, 2020.2.0a9

The issue tracker states that the issue is:

Fixed in 2020.2.0b1

Fixed in 2020.1.8f1

Fixed in 2019.4.24f1

0
On

Ran into this a couple of years ago when developing an indie narrative action platformer which had large-ish Scenes with a whole ton of GameObjects for visual hierarchies AND a lot of logic Components.

LoadSceneAsync is nice in that it loads asynchronously and somewhat smoothly, but then comes the Scene activation as you notice, and that's synchronous - everything on that "game frame" grinds to a halt for however many seconds until

  • all hierarchies are activated: depending on the size and complexity of your Scene this takes a shorter or a longer time in itself already
  • all Awake and OnEnable logic is run for every Component that implements those methods: almost everything is a Component and any GameObject-hierarchy/Prefab you've built can have however many Components in itself, multiplied by how many and how complicated GameObject hierarchies you have in the Scene and then multiplied again by what those Components do in their Awake/OnEnable. They are likely initializing class members, looking up references to other Components or core systems, and whatever else. Some Components don't need Awake/OnEnable methods while some may do impractically expensive stuff in them. In practice it falls back on developers to figure out by profiling which Components end up spending too long on this stuff and decide what to do about it on a case-by-case basis.

Now, there's less that you can do about Unity's built-in Components that you don't or can't maintain a self-controlled version of: it could be any kind of combination of Components where each Component type could do whatever during its Awake and OnEnable, however slow or expensive.

In general, I think you could split the Scene into groups of smaller hierarchies that are disabled by default and then write some initialization logic in a Scene root-level GameObject that activates those disabled groups one-by-one, making things a little more "asynchronous", sort of: this should smooth out both Unity's and your own Components.

But all of your own Components (and any Package Components where you maintain a self-controlled/overridden/in-Project version of the Package), there's some things you could consider.

  • Check your Awake and OnEnable methods, run profiling and find out the top time-sinks during that activation peak. Undoubtedly a lot of Unity's built-in stuff there, but if you have time-sinks of your own you'll spot them on the list soon enough. Figure out some optimizations on a case-by-case basis.
  • Figure out some alternative to relying too heavily on Awake/OnEnable methods for your Components. Firstly this is just an example, and I fear far from a perfect solution either. Secondly, I think this is a more involved part-solution, so I'll just describe the concept broadly; in the game I worked on I made something I called DelayedActivator and setup a IPreAwake interface that any heavier-initialization-requiring Components could implement. DelayedActivator hooks up to Unity's Scene-saving and automatically looks up all Components that implement IPreAwake (serializing them as UnityObjects since I couldn't serialize interface instances). When the Scene is activated, DelayedActivator iterates through this serialized list, casting each UnityObject back to IPreAwake and then executing a initialization method declared in the interface for n amount of IPreAwakes per frame (since I wanted smoother loading, to allow displaying some animated loading indicator, etc.), until all IPreAwakes are run and we finally activate a "group"/"folder" root GameObject that holds most or all of the Scene's contents. We still take the hit of running the whole hierarchy activation at once, but this way I could split Awake/OnEnable costs, first into multiple frames of iterating IPreAwake initialization methods, and then later all Unity's builtin Components' Awake/OnEnable (and for those of our own that I didn't turn into IPreAwake implementers).

Summing up: profiling takes some getting used to - I'd probably use the hierarchy/list view (dropdown where you have "Timeline" right now) on that peak-duration frame you already have there and see what Components and methods come up top when sorted by longest time taken (my own? Maybe I can optimize them. Unity's? Maybe I can work around using them, or use less of them, roll my own or build a custom version of them?). If after that there's still a wide-spread issue with a lot of Awake/OnEnable popping up taking time, I'd consider splitting the Scene (either into disabled hierarchies and opening them up one at a time or maybe splitting the entire Scene into multiple sub-Scenes and asynch-loading them in row, only activating gameplay logic after all sub-Scenes are loaded). I'd only resort to wacky hijinx like above-described DelayedActivator/IPreAwake combo if absolutely necessary for a bigger project.