An important part of building most games is file loading. You need a way to get an asset, such as a model or texture, loaded so you can display it during gameplay. Unity offers multiple ways to load assets, but they all have major caveats. I am only going to cover loading assets in this post, as unloading assets is an entire separate topic, that is actually far more complicated than loading assets.
Resources.Load is the easiest to work with, with the least non-obvious problems. To use this function, your asset needs to have a folder labeled “Resources” in the path of the asset in your project. This means it does not play nice with dynamic assets, on iOS you can’t use this to load assets you download off the internet. The big caveat to using Resources.Load is it is a synchronous operation, and there is no way to use it to load assets asynchronously. Synchronous operation means your entire application might have a brief pause every time an asset is loaded. Depending on the asset size and what else your game is doing, the cost of loading a resource might be less than the free processor time you have in a frame. There is also a per-load overhead to Resources.Load, so packaging your assets together can improve overall load times. If you have many small textures you are loading with this, combining them into an atlas can give you a huge improvement in load times. The other quirk to be aware of with Resources.Load is the bug with asset dependencies, detailed in my previous blog post.
AssetBundles are more complicated to work with for loading assets than Resources, but are also more flexible. Asset bundles can be downloaded from a server, and cached locally. Asset bundles can be loaded from asynchronously. The tradeoffs to asset bundles are largely a pipeline and production problem, and not as much runtime problems. Unity does not provide an interface for creating and maintaining asset bundles, so you will either have to build your own, or find a solution on the asset store. Asset bundles also do not place nice with Unity in editor mode. When building an asset bundle solution, there are many cases you need to handle. Dependencies and reference counting are not tracked across asset bundles, and you can end up with an asset loaded multiple times, or not loading its dependency. While this can be easier to build a solution to manage for textures and models, it can get very complicated with prefabs, especially if you are running any sort of solution for handling nested prefabs. When using monobehaviors as components on game objects, versioning becomes a huge issue. If you have an asset bundle with one version of a monobehavior on a prefab within it, and then update that monobehavior without rebuilding the asset bundle, you will get unexpected behavior, as it tries to use the old data with the new script. This gets especially tricky if you have users downloading and caching asset bundles on their device, you will need to make sure your asset bundle solution can force updates if the user updates the binary, and make sure users with old binaries are not able to download and run new asset bundles.
A more sideways solution to asset loading can be making use of the scene loading functionality of Unity. Application has many functions for loading scenes. The most interesting one for asset loading is LoadLevelAdditiveAsync. This means you can build a solution for loading assets into your game by populating scenes with content. This is also a way to work around nested prefab problems: If you keep your prefabs one layer deep, then you can use a scene as a second and final layer. An example of this would be HUD implementation: If you have prefabs for buttons and other UI elements, then you can create a HUD scene, and place these prefabs within it. Then, during gameplay, when you want to load the hud, you can call Application.LoadLevelAdditiveAsync(“HUD”), and have your HUD loaded in the background while other things are going on, maybe a level intro flying camera. I recommend setting most game objects in a scene to be loaded asynchronously as disabled, to parent everything to a root game object you can easily search, so you can manage each loaded scene through that. LoadLevelAdditiveAsync does return an AsyncOperation so you can track when it finishes. The downsides to the scene style of resource loading generally fall on games that don’t have a strict gameplay pipeline, with known resource loads. If a game has a heavy amount of customization, either through base building, or custom character parts, building a scene per character part is going to create some strange workflow problems.
Another option for loading assets is the WWW class. This class is mainly intended for pulling files down off of a network, and can be used with AssetBundles to build a more robust asset loading solution. The WWW class can be pointed at your own project locally, and any assets with a folder named “StreamingAssets” in their hierarchy are accessible through Application.streamingAssetsPath. However, if you want to avoid asset bundles, maybe your asset loading requirements are fairly simple, you can use the WWW class to load data. The asset type this class plays most nicely with its textures, and even as a WWW.texture accessor. There are two big tradeoffs to using the WWW class. You lose access to the texture import settings, so if you have complicated rules for your textures, you will need logic in your code to apply them to the Texture2D after it is loaded. The other tradeoff is, the WWW class only supports loading JPG files and PNG files, it does not support TIFs. Some artists prefer to use TIFs as source textures, as it contains more data than PNGs and JPGs. For non-textures, you will have to access your asset data through WWW.text or WWW.bytes and then convert that to your desired asset type, which is unrealistic for many asset types, such as models and animations. Be aware that anything in Application.streamingAssetsPath the user can gain access to through file exploring tools for Android and iOS, so having a text file in there with “PlayerMoney” means some users will open that file and give themselves some bonus money.
The final option for loading is to make use of existing C# file loading behaviors. These I would recommend staying away from for all asset types except for text files, or binary files you need to process yourself, such as a database. Most of the problems with this loading option are identical to the WWW method above, especially because you will probably be putting your assets into the streamingAssetsPath so your file loading function can see it.
So with all of these solutions for loading assets, what do I recommend? Building a composite solution, that makes use of different loading functions, depending on asset type, location, and platform. Building a smart layer that sits between your assets and game code can make it so you do not need to rebuild asset bundles every time you want to test an asset change in Unity editor, by falling back to loading through Resources when in editor. It can also give you a point for asset fallbacks, if you hit the point where your asset might be anywhere between a server, compressed on disk in a cache, uncompressed on disk in a cache, or in memory already.