This article is part of a series. If you’d like to skip ahead, you can go to the master post that links to them all.
Last time we upgraded a game to the Unity3d 5 asset bundle system, and dealt with handling shared data files. This time we will explore an unexpected use for asset bundles – automatic texture scaling for different classes of device.
On this project, the artists really outdid themselves – very high quality, high resolution artwork. Unfortunately, rather too high for most of the devices we wanted to play on! They had done a lot of work to balance each image size against its detail and how large it would be used in the game. Notice how we had to make the high detailed icon relatively large compared to the core game sprite, all because the icon had more detail.
But there is a completely different way to approach the problem. A much simpler one, in fact. First you decide what device you consider the largest pixel size you want to support. In today’s market, this is typically a tablet device of some flavor. Art then designs everything at that consistent scale that looks good on that device. Now the art looks like the following image. Notice how they are all scaled based only on their visual importance in the game, with no wasted pixels. The car, a main focus of the game, is now large compared to the button icon that is only occasionally used.
Okay, but how do we support the rest of the phones and other devices that are smaller or less powerful? Trying to run those full size textures on just a different tablet is enough to drag the framerate down to unplayable. Why is that? It comes back to how mobile devices work. On a desktop machine, the video card typically has a dedicated memory space for textures. They are uploaded once (when things go right) and they never touch the main computer memory again. Every frame that is drawn uses that custom video memory. In mobile devices, there is no such dedicated memory. Every frame that is drawn pulls every texture through the same memory system the main processor is using. Only so much data can be pushed to the renderer in a given amount of time, and bigger textures means more data needed. Even if the framerate can be maintained, it takes precious battery power to transfer the data. Mipmaps can help reduce how much of the texture is used at a time, but you still need the entire texture somewhere in memory, taking up room that other applications use and need.
So back to the texture sizing. How do we handle those devices that don’t need the extra pixels? Simple – scale the textures. It’s a fairly simple thing to ask Unity to scale the textures for you, using the texture settings of the “maximum texture size”. And you can save those changes into asset bundles! Then the client once again can get the correctly sized textures by simply deciding which bundle directory to load. Easy peasy.
Things we learned while creating an automated system to do those bundle builds for us:
- This kind of texture scaling and processing works just fine with both the pre-Unity 5 and Unity 5 asset bundle system.
- Make sure you edit texture settings before any BuildPipeline.PushAssetDependencies. You can safely do so once you’ve balanced all the Push with Pops, however. Like if you are doing one build to generate multiple device asset sets.
- Be sure to call AssetDatabase.StartAssetEditing/Stop only once before starting each build process. Unity does a bunch of extra processing even if you nest them. Wrap it in your own reference counting if you must.
- It works pretty good to use the Default texture settings as the “master” value, setting the current platform’s custom settings to whatever you calculate for that bundle.
- Remember that the default ‘max size’ of a texture is nothing like the actual size of the texture. There are ways of asking Unity for the true size of a texture to get the accurate default size.
- Expect to scale the textures in powers of two, simplifies a lot of the task.
Next week we will wrap up our series by demystifying the unload process!