TL;DR; Try out the experimental Configurable Play Mode option in Editor ProjectSettings to improve iteration time when you don’t mind scripts not being fully reset.
Play Mode is one of the key concepts in game development with Unity. The ability to iterate fast in the Editor is a great advantage which allows developers to prototype faster, fail faster, and create great games more easily. The time it takes to enter PlayMode is a vital metric when it comes to fast iterations. With an empty project it typically takes 1.5-2.5 seconds. However, with the project evolving, time to enter Play Mode might grow significantly to 10-25 seconds, crippling productivity.
We profiled and analyzed Enter Play Mode to understand where the time is spent. As a result, we would like to offer options to skip the most time consuming parts during Enter Play Mode and let you decide whether accuracy or speed is preferred during development.
How much time can be saved
Domain Reload and Scene Reload are the main contributors to the cost to Enter Play Mode - typically their cumulative impact is around 80-90% of Enter Playmode time.
Domain Reload is a generic way to achieve a clean state of game and Editor C# code (besides its main role of reloading a new scripting code). Similarly Scene Reload is a generic way to get a clean state of GameObjects and associated MonoBehaviors/ScriptableObjects. Generic approaches are simple and allow not to think about behavior differences, but incur huge time costs in some cases and reduce productivity.
As you can see below, the effect of skipping Domain Reload and Scene Reload is very promising.
It should be noted that the outcome depends on the project and might vary depending on Scene complexity and used packages and AssetStore plugins.
How to enable
You can find the options in the Editor Project Setting.
ProjectSettings → Editor → Enter Play Mode Settings
The same settings are configurable through the EditorSettings scripting API that we have added to the UnityEditor namespace:
- bool EditorSettings.enterPlayModeOptionsEnabled;
- EnterPlayModeOptions EditorSettings.enterPlayModeOptions;
EnterPlaymodeOptions allows you to disable Domain and/or Scene Reload.
What exactly is skipped
From a high-level perspective, Enter Play Mode consists of the following main stages:
- [Backup current Scenes]. Allows us to restore state during Exit Play Mode. Is optional - only happens when Scene is dirty.
- Reset scripting state (reload scripting domain, aka “Domain Reload”).
- Resets Scene state (Scene Reload).
- Update scene (2 times - without rendering and with rendering).
Combination of Domain Reload and Scene Reload is the main job of Enter Play Mode state. It resets the game world and simulates the startup behavior as the game would run in the player.
To help you understand those options, I would like to provide a scripting events diagram for Enter Play Mode and highlight differences.
What are the side effects
Important differences due to skipped Domain Reload
The main side effect is that the scripting state is not globally reinitialized and it is up to you to decide what has to be reset.
-
Fields marked with [NonSerialized] attribute keep their values - as script serialization step is skipped completely and scripts remain the same, all data is carried to the Play Mode.
-
Static variables keep their values - because Domain Reload does not happen types stay initialized.
-
Static events keep their subscribers.
-
There is no extra OnDisable/OnEnable calls for ExecuteInEditMode scripts.
Static variables can be reinitialized with the help of the RuntimeInitializeOnLoad (RuntimeInitializeOnLoadMethod) attribute.
using UnityEngine;
public class StaticCounterExampleFixed : MonoBehaviour
{
static int counter;
[RuntimeInitializeOnLoadMethod]
static void Init()
{
counter = 0;
}
}
Important differences due to skipping Scene Reload
Avoiding Scene Reload is much more complex to implement and align with the normal loading path, but should have minimal side effects. However, due to its tight connection to Domain Reload, there are a couple of important differences:
- ScriptableObject, MonoBehaviour fields which are not serialized into the build ([NonSerialized], private or internal) keep their values - as existing objects are not recreated and constructors are not called.
Watch out for null private and internal fields of array/List type - they are converted to an empty array/List object during Domain Reload and stay non null for game scripts. - ExecuteInEditMode/ExecuteAlways scripts are not destroyed/awaken - no OnDestroy/Awake calls for those.
Watch out for Awake/OnEnable methods which check EditorApplication.isPlaying property - Awake is not called and OnEnable is called only when EditorApplication.isPlaying is already true on Play Mode change.
Nonserialized fields for game scripts should not be an issue, as game scripts are not active in Edit Mode, however, the ones marked with ExecuteInEditMode/ExecuteAlways might change themselves or touch fields of other game scripts. This can be mitigated by initializing affected fields in an OnEnable callback.
Known issues
- Unity Issue Tracker - Particle System is not playing on awake when Reload Scene enter Play mode setting is disabled - Fixed.
- Unity Issue Tracker - Unassigned private array field is not null when entering play mode with Reload Domain & Scene disabled. - Can’t be fixed due to being a Domain Reload issue.
- Unity Issue Tracker - Lighting is not applied correctly after using Progressive Lightmapper when Scene Reload is disabled in Project Settings - To be fixed.
Call for feedback
We believe that if your project is currently slow to enter Play Mode, this feature will speed things up significantly. However, we understand that the side effects of the feature might be difficult to handle and we’re looking forward to hearing what you think, which issues you encounter in you project/ package/AssetStore plugin and how we can make life easier for you!
Big thanks to @sinitreo , @chrisk , @Peter77 , @Baste , @Hyp-X and others for providing a valuable feedback.