Configurable Enter Play Mode Compatibility

With 2019.3, Unity released a brand new feature called the Configurable Enter Play Mode, which can drastically reduce the time it takes to enter the play mode inside the Unity Editor. Read more about it on the page linked above or the blog post.

Problems

The two options, namely Reload Domain and Reload Scene, either disable the resetting of the script state or the scene reload when enter play mode. Naturally, this brings some problems with itself.

Domain Reloading

When disabling this feature, static fields do not get reset and static event handlers will not unregister methods, meaning that when entering the play mode the methods get registered again. This may be very undesired and can cause troubles.

For more information, see here.

Scene Reloading

Disabling this feature makes Unity no longer destroying all existing scene gameobjects and reloading everything from disk, but only recreates the modified content of the scene. This has the consequence that certain ScriptableObject and MonoBehaviour fields keep their values and that scripts that use the ExecuteInEditMode or ExecuteAlways attribute do not receive OnDestroy or Awake calls.

For more information, see here.

Game Creator

Game Creator itself uses not only static fields but also the ExecuteInEditMode attribute, meaning that there are errors thrown when enabling either of these options. We will tackle this in this guide, read how to below.

What To Do

Requirements

This tutorial uses Unity 2019.4.24f1 and the latest versions of GC and the modules at this point (GC v1.1.12, Inventory v1.1.3, Quests v1.0.2, Stats v1.1.3, 04/20/2021).

Analyzing

First we need to analyze which classes in Game Creator and the modules use either of the mentioned things above. These are the following:

Game Creator standalone:

  • AudioManager

  • Character

  • CharacterHeadTrack

  • CoroutinesManager

  • EventDispatchManager

  • EventSystemManager

  • GlobalID

  • GlobalVariablesManager

  • LocalizationManager

  • PlayerCharacter

  • PoolManager

  • RememberActive

  • SaveLoadManager

  • SimpleMessageManager

  • Singleton<T>

  • TimeManager

  • TouchStickManager

Inventory:

  • InventoryManager

  • MerchantManager

Quests:

  • QuestsManager

Stats:

  • Stats

3rd party modules:

The safest way to know which classes are affected is by asking the module owner. Still, you can also find it out by yourself. Open the GlobalID script with your scripting environment (like Visual Studio) and check the references of the class. It is very unlikely for a module to use the ExecuteInEditMode attribute. Once found out, follow the instructions below.

Managers first

The first thing we want to do is change the base class of every manager class, because there are 14 deriving from Singleton<T>. We add the following code after the "Constructor" comment and before the [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] attribute:

// Singleton.cs
// CONSTRUCTOR: ---------------------------------------------------------------------------
// Code to add start
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
protected static void Init()
{
instance = null;
IS_EXITING = false;
}
// Code to add end
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
public static void OnRuntimeStartSingleton()
{
IS_EXITING = false;
}

What does this code do? When the Reload Domain feature is unchecked, the attribute RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] is called, the method Init gets executed which resets the static variable instance and sets the IS_Exiting bool variable to false. Normally, the second one is not necessary, but with Reload Scene disabled, the method OnRuntimeStartSingleton is not executed and thus we need to set it.

The next point on the list is to add the following code to each of the manager classes:

[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
public static void Initialize()
{
<className>.Init();
}

Replace <className> with the name of the class of the manager. Here are a few examples:

// TimeManager.cs
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
public static void Initialize()
{
TimeManager.Init();
}
// EventDispatchManager.cs
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
public static void Initialize()
{
EventDispatchManager.Init();
}

What does this code do? As we have seen before, when Reload Domain is disabled, the attribute in the code is called. The <className>.Init(); tells the manager to be initialized (reset) when the play mode is entered.

Components second

Game Creator and the modules feature a variety of components to add to the gameobjects. But the base class, GlobalID, also uses the ExecuteInEditMode attribute. This means we can't use the Awake method. We need to change it like this for all classes that derive from GlobalID:

  1. Add a private boolean variable called initialized<className>, i.e. initializedStats, with the value false

  2. If there is an Awake method, rename it to OnEnable

  3. If you are not in the GlobalID class, add the Override keyword after the modifier if it is not already there

  4. Move the code in the renamed method behind an if condition that checks for the negative value of the newly created boolean variable

  5. If there already is an OnEnable method, merge the two of them together, whereas the code of the old OnEnable method after the closing of the if condition comes

Let's take a look at an example:

// GlobalID.cs
// ----- Old Code -----
// Old code, will be changed
protected virtual void Awake()
{
this.CreateGuid();
}
// ----- New Code -----
// Add the private boolean variable
private bool initializedGlobalId = false;
// Rename method, add if condition
protected virtual void OnEnable()
{
if (!this.initializedGlobalId)
{
this.CreateGuid();
this.initializedGlobalId = true;
}
}

Another example with existing OnEnable method:

// RememberActive.cs
// ----- Old Code -----
// Old code, will be changed
protected override void Awake()
{
base.Awake();
if (!Application.isPlaying || this.exitingApplication) return;
this.defaultState = this.gameObject.activeSelf;
}
// Old code, will be merged
private void OnEnable()
{
if (!Application.isPlaying || this.exitingApplication) return;
this.UpdateState();
}
// ----- New Code -----
// Add private boolean variable
private bool initializedRememberActive = false;
// Rename method, add if condition, merge methods
protected override void OnEnable()
{
if (!this.initializedRememberActive)
{
base.OnEnable();
if (!Application.isPlaying || this.exitingApplication) return;
this.defaultState = this.gameObject.activeSelf;
this.initializedRememberActive = true;
}
if (!Application.isPlaying || this.exitingApplication) return;
this.UpdateState();
}

Known Issues & Workarounds

Problem: You may experience the triggers On Mouse Left Click, On Mouse Middle Click and On Mouse Right Click (maybe some other triggers as well) to not be working when Reload Scene is deactivated.

Answer: Make sure to add the components Physics Raycaster and Physics 2D Raycaster to the main camera. When Reload Scene is deactived, Unity doesn't check the gameobjects whether they have dependencies to the named components and thus won't add them.