Unity Asset Management (Part 1)
This article will teach you on how Digital Asset Management works in any of your Unity Projects. With Unity 3D’s outstanding functions for game development, we will take you through optimizing the speed at which your Utility application is loaded. You will learn how to reduce the final size of your build.
To do this, first we will dive deep into how Unity handles assets internally, which includes topics such as:
- What are the differences between Assets and Unity Objects?
- How can I, as a developer, create my own Unity Objects?
- How does Unity serialize my Objects inside Assets?
- How does Unity handle a resource lifecycle in memory during run-time?
- Best practices to utilize Unity’s Resources folders.
Webinar Video: Unity Asset Management
Creating Your Own UnityEngine.Object: MonoBehaviour, ScriptableObjects and MonoScript
Unity provides two base Objects that allow you, with inheritance, to create your own custom Objects that Unity will serialize.
The first one is the MonoBehaviour. Your MonoBehaviour Object can be serialized within a Scene or Prefab. They can be attached to GameObjects as components.
Secondly, there is the ScriptableObject. Unlike MonoBehaviour, ScriptableObject cannot be serialized within a Scene or Prefab asset. They are often used as Data Storage types.
MonoScript is a reference to the script your custom object is using. That is, your custom Objects (MonoBehaviour and ScriptableObjects) contain a reference to a MonoScript. A MonoScript includes the information needed for you to locate the script that defines the Object. The basic information needed to locate a script includes assembly name, namespace and class name.
Every C# script in your project is compiled into Assembly-CSharp.dll. Except for the ones in plugin folders that are compiled into Assembly-CSharp-firstpass.dll. In Unity 2017.3, the possibility of you defining your own assemblies with assembly definition reference files was added. All your assemblies are loaded in application startup.
This MonoScript Object is the reason why an AssetBundle (or a Scene or a prefab) does not actually contain executable code in any of the MonoBehaviour Components in the AssetBundle, Scene or prefab. This allows different MonoBehaviours to refer to specific shared classes, even if the MonoBehaviours are in different AssetBundles.
Serializing the Unity Objects: File GUID and Local ID
All your Objects may have references to one or more different Objects. This is called Inter-Object References. The file GUID and local ID are tools used by Unity to serialize your references, as Reference Serialization.
It is a robust identification system, designed to abstract the asset path location, regardless of the platform that we are building our Project for.
GUID stands for Globally Unique Identifier. It is 16 bytes (128 bits) integer value, guaranteed to be unique. File GUID identifies a particular Asset file that you have stored on the disk. It is a pointer to the Asset’s specific location. Unity stores this identifier in the .meta file associated with the Asset.
Local ID uniquely helps you identify an Object within an Asset file. After all, an asset file may contain more than one Object, often of the same type.
Since GUID comparisons are slow at run-time. Unity maintains a cache of session-unique integers to identify an instance of a resource, called Instance ID.
This cache is initialized at application-startup. It maintains a mapping between an instance ID, the file GUID, the local ID and the instance of the resource in memory (if any exist). The cache is initialized with all the Objects that are immediately needed by your application, which are the objects referenced in your built-in Scenes and the Objects inside Resources folders.
Non-Native Asset Importers
Non-native Asset types must be imported into Unity. This is done via an asset importer. While these importers are usually invoked automatically, they are also exposed to scripts via the AssetImporter API. For example, the TextureImporter API provides access to the settings used when importing individual texture Assets, such as PNG files.
The result of the import process is one or more UnityEngine.Objects. These are visible in the Unity Editor as multiple sub-assets within the parent Asset, such as multiple sprites nested beneath a texture Asset that has been imported as a sprite atlas. Each of these Objects will share a File GUID as their source data is stored within the same Asset file. They will be distinguished within the imported texture Asset by a Local ID.
The import process converts source Assets into formats suitable for the target platform selected in the Unity Editor. The import process can include a number of heavyweight operations, such as texture compression. As this is often a time-consuming process, imported Asset are cached in the Library folder, eliminating the need to re-import Assets again on the next Editor launch.
Specifically, the results of the import process are stored in a folder named for the first two digits of the Asset’s File GUID. This folder is stored inside the Library/metadata/ folder. The individual Objects from the Asset are serialized into a single binary file that has a name identical to the Asset’s File GUID.
This process applies to all Assets, not just non-native Assets. Native assets do not require lengthy conversion processes or re-serialization.
Resource lifecycle in Memory
To manage an application’s memory footprint at run-time, it is important to understand how Unity handles automatic Object loading and unloading.
Objects are loaded automatically when an Instance ID mapped to an Object is dereferenced, and the referenced Object is not already loaded, of course it is necessary that the Object data source can be located with the File GUID + Local ID pair.
There are three main ways that Objects can be unloaded from memory:
- When Asset clean-up occurs, this is triggered automatically when a new Scene is loaded in a non-additive way. Or when Resources.UnloadUnusedAssets is invoked manually within a C# script. This process will only unload unreferenced Unity Objects.
- If you explicitly loaded an Object from a Resources folder, it can be unloaded invoking the Resources.UnloadAsset method.
- Objects sourced from AssetBundles are automatically and immediately unloaded when invoking the AssetBundle.Unload(true) API.
Resources Folders: Best Practices
Assets that are stored within any folder called Resources, inside the Assets folder of your Project, will be packaged inside the Unity Player when you make a build, similar to the Assets with Objects that are referenced within your built-in Scenes added in Build Settings.
These Objects can be loaded with the Resources API provided by Unity.
With that said, it is better to avoid using this system. Here are a couple of points to take into account when using the Resources folders:
- Using Resources makes it harder to micro-manage memory footprint at runtime.
- Improper use of these folders will dramatically increase application start-up time and build sizes.
- It degrades the Projects ability to deliver custom content to specific platforms and eliminates the possibility of incrementing content upgrades such as DLCs
When is it ok to use the Resources folder?
- For fast prototyping, where optimizing is not the main focus.
- For non-memory intensive assets that will be required throughout the lifetime of an application during run-time.
In the next part of these Webinar, we will discuss in-depth the inner workings of Unity’s Asset Bundles and Addressables.
Getting Your Project Underway
Now that you have learnt a lot in this article, it is time for you to start your own game development project with Unity.
Feel free to ask questions and contact Starloop Studios for free consultation.
Also, check out this article on the 7 benefits of outsourcing game development.