Yes, I’ve been procrastinating by taking on another large restructuring project instead of working on Ultralight or the Rest of the Prologue. I could ship new content for you to play, or I could… yes… rebuild another system…
To be fair, today’s insanity is something that I shouldn’t really need to do, but because Unity refuse to fix the Resources.Load API, my hand has been forced. Oh yes, it’s time to divvy up all of Exon‘s contents into Asset Bundles and do the exact same thing as before, but in a much less convenient and intuitive way.
Kicking Ass(et Bundles)
Consider the situation: you have a game with lots of stuff in it. Units, items, the underlying models and textures that make them up. When a player loads a saved game, you have to bring an arbitrary set of those things off the hard drive. The level they’re in, the items they’re carrying, the wreckage of their dead enemies and the living guards still on patrol. That all sounds pretty reasonable, right?
The obvious way to load those things is by their relative file paths using the Resources API. You say to Unity, “give me Items\Equipment\FiredWeapons\ShardRifle.prefab” and lo! You get the shard rifle! Handily, it also loads all the junk that makes up the shard rifle — the textures, the materials, even the other prefabs like the projectiles it launches. It’s very convenient. It’s very straightforward.
But Unity, the company, hates the Resources API. They’ve spent the last five or six years telling everyone to stop using it. This is not because of any fundamental flaw, just because it has some inefficiencies baked into how it works under the bonnet. Inefficiencies that they refuse to fix. Instead, you’re supposed to use Asset Bundles, which are a good deal more complicated. Thanks, Unity.
The inefficiency is apparently that all of your loadable “Resources” are packed into one big archive at build time. That doesn’t sound so bad; I don’t really care what comes out the other side, just that it works. But in a blinding flash of “u wot m8?”, the engine apparently loads the entire contents of that archive into memory at start-up. Not just the list of all the files in it — the whole lot. So if your archive gets big, this can cause obvious an obvious delay on launch. You’d have thought they might just make it not do that, but, well, what do I know about running a billion dollarydoo company?
(And just in case you think that’s superstitious nonsense, I have just started playing a game called Tower of Time. I noticed it took suspiciously long between me double-clicking the shortcut and the actual game starting up, so I took a wee peek at the data files… Yep, it’s made in Unity and yep, they don’t seem to be using Asset Bundles. Oops.)
Fine, whatever. Asset Bundles work more sensibly, at the cost of you having to write a lot more scaffolding to end up right back at the same place as the Resources API. Since I’m building an offline singleplayer game, I don’t give a rat’s ass about streaming content from The Cloud at runtime, so I simply don’t need all the extra advantages that Asset Bundles advertise, nor their attendant complexities. I just want to be able to drag my arbitrary set of whatever items the player is wearing off disk, behind a loading screen, so they can pick up where they left off.
Needless to say, the first step is to… rearrange the entire structure of my project. Every loadable asset needs to be taken out of the Resources folder and assigned to an Asset Bundle instead. This is a long, boring, manual and… actually quite a difficult process.
I mean, the ultimate question is simply: how do you split things up? Exon has a broad mix of shared and unique assets. Some units, for example, only appear in the Gauntlet (the Prologue), while units like the exon mechs will show up all through the campaign (and, indeed, across independent campaigns). Should all units go in one bundle so that I can manage units in one convenient place? Or should I divvy them up into blocks by what levels contain them? What about bits of level geometry? What if I use a texture on one landscape in one campaign and so put it there, but years later on I reuse it somewhere else?
The thing is that Asset Bundles can depend on each other. That’s normal. What’s not so normal is that if you get this divvying-up process wrong, you can very easily end up with circular dependencies — and you can’t load an Asset Bundle without first loading all of its dependencies. There are no tools to help you prevent, let alone detect, that you have circular dependencies. The Asset Bundle Browser plugin, which offered some extra scaffolding to help you arrange things, is defunct; so while I got the source code and dropped it it, I’m on my own for actually tuning it up so it works better for me.
I think I’m through that process… Or at least, through it enough that I can stop caring. Realistically, I am probably overthinking this — Exon does not have large assets. It is very likely that you’ll be able to load the entire game into RAM, every level of every chapter and all that goes into each one, without feeling a the slightest whiff of a delay (my dream is that the entire game will ship at under 750MB and therefore fit on a CD). But taxonomies and heirarchies are important to me, and having them not be quite right is a constant source of unease.
Anyway. The next step was to replace the innards of my existing loader. This is another pain in the ass because you need to be able to switch between loading objects from the Asset Bundles — so you can test that logic actually works, and run it in the final build — and loading objects from the editor Asset Database — so you can have a quick turn-around time while actually working on the game. Yes, it takes Several Minutes to compile your asset bundles from the raw objects in your project, so you can’t just use bundles directly at all times, and of course Unity wouldn’t dare offer you some kind of mechanism to bridge the gap automatically or inuitively. No, you need to start using compiler directives and other eldritch nastiness to keep your game working on both sides of the fence. Both sides of a fence that, needless to say, Resources.Load straddled completely seamlessly. Fuck.
Alas, as with all such things, it’s probably better that I got it done sooner rather than later. As far as I can tell, I shouldn’t need to revisit this logic ever again.