If your primary target is a higher end machine, like the current iPad or a PC, it might seem like you have all the memory in the world to work with. Unfortunately, on lower end devices like the iPod Touch, this is not true. The first thing to be aware of when managing memory on any modern device is you have no hard limits. The device memory is shared between your app, the OS, and any apps running in the background. On iOS, this can include mapping software like Google Maps, and music playback software like Pandora. Even though the 3rd and 4th generation iPod Touch devices have 256 MB of total memory, iOS might start sending low memory warnings at the point your app is using only 80 to 100 MB of total memory.
Before you begin optimizing memory usage, you will need to know about tracking memory usage. To get an idea of the total memory usage of your app, I recommend using the Apple provided Instruments, with the Memory Monitor instrument. Memory Monitor tells you how much memory each app running on the device is using. Opening the profiler in XCode is simple, but easy to miss. Hold down the “Run” button in the top left corner of XCode, and you will see an option to run the profiler. Choose this, and you will be given a list of instrument sets to run your game with. Choose any of them, and once it starts profiler, use the interface to add a new instrument, you want the Memory Monitor.
Now you know the total memory your app is using, but you are probably wondering what is using all of the memory in your app. Generally, app memory falls into a few categories: Assets, monoheap, and plugins.
Monoheap is the memory used by your code. When you max out your monoheap, it will allocate more memory to expand it. Monoheap will never reduce itself in size, so your goal with monoheap management is peak usage. There are a few ways to track the monoheap, Profiler.GetMonoHeapSize, or the internal profiler, see here for more information http://docs.unity3d.com/Documentation/Manual/iphone-InternalProfiler.html. If you decide that you want to start wrangling memory back from monoheap, look into standard methods for reducing memory usage by code. Generally when the monoheap is using more memory than you want, you probably have a large array or other data structure held in memory by code.
Plugins are a tricky category for memory management. Often times you won’t have as much access to make changes to plugins as you will with other things draining your memory. Plugin memory can be easiest managed by pulling out plugins you might not need. It’s easy to start filling your game with every stat tracking or ad service plugin you can find, but each of these will have a cost on your game.
Assets are the most management place to keep your memory where you want it. The easiest solution is to reduce asset quality on the lower end devices, through the quality settings for your project, and loading lower quality assets on lower end devices. While you will have your game running on target at this point, you probably want to be able to keep memory usage low on these devices without reducing the visual quality of your game.
Textures can be a large memory sink. The easiest solution here is to just reduce the image resolution. Other options include reusing textures where you can, and atlasing textures so you can make them square and power of two to work with compression. Look for textures marked with the isReadable flag, these textures will use twice the memory to keep one version available for the GPU, and one for you to edit. Also look for non power of two textures. The way Unity handles these textures is by keeping two copies in memory, one copy is the original texture, and the second is a version of the texture expanded out to power of two dimensions. You can track most textures in use by using GameObject.FindObjectsOfType(Texture2D). I would recommend building a memory profiling menu that calls this, and have it look for duplicate and non power of two textures, pointing those out explicitly. It is really easy to accidently have multiples of the same texture loaded due to the lack of universal ref counting for assets in Unity. Lightmaps, fonts, and other textures can accidentally end up loaded twice by accident.
Sound effects can be another large memory sink. Audio quality plays a huge role in the memory size of a sound. If you want to keep the quality cranked up for your main platforms, you might want to populate your game with lower quality sounds for lower end devices. If your game is full of alternate sound effects for different actions to minimize audio repetition, setting it up so your lower end devices only play a smaller selection of these random sounds can also improve your audio memory usage.
Models are the last major asset type to use memory. Models can be broken into two categories for memory usage: Environment geometry and animations. Generally character models, effects, and objects are going to use less memory than these two major memory hogs. To track memory usage, I recommend you use the GameObject.Find to search for loaded meshes, and to build a similar system to what I suggested for textures. Have your tool search for duplicate meshes loaded, and sort by size so you can focus on the biggest problems first.
With environment geometry, you run into memory issues due to vertex count mixed with the per-vertex data. Environment geometry is often a very large number of vertices, and reducing this vertex count can reduce the memory usage of your geometry. The other way to reducing memory usage of your environment geometry is to reduce the data stored per vertex. Chances are you are making using of lightmapping on your environment geometry, which needs normals to generate lighting data. Once you have your lightmaps, you should be able to disable normals on your environment geometry, getting you back that Vector3 worth of data per vertex. If you aren’t using lightmaps, disabling the secondary UVs on your environment geometry can gain you back some memory. These settings can all be tweaked through the asset importer.
Animation memory is going to be a product of animation length, and number of bones. You also want to take into account the total number of animations. One way to manage animation memory usage is through the animation compression settings. The tradeoff to these settings can be visual fidelity, but shorter animations that are built to not have a lot of travel from the origin are easier to compress and regain memory.
You are best off managing memory through vigilance, and not letting it reach dangerous peaks. It’s easy to spend a lot of your development time testing and working on higher end devices, or in-editor, and lose track of memory consumption on lower end devices. You want to test these devices often, it’s far easier to make small tweaks to keep your memory at your goals than to try and regain large blocks of memory later.