Recently I’ve been thinking a lot about how a high-quality ECS would appear / present itself within Unity, how it would interact with standard Unity3d game-code at the API level, how it would appear in the Unity Editor, etc. This evening, I had a go at making some of that within Unity itself.
NB: a few weeks ago I made a small Editor Plugin that used Reflection to upgrade and fix some of the core Unity Editor code/GUI. That was practice for what I’m doing here. It’ll hopefully appear on the Asset Store at some point (pending review right now!) – it has the full source unobfuscated, so you can see for yourself both what and how I got it to do its clever bits.
References
Some things to read if you haven’t already:
- Sampsa’s post on the missing documentation for Unity’s ScriptableObject (follow him @snlehton)
- Mockups of GUI we might want in Unity3D for managing an Entity System
- High-performance, low-level memory management for Entity System implementation – we won’t be doing this immediately, but it’s a precursor to some of the things I’ll end up doing here.
- A single worked-example of where Unity’s built-in architecture makes it much harder to write games than if you were using a modern Entity System
- Lucas Meijer’s (@lucasmeijer) post on official Unity blog aboutSerialization in Unity – what is it, how it works under the hood, and how you fix it when it breaks/has missing features
(that last one is particularly important; working around Unity’s broken Serialization system is a whole mega topic in its own right, and I’ll be largely assuming you already have read up on it. If you don’t know Unity Serialization yet (enough to fear it!) go read that article)
Initial thoughts/plans
There’s been one thought in particular nagging away at me for a while now:
If we’re going to really, truly, genuinely replace Unity’s core scripting architecture (which sucks for game development) with a modern ECS one, then we MUST create a new custom panel for the GUI Editor.
…until / unless we do that, it’s just a Programmers’ Circle Jerk: it loses 90%+ of the audience of Unity developers, people who chose Unity because of the Editor. 1st-class Editor integration is more important than anything else (certainly a lot more important than – say – performance).
Core Unity bugs that got in the way
Unity never repaints windows when Scene changes
If the scene changes, we’ll be looking at a new Entity System (different entities, different components). You would naturally expect that Unity automatically repaints all windows when Unity changes the Scene, right?
Wrong.
(incidentally, this is why it’s also very hard to be sure that a scene has actually loaded when you double-click on it – Unity gives no indication to the user that anything changed, if the Hierarchy for the two Scenes was the same)
Workaround to Unity bug
The undocumented method “EditorWindow.OnDidOpenScene()” which you can override. Amusingly, in Unity 4.5 they “fixed” the “bug” that this was documented; now your only way of discovering it is by finding a Mystical Unity Guru who knows The Secrets of Unity.
ScriptableObject: fundamentally broken
This isn’t even funny any more…
ScriptableObject System.Guid properties are wiped by Unity Serialization
public class EntityID : ScriptableObject
{
public System.Guid GUID = System.Guid.Empty; // this will NEVER be saved by Unity
}
… you have to manually write a serializer and deserializer for System.Guid. Because C# built-in structs weren’t considered important enough to be supported by Unity. WTF!!!??!??
ScriptableObject breaks C#: no constructors allowed, no initialization allowed
We know why: it’s because Unity Serialization is crap.
Unity Serialization requires every class to use a zero-argument constructor and has a bizarre array of hacks to try and restore state.
If – for instance – you need to create a ScriptableObject and guarantee that it gets an internal globally unique ID … you can’t. C# supports it, but Unity removes that support. Joy!
ScriptableObject data loss
New discoveries:
- SerializedObjects saved into your scene are automatically deleted whenever you change Scene.
- …Unity staff apparently felt that developers are too stupid to write code; an info message tells you you “leaked” memory (no, no you didn’t. Unity incorrectly DELETED your non-leaking data)
- …probably they’re too embarassed to admit that their failure to include Editor UI to show “SerializedObjects in current scene” was causing many projects to accidentally build up masses of invisible objects.
- …there was a fix for that bug. It would have taken them MINUTES to code it up (I’ve done it myself, seems to work fine?). Or … they could break Unity further. Which option did they go with?
- (this was probably due to some time pressure at the time; but that’s what CTO’s and Tech Directors are for: to make sure such hacks are then fixed properly shortly afterwards, not brushed under the carpet)
- If you mark a SerializedObject as “DontDestroyOnLoad” you discover that bug is even worse. It’s really, really bad:
- When you try to change Scene, Unity correctly asks “do you want to save? You have unsaved changes”
- …and then whatever you do, Unity deletes your SAVED DATA, using the first bug above.
- …looks like the bug isn’t just “deleting data when it shouldn’t”, but “incorrectly implemented DELETE that overrides Unity’s built-in data and scene management”
From Unity forums, this has been around for a good 4 years.
Some Classes
EntityID – with workarounds for core Unity bugs
Here’s the fixed EntityID class from above, with the hacks needed to make up for Unity’s “oh, let’s take C# core features, and break them” attitude.
You need to do this a lot if you work with Unity, sadly…
[csharp]
/**
NOTE: this class is really only 1 line long. Unity problems add 10 lines to it!
*/
public class EntityID : ScriptableObject, ISerializationCallbackReceiver
{
public System.Guid GUID = System.Guid.Empty;
private string _UnityWorkaroundProperty_GUID = System.Guid.Empty.ToString();
public void OnBeforeSerialize()
{
_UnityWorkaroundProperty_GUID = GUID.ToString();
}
public void OnAfterDeserialize()
{
GUID = new System.Guid (_UnityWorkaroundProperty_GUID );
}
}
[/csharp]
Conclusions
I got a basic self-managing EditorWindow interacting, and duplicating the most basic behaviour of the Hierarchy window, but I wasted so much time trying to kick Unity’s awful ScriptableObject into working shape that I got no further than that. Very frustrating.
Here’s a video: