Categories
programming Unity3D

Some thoughts on loading Scenes in #unity3d

How do you program a “level”?

This is not usually a problem in a game engine, but Unity requires you to isolate each “level” as a Scene. And Unity’s Scenes have some strange design choices (bugs?) – IMHO they were designed for toy projects, not for production games.

Here’s a stream-of-consciousness on what works, what doesn’t, and how I can workaround / do it better within Unity… it’s not carefully researched, mostly it’s from memory / off the top of my head. So … it might not all be true :).

When I switch to Unreal4, I’ll be using posts like this as a baseline to compare where Unity failed, and how badly – or vice versa.

UPDATE: added some more approaches and ideas/commentary

For example:

  1. When you load a Scene, everything in your game is destroyed
  2. Scenes cannot take parameters
  3. Scenes cannot hold state
  4. Scenes cannot be constructed – nor modified – in code. Procedural game? HA! No chance!
  5. You can’t edit more than one Scene at once (this is absurd, and makes development unnecessarily painful)
  6. … and a bunch of other problems, many of which are side-effects of those above

More Unity bugs that make this Hellish

  1. When a Scene loads, there is no way to tell Unity what code to run.
    • This is REQUIRED because Unity refuses to let you pass parameters
    • Without it, you have to rely upon Awake(), Start(), and OnEnable()
    • Those three methods are DEFINED as running in “random” order among your objects
    • If you have a parameter that needs to be found, loaded, and positioned, you can put it in Awake()
    • If you need a global reference to that parameter, you’ll have to set it during Start(), otherwise there’s no guarantee it’s been found yet
    • Any code that needs to use that parameter cannot go in anything except OnEnable()
    • …BUT: a lot of (most?) Unity code that should be in Start() will break if it’s in OnEnable(). The two methods do different things!

Unity’s scene-loading SUCKS.

Some common workarounds

Unity5: additive loading

You can load one Scene, and then load another over-the-top. This has long existed in Unity Pro to some extent (allowing “level loader” Scenes etc), but U5 upgrades/extends it a bit.

As I understand it, Unity5 does NOT fix the problems, but it does mitigate them. In particular, I’ve looked at Editor changes to improve handling for loading multiple scenes at once. I haven’t experimented with this yet myself (I’m still on U4). From the videos I’ve seen, it looks like a definite improvement, but still quite a long way behind what I’d call “standard” in game-engines.

If you’re able to use U5, I’d start with this, and see how far you can get with it. And then post / tweet about your tips and tricks, please.

Don’t Destroy On Load

You can mark any number of GameObjects as “Hey, Unity! When you delete everything (which is stupid, but OK), don’t delete THIS one!”

In theory, that means you can write your own mini game-engine (um, why are you using Unity again? Because you want to re-invent the wheel all the time, right? /sarcasm) that stores all the game-data in a “don’t destroy” object, and then parses and loads it after the new Scene has finished loading.

This doesn’t solve anything – it shifts the problems to a new place, but at least they’re now in YOUR code, instead of in Unity’s. You end up writing your own mini game-engine. You get almost zero help from Unity, and … to make it harder … you have to remain compatible with Unity’s proprietary data-handing: Initialization protocols, crappy Serialization, etc.

Static variables

This is the “real” way of doing DontDestroyOnLoad, the standard approach that is built-in to the programming language. It always struck me as bizarre that Unity added DontDestroyOnLoad when there’s a more standard approach already in place. Was it a junior programmer who didn’t know how to use the “static” keyword? Or is there something nasty buried in Unity’s core engine that breaks on statics? (hint: I can almost guarantee it would be a bug somewhere in the Serialization system, which I detest, in case you hadn’t noticed already ;). Almost everything nasty in Unity’s architecture comes back to that monster!)

It has always seemed to work fine for me. But it’s scary: when will it stop working? How will you know? What Unity bugs are lurking here?

Self-constructing Scenes

Take all the game-logic that WOULD have handed data to your Scene as parameters (in any normal engine) … and embed that INSIDE the Scene, instead.

When the Scene loads, it runs this logic, and goes “Oh! I seem to be missing lots of core data. I’d better recreate it on the fly!”.

It’s a tortuous kind of logic, and it can be very confusing for new devs joining your team (and for yourself, if you take a break from the project and return later). But it has some significant benefits:

  • You can run a Scene in-game … OR … run it directly from Unity Editor, and it will behave (almost) identically. Testing + Debugging can be a lot quicker this way
  • Scenes remain self-contained. This is how Unity’s designers “intended” you to use Scenes (although it’s a really bad solution for Games). This has minor benefits with things all the way through to source-control and multi-user editing of your game: if you work this way, you’ll encounter slightly fewer of Unity’s core bugs.
  • In theory: more of the “Scene specific” code is located within the Scene (in Unity Editor). When your project gets large, in theory: this makes it easier to understand what’s going on, because the code + objects are local to each other. I’m not entirely convinced by this: with Unity’s poor arrangement of Code + Data … In practice, this locality might not be noticeable(the Unity Editor “hides” the locality, so you as developer get little benefit).

Use Prefabs

Well, I’ve met people who claim to do it this way – make your Scene in Editor, then convert everything to Prefabs, then load the Prefabs when Scene loads, passing in a different list of “names of prefabs to load”.

Oh man, please, no!

Firstly: Prefabs in Unity are badly broken for anything complex like this. They don’t work, you should be avoiding them as much as possible – except for the simplest of use cases (non-nested, simple, small prefabs).

Secondly: wow, this is tortuous. You’re abusing the “templated object system” to get around design-bugs in the “data layer” and “scene-loader”. It may be genius – but it’s the kind of genius that’s so unusual it’s prone to get broken in a future update to the engine, and Unity could legitimately say: “Wat? Why were you even doing that?”.

If it works for you, great! But for me, this was always a mind-**** too far. Possibly I’m missing out, and this is a much better route than I realised. Mostly, it’s the fact that Unity can’t handle Prefabs that puts me off. U5 allegedly fixes some prefab bugs, so … I’ll re-consider this at some point. However, the history of Prefabs in Unity is ugly, and I’ll be very cautious about trusting them.

Problems with these workarounds

Unity doesn’t support Dictionary/Hashtable

This is an enormous stinking problem (throughout Unity!).

It changes the balance of pros/cons in the above workarounds. You cannot simply add named parameters to your static / DontDestroyOnLoad / etc – because Unity will piss all over the programming language and delete/destroy/corrupt them.

(did I say I hate Unity’s broken-from-day-1 Serialization system yet? Yeah, well: I do)

In the end, I’ve found that the most attractive part of DontDestroyOnLoad is that you can use:

[csharp]
GameObject param1 = … ;
GameObject persistentObject = … ;

param1.transform.parent = persistentObject.transform;

//param1.tag = … oh, no – can’t do this. Unity’s tags are hardcoded (that is NOT what Tag means, guys!)

param1.name = "Parameter 1";
[/csharp]

You can then retrieve it later using something like:

[csharp]
GameObject persistentObject = … ;
GameObject param1 = persistentObject.Find( "Parameter 1" );
[/csharp]

(assuming you get a reference to the persistent object somehow … e.g. make it a Singleton)

I believe you can do this with static objects too (?) but … having a whole object graph hiding in a static member variable is getting into the realm of “this will probably expose bugs in Unity Serialization”. Best to avoid it: life is too short to debug Unity.

Ideal / Correct solutions

Data is Data

We come back to the recurring flaw in Unity that undermines so much of the engine: Unity refuses to acknowledge that “data” is “data”. It keeps insisting: “No, no! Data is code and objects and graphics and a physics object and … and … and … and …”.

(Let’s be clear: experienced programmers look down on Unity’s approach and are generally scathing. This isn’t fanboism – the Unity approach to programming largely died in the 1980s, for good reasons. It’s unhelpfully restrictive, and quickly proved very difficult to create complex programs. e.g. Video Games)

Ideally, we would store our game’s Data as data, and hand that data to the Scene when it loads. Unity blocks us from doing that. Everything we do to emulate this (e.g. embedding a JSON (de-)serializer) is a workaround for Unity’s road-blocks.

Current experiment

I’ve tried most of the above approaches with previous Unity projects, and none have worked out particularly well.

What to try this time?

Here’s my current needs:

  1. When the Scene loads, I need to inject the player-character. This is a complex object including rendering, physics, procedural meshes
  2. When the Scene loads, I need to separately configure some invisible state: the player’s characteristics (e.g. Hitpoints, Score) … which the level clones, and uses as visible state (hitpoints during the level change; hitpoints when you started the level is a “saved” value that doesn’t change)
  3. When the Scene loads, … I also have invisible state that is used to generate other state. e.g. information about how much of the level has been explored to date. Fog-of-war, etc.
  4. When the player finishes the level, I need to cherry-pick some data to save out to the main menu, and to save to disk
  5. When the player finishes the level, I need to store the differences between “before” and “after”, so that I can award various Badges/Achievements, and one-time rewards, etc. Also: so that I can render “this is what you achieved!” end-screen

Approach

I’ve given up: I’m writing my own mini-engine to workaround Unity’s awful scene-management. Looking back over my many Unity projects, I’ve lost many days, probably more than a week, on having to constantly re-implement this core feature because Unity’s version is so FUBAR.

It’s one of those bugs that makes me think Unity’s tech leadership never write games themselves; if they did, they’d refuse to put up with crud like this.

Short story:

  • Write a class-load hook (using C#) to jump in every time a Scene starts loading
  • Debug all the undocumented crap that Unity does when loading a Scene. This could take a long time.
  • Reverse engineer what parts of a Unity scene are “Valid” and when.
  • Write the kind of code that every commercial game engine (except Unity!) has for managing scene-loading
  • Wrap it up in an API
  • Never deal with this turd that is Unity’s scene system ever, ever again!

Useful features

“Diff” two GameObjects

This would be fantastically useful. It would make many of the workarounds above much easier

It also exists, built-in to Unity. We know this: the Serialization system relies upon it.

But they don’t allow us access (as far as I can tell: I’ve not seen a public API call / set of calls for this?)

“Clone” a GameObject, keeping a reference to the original

All Objects in an OOP language (C# included) keep track of the Class that created them.

If Unity would do the same thing with “clone object1 to create object2” … again, that would make many of the above approaches easier and less error-prone.

10 replies on “Some thoughts on loading Scenes in #unity3d”

Wow, harsh words :)

I am not very familiar with UE4, are there better events/callbacks to deal with scene loading, etc? I was also inquiring about similar things lately, but did not find any useful information (yet).

Let me say first that I *DO* agree on some of your points on how Unity deals with scenes, such as “run code when level is loaded”.

For some of the points you mentioned that are actually few workarounds:

1. Run code when scene is loaded – The MonoBeavhiour class can implement the OnLevelWasLoaded method (see: http://docs.unity3d.com/ScriptReference/MonoBehaviour.OnLevelWasLoaded.html)
This gets fired when a level (scene) is loaded.

Alternatively, you can designate special “scripts” and play with their execution order, making sure that they’re fired first, so you can place them in a scene and make sure they initialize what’s needed.

Regarding “scenes cannot hold data” – ScriptableObjects are the “formal” way of dealing with data container objects that are held by the scene (not attached to a GO).

I do agree that some of these mechanisms may seem counter-intuitive or not convenient to use.

Regarding DontDestroyOnLoad – I am not sure I understand how it can (or should) be replaced with statics – The engine keeps track of all game objects and manages them (destroys them when loading a new scene). Only objects that are marked by calling this method will survive this deletion. Having a static field won’t do any good in this case (remember – the C#/Mono layer is just a “thin” view over the actual game engine; having a static field or anything in the managed side does not really indicate you have a live game object underlying that reference. This can also be considered a bug BTW).

Just my 0.02$ :)

@Lior

“OnLevelWasLoaded” is interesting. But the docs suck.

When, precisely, does it get called?

Will it fix-up stuff before Start() is called on everything else in-scene? Awake? OnEnable?

What if objects NEED this data?

(my guess: it gets called last, and in practice is mostly useless)

@Lior

Re: statics. Nope, they work fine. Unity messes around with the C# language (FSCKING Unity Serialization is so EVIL!) – but at least this part they left alone.

You get the lovely situation where you have a live GameObject, that you can access via static references, but doesn’t exist anywhere in your Scene.

Muahahahahaahahahahaaaa!

I’ve been using Unity 4 for about 3 months and I left the project. Bugs are bugs (understendable thing), but overall architecture sucks so badly. And man, it’s so hard to collaborate (share code/assets on some git/svn repo). I’ve been ranting and commenting about diffing Prefabs and Scenes on my blog:
http://www.namekdev.net/2014/08/working-on-workflow-for-unity-and-git/

And here’s, additionally, why I don’t like Unity:
http://www.namekdev.net/2014/06/why-i-hate-unity3d-popularity/

@Adam

Regarding OnLevelWasLoaded – I am not sure when it gets called (with respect to Awake, Start, etc); that is undocumented. I opened a bug about this actually :)

Regarding statics – you are wrong on this. Keeping a static reference to a script will not prevent it from being destroyed (without using DontDestroyOnLoad).

This is the expected behaviour, and i’ve just verified this with an example. Maybe I did not correctly understand what you meant there… But a static reference to a script instance will remain in the C# sense of the word, but its underlying object will be gone. Attempting to access it will throw an exception.

No exception on unity 3.x through 4.6.3 (verified on latter before I posted).

Change in 5.x ?

I called loadlevel from update. Maybe calling it from start causes different behaviour?

Otherwise – this looks exactly the same as what works fine for me without crashes.

(Can’t test now, no laptop)

Wow, what an immature, uninformed rant.

First, let me disprove your claim that “experienced programmers look down on Unity’s approach and are generally scathing.” If you want to whip ’em out and compare, I’ve been a professional programmer for 28 years, have been using Unity for about five, and I love it. Its abstractions are at just the right level to let me create any kind of game or simulation, handling all the messy platform details without getting in my way.

I don’t see what your beef with scene loading is. It’s a scene graph hierarchy (look it up if unfamiliar). Loading a scene means loading a bunch of new objects into the hierarchy. You can choose to have Unity first delete most of the existing objects, except ones you’ve specifically exempted — and this is exactly what I almost always want it to do — or have it leave them, and just add a bunch of new objects. What more could you want in a scene loader?

And yes, of COURSE you can store data in static variables. Everybody does this. Nothing new or astounding here. C# is C#; there’s nothing mysterious or scary going on just because you’re using it in Unity.

And yes, game objects are special because they are data that hook directly into Unity’s scene graph and rendering system (all those messy platform-specific details I don’t want to screw around with). So yeah, there is a difference between a game object that’s live in the scene, and one that’s been destroyed. So what? If you don’t want it to be destroyed, then tell it DontDestroyOnLoad before you load the next scene. And then keep a reference to it in a static variable if that’s what you need to do. Again, nothing scary here.

Ignorance is curable, so if you really WANT to understand Unity and how to develop professional-quality games in it, then quit ranting and instead as calm, reasonable questions about the things you don’t understand. Many thousands of other devs have built successful games in it, and done so very efficiently. I’m sure you can learn how if you try.

TL:DR – “unity does things badly, ignores standard practice, but its OK because I can think of a reason why each of the things was done badly in the first place”

For each of them, you can find many examples of those things done much better. Most of this is so well known it appears in text books. Old textbooks, even.

If you want to see what I know about Unity, browse the other posts in this category. If you think there’s “no problem” with statics in Unity, you’ve got some nasty surprises in store: they can cause some horrendous problems in Unity, interacting with design flaws (and bugs) in the other core parts.

Comments are closed.