Note: This post can be considered a ‘take 2’ of my previous No More Unity Hot-Reload post. While the solution has changed the description of the issue in that post is still valid.
So what’s the issue again?
If you edit your scripts while running the Unity Editor in play mode, it will recompile your scripts and then try to hot-reload the newly compiled assemblies (ie. switch over to the new code without leaving play mode).
In order to do this the editor will:
- Serialize all the objects in the hierarchy
- Switch the managed environment over to the new assemblies
- Deserialize all the objects back into the hierarchy
This sounds great except anything which isn’t serialized will be lost (see the Unity serialization rules here). This includes any Dictionaries and non-serializable custom types, anything private that isn’t marked with the [SerializeField] attribute. Basically loads of stuff you almost certainly have in any non-toy project. That means that, in practice, after the reload you just get a lot of NullReferenceExceptions every time anything tries to use everything which wasn’t serialized in the hot-reload process.
I see, what can I do about it?
It is theoretically possible to handle these reloads in your code by handling all the null cases – regenerating the data which is essentially just cached data and always serializing everything which can’t be regenerated (and hiding the things you don’t want to see in the inspector using the [HideInInspector] attribute). This becomes a lot easier if you use the Odin Inspector and Serializer (which I’m using for Re-Entry and is really excellent!) as that replaces Unity’s built-in serialization and allows you to serialize everything you’d expect to be able to serialize in C# on your MonoBehaviours (and edit them in the inspector). Seriously, it’s awesome!
Though I do use Odin I still choose not to bother making hot-reload in play mode work. Handling all the special cases is a heavy burden on the developer and significantly increases code complexity. It ultimately provides a minor improvement on the already quick edit-compile-test cycle Unity has without it.
I’d recommend a lot less time spent configuring your project to use Assembly Definition Files to get the cycle even shorter is a much more efficient way to go. Supporting Hot-reload while in play mode just isn’t worth the time spent maintaining it.
Ok, so how can I disable assembly reload while in play mode?
Back in 2015, I wrote a post titled No More Unity Hot Reload in which I propose the solution of exiting play mode immediately if script compilation is detected. I used that happily for a couple of years but it still bothered me that there was the potential for ‘oh no, I just lost some play mode changes I really wanted‘ situations.
I did some more investigation and there is an API for locking the assemblies which in-turn prevents auto-reload.
Note: The LockReloadAssembly API already existed back in 2015 but I couldn’t seem to get it to work for this purpose back then.
So I’ve switched to a new editor script for handling this issue which uses that API. Its behaviour is a little different from the previous ExitPlayModeOnScriptCompile script.
In this version, if script changes are detected while in play mode the Unity editor will display the script updating spinning ring in the bottom right but it won’t actually do any compilation or reload until play mode is exited. The only downside of this is that you won’t see any compile errors until you exit play mode but I guess you can’t have everything!
Here’s the code: DisableScriptReloadInPlayMode.cs