Blog: The memory optimization struggle in Unity
The following blog post, unless otherwise noted, was written by a member of Gamasutra’s community.The thoughts and opinions expressed are those of the writer and not Gamasutra or its parent company.
Ok, up until now we paid a lot of attention to the CPU stress in the development of Empires in Ruins, both in terms of Update and Fixed Update loops, but also a lot in terms of keeping the drawcalls and fillrate low. And what about RAM? Well, here we need to declare a Mea Culpa; about RAM we always thought “Even not-so-new computers have far more than enough RAM to run a 2D game”. I would probably deserve a lash for each character of this sentence.
Meanwhile we added a higher zoom factor in battle, then we raised the resolution of the spritesheets, then we increased the number of frames per animation, then we had to reference all the tower and units prefabs from the inspector instead than pooling them up from Resources at game load (because of the 4 GB limit of the Resources file in build, the one that nobody ever told you about), etc etc. And then one day we checked the profiler, and HOLY CRAP oh! 3 GB of Ram in use constantly during the battle map and we still don’t even have all the enemy units in!
I mean, 3 GB is still “humanly acceptable” in a sense, but:
We still needed to add more Units, including the three largest ones, the vehicles (Tank, Paddleboat and Zeppelin), that can’t have tiny spritesheets if we wanna have them look sharp and clean.
It’s a 2D game. And mind this: a developer will often understand why a ultraHD 2d game with realistic smooth animations and a lot of zoom does use a lot of RAM. A generic gamer quite often will not, and it might easily be that flames will raise on your store pages.
It is quite pointless and dumb to have all the units and towers referenced in RAM even if you don’t need them. Very pointless and very dumb.
A mistake we surely made was, specially for the Artillery towers, that can rotate 360 degrees (discretized in 45 positions) and reload/shoot in 8 directions, to have in some of them (Cannon and Bombard being the main culprits) the pre-rendered VFX of the shooting embedded in the spritesheets. That caused, specially for the Bombard, the need for 4k or larger sheets (we got started using 8k ones for full resolution but that was just madness). Now if you do some math, a 4k sheet is approx 21 Mb in Ram, and a 2k sheet is 10 Mb, so 21×8+21×8+21 = 357. So here you go, one single large artillery tower reference (not per instance, mind), uses 357Mb of Ram. We have 3 large ones and 3 smaller ones that are probably around 200 Mb each. That is a crazy 1600 Mb of Ram used for artilleries that you might not even be using (they are powerful and expensive, so i’ve rarely seen players having more than 1-2 of them in the workbench map). The first step was then to strip those sheets of all the VFX, and minimize the size of the spritesheets. And that was done outside of Unity, using Blender, Texture Packer, Houdini and Gimp. 2k spritesheets, if well packed and made, are absolutely fine enough for a good quality and good res animation.
So, this whole premise took me to investigate what one could do instead. Googling took me to one of those mysterious Best practices pages of the Unity documentation you wished you had read and found years ago. I loved the intro even though it was a “useless” warning at this point as we already ditched the use of Resources: “Best practices for the Resources system: Don’t use it” – Ok :DThrough googling, searching and asking around, I found two possible alternatives to pick: additive scenes loading (aka loading, on top of the main scene, small scenes that have been prepped with no camera and just some prefab referencing) or the always heard about and always feared (let me tell you – without a good reason) Asset Bundles.
The first couple of tests showed right away that time-wise, there was no comparison between the two approaches, Bundles are much much faster. To load an additive scene with a single cube on top of an empty scene took approx 50 ms, while loading an asset bundle with a cube into the same scene took approx 14 ms. Tests made with more complex assets gave more or less the same results, and so it was one more choice taken: Asset Bundles seemed to be the way.
To summarize the possible approaches to use an asset in a scene :
Have the object already in scene from the editor at building time
Resources (but we saw above why it might not be the best idea)
Referenced in a public variable of an executed script (but in this case the whole asset will be always loaded in the scene, no matter if needed or not)
Loaded as additive scene (but again, we saw above that for multiple small bundles it might not be a convenient solution because of the loading time)
How do AssetBundles work? Well, these pages https://unity3d.com/learn/tutorials/topics/best-practices/guide-assetbundles-and-resources will tell you mostly everything, but let us give our short version as well. Rough as hell but fairly straight to the point.An AssetBundle is a pre-packed set of assets with a header to handle the collection. It can be streamed into the game from network or from files folder and they need to be built for a specific deployement platform.
The network use is easy to figure out when you think about web or mobile applications (an example, years ago i wanted to make a unity game for Kongregate, but Kong only accepted a limited size executable, so the way to add assets and contents was Assets Bundles or in case of mobile apps, it might allow you to stay below the mobile network download threshold).
We said what they are, we mentioned what alternative approaches they have, now time to explain exactly how we use the AssetBundles for towers management in game.
Each archery, magic or scouting tower has two spritesheets and three prefabs (Built, Damaged, Destroyed). The base towers have also the UnderConstruction. Each tower is packed in a bundle with the tower name (trimmed whitespaces, all lower case). Sprites occupy approx 3.5 MB all together, with the packed assetbundle being around 7-900 KB.
Artillery towers do have a building part that is identical in size and type to the ones above, then comes the large one, 8 attack sheets, 8 reload sheets and 1 rotation sheet, for a total of approx 50 to 80 Mb according to the artillery, that compressed in the AssetBundle become 10 to 15 Mb. The artilleries are split into bundles. One small one with the building only, loaded at start time of the map if the tower is unlocked (because of research), and one large one, only loaded whenever the player gives the building command for such a tower (and no other towers of that type are in map). After the command there is the walking time of the Builder + the building time of the towers (6 to 10 seconds) before the prefab is needed. The loading takes approximately 1 to 2 seconds, so that we are always on the safe side. Unused bundles are unloaded and kept so until needed again. Given the nature of the game and battle maps, it is fairly improbable to have more than 2-3 different types of artilleries loaded at the same time in map (they are costly and slow to build).
How much memory did this save? Well, according to a map unlocked towers, up to 460 MB. It might not seem “much” give modern hardware, but if you sum that up with the amount we are gonna spare with converting the enemy units into AssetBundle (estimated memory savings there are up to 1-1.5 GB compared to the actual manager managed pooling approch with all the units prefabs referenced on start.), but for example, in a 32 bit environment, such a difference that would be far more than enough to make a difference between being able to play the game or not at all.
CONCLUSION AND A FEW USEFUL LINKS
As mentioned before, making a 2D game, is a risky choice when talking about minimum required hardware. Keeping drawcalls and memory consumption under control, has become nowadays fairly difficult, with most of the optimization and R&D focus going towards 3D. As developers, we know the problems that HD 2D might cause and why it might have very high hardware requirement. Keep in mind that the player does not, and he might easily get into the state of thinking “if a 2D game is using that many resourced, definitely it’s because it’s badly programmed”. And we all know where this might lead your game and brand reputation.
NOTE, this is a bug encountered on shaders used through Asset Bundles https://support.unity3d.com/hc/en-us/articles/208380753-Shaders-are-pink-when-loaded-from-an-AssetBundle and here is a useful link i found almost by mistake about Asset Bundles troubleshooting https://support.unity3d.com/hc/en-us/sections/201743566-AssetBundlesThen ofc go to the manual and read it all through and through before starting https://docs.unity3d.com/Manual/AssetBundlesIntro.htmlThe full resources and assetbundles management best practices pages https://unity3d.com/learn/tutorials/topics/best-practices/guide-assetbundles-and-resources
Last reminder : Make sure you check the Dependencies of the AssetBundles and store them in a specifically made asset to avoid duplication. Check the bug on shaders and materials before you go mad about it.
And if you have any question feel free to ask. I might not know the answer, but if I do, I will be glad to help! And as usual, prove me wrong so that I can improve further!
Emiliano, H&R Lead Dev
Hammer&RavensEiR on TwitterEiR on Facebook
To read the full article visit Gamasutra
Blog: The memory optimization struggle in Unity