# Maintaining head tracking in Unity

## Ensuring immersive user experience quality

This guide shows you how to ensure that your Unity apps meet the following
quality guidelines:

- [UX-D2: App maintains head tracking](https://developers.google.com/vr/distribute/daydream/design-requirements#UX-D2)

- [PS-P1: App loads quickly or provides feedback in VR to the user if the app takes longer than three seconds to load](https://developers.google.com/vr/distribute/daydream/performance-requirements#PS-P1)

## Background

In Unity, app code runs on the main thread. When your app performs a CPU
intensive task, such as loading a complicated scene or initializing app state
for a large number of game objects, the main thread can become blocked.
This can result in full or partial loss of head tracking.

Users experience this as the world appearing to be locked to head movement or
as black borders intruding from the edge of the display. These black borders are
caused by the *asynchronous reprojection* feature having to apply a significant
translation to a very old image frame generated when the user was previously
looking in a different direction.

## Unblocking the main thread

There are a number of techniques you can use to help avoid blocking the main
thread for a long time and help ensure that your app maintains head tracking.

### Identify specific performance issues

Use the
[Unity CPU Profiler](https://docs.unity3d.com/Manual/ProfilerCPU.html)
to identify performance issues in your code.

Once you've identified a problematic section in your code, use the techniques
below to avoid blocking the main thread.

#### Defer initialization work

Identify work that it done in `Awake()` or `OnEnable()` that can be deferred to
a later time, using a Coroutine, or a refactored `Start()` method.

#### Refactor expensive `Start()` methods

Replace an expensive `void Start() { ... }` script block with one that
makes use of the `IEnumerator` pattern in order to defer some or all of the
work to subsequent frames. Here is an example:

    IEnumerator Start() {
      // Lets say we need to create 1,000 game objects, but we've determined that
      // we can only create approximately 100 at a time while maintaining framerate.
      for (int i=0; i < 10; i++) {
        // Wait until the next frame before continuing the loop. By starting
        // with a yield statement, we don't start any work until the next frame.
        yield return null;

        // Set up 100 game objects (1/10th of the total work).
        SetupOneHundredGameObjects();
      }
    }

#### Refactor expensive game logic

Similarly, you might be able to replace an expensive section in your
script with a
[Coroutine](https://docs.unity3d.com/Manual/Coroutines.html).
This lets you perform a small amount of work in each frame before yielding
control back to the game engine.

### Use a loading scene

If your initial scene takes too long to load, consider adding a loading scene
that provides feedback to the user. You can display a VR splash screen and/or
provide audio feedback while your main scene loads.

### Use asynchronous scene loading

When loading a new scene, start by using
[LoadSceneAsync](https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager.LoadSceneAsync.html)
and experiment with different values for
[Application.backgroundLoadingPriority](https://docs.unity3d.com/ScriptReference/Application-backgroundLoadingPriority.html).
You can use the returned
[AsyncOperation](https://docs.unity3d.com/ScriptReference/AsyncOperation.html) to
monitor or control the loading of the scene.

If your app still freezes or loses head tracking, use the
[Unity CPU Profiler](https://docs.unity3d.com/Manual/ProfilerCPU.html)
to find expensive script operations, including any `Awake()` or `Start()`
functions in the new scene and `OnDestroy()` methods in the old scene.
There are a few more tips in
[this Unity post](https://forum.unity3d.com/threads/loadsceneasync-loadlevelasync-freeze-at-90-for-large-scenes.379700/#post-2496996).

### Use a background thread

Move an expensive task off the main thread onto a
[background thread](https://docs.microsoft.com/en-us/dotnet/standard/threading/using-threads-and-threading).
However, note that Unity APIs may only be called from the main thread.

## Hiding effects of a blocked main thread

Even with other optimizations, you might still find it difficult to maintain
head tracking and frame rate at certain points in your app.

You can work around these issues by hiding the effects of a blocked main thread.
For example, when transitioning between two levels in a game, you can fade the
camera to black, spend a few hundred milliseconds on expensive computational
work, and then fade back into the new scene.

### Fade to black

To create a fade in and out effect, attach the `ScreenFade` script (and the
material and shader by the same name) in
[daydream-elements](https://github.com/googlevr/daydream-elements/blob/master/Assets/DaydreamElements/Main/Scripts/Utility/ScreenFade.cs)
to the main camera.

See the `LevelSelectButton` script for an example of how to use screen fade.

- If you fade to black while loading a scene, try using the synchronous
  [`LoadScene()`](https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager.LoadScene.html)
  method, as this allows the main thread to load the new scene slightly faster.

- Alternatively, you may find that you can start loading the next scene
  asynchronously using
  [`LoadSceneAsync()`](https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager.LoadSceneAsync.html)
  before fading to black and then delay the fade slightly using a Coroutine.

  This lets your app:
  - Provide feedback to the user for a longer time, meeting performance
    requirement
    [PS-P1](https://developers.google.com/vr/distribute/daydream/performance-requirements#PS-P1).

  - Maintain head tracking, meeting design requirement
    [UX-D2](https://developers.google.com/vr/distribute/daydream/design-requirements#UX-D2).

### Provide audio feedback

Consider providing audio feedback (music or other sound) while the screen
is faded to black to let users know that the app is still running. This is
especially important if screen remains black for a long time, for example while
loading and initializing a level.