Memory Management for Unity iOS
Developing a game intended for multiple end platforms can present some unique challenges to consider. For one, the differing pixel resolutions on apple devices over the years necessitate at least some different background images and layouts to accommodate the different aspect ratios. My initial technique to deal with this problem was to include a copy of each background for each device present in its scene, with its rendering switched off. However, this technique introduced another, more challenging hurdle related to multiple end platforms: memory management. Even with the render functionality disabled (which prevents draw calls and other related processes), the multiple inclusion of such large GameObjects was simply too much to handle on anything less sophisticated then an iPhone 4s. The iPod touch (4th gen) that I was using to test simply quit when loading the second scene. Apple devices range from 128 MB to 1GB of onboard ram, and the new operating systems have progressively eaten more and more of that space, so it can be a challenging problem to deal with if you want to develop on older devices.
My first solution was to break up the scenes entirely. I now have a duplicate scene for different apple tech levels. This actually simplified many of the performance optimizations that I would later have to make. For example, a black-and-white camera filter used for the tutorial levels runs fine on the 5 and above. However, it slows the phone down to a crawl on 4s. So, rather then split up the cases with scripting, it was simpler to create two largely identical scenes that differ where it counts. In this example, I removed the black/white filter from the scene intended for older devices and instead used a much more inexpensive technique. Likewise, I (regrettably) had to remove the cool shimmering water effect for these older devices. It was just too much of a performance hog.
I booted it up, and tried again. Unfortunately, the scene still refused to load. I hooked up a more intense profiler to track exactly what was happening with the memory, and discovered two things. First (and more immediately troubling) was that there appeared to be some not inconsequential memory leaks in my problem scenes. Dealing with this problem first, I went through and checked each and every apparent ‘Update’ loop in my scripts. If you are not aware, Unity has a number of standard methods that it incorporates into each of it’s mono-based scripts. Update is one of them, and it runs on every frame in each game object it is attached to. It’s useful for constant actions such as motion or AI. It also has the potential to be memory intensive, because it’s run so often in a scene. There were a couple of expensive methods launched in a some of update loops, so I re-architected where those might be triggered. Following the advice on Unity’s web page about memory management (http://docs.unity3d.com/Documentation/Manual/UnderstandingAutomaticMemoryManagement.html), I also removed frivolous assignment of game object variables. For example, it can be tempting to update the text of some UI element on every frame to insure snappy response. However, this constant reassignment creates a trickle of new memory garbage. Unity recommends only reassigning if the value actually changes, something that is simple to implement with a basic if conditional.
These changes were all positive developments, but unfortunately not enough to get the scene to load on an old device. I tried a couple of experiments to see what might be wrong here. I learned that I could load these problem scenes individually on the phone by turning off the other scenes. Using the profiler, I learned that the phone was having trouble recycling memory between scenes at an appropriate speed. By the time the next scene loaded, there wasn’t enough recycled memory allocated from the previous scene. So, I created a bare-bones transition scene in Unity to deal with this problem. Now, whenever the app has to transfer from scene to scene, it first loads an empty scene that contains only one script. This script explicitly calls for garbage collection, before loading the intended scene. The relevant Unity script command is ‘Resources.UnloadUnusedAssets()’. I also explicitly called ‘System.GC.Collect();’, which ensures that the heap is collected.
Finally, with this transition scene strategy, all of my scenes could load on these older devices. The memory leaks were plugged, and a more drastic solution (such as compressing the art, or redoing the art altogether at a lower quality) was not needed.