When creating a game, a solid content loading
strategy is a key part of the development process. Starting the game fast, streaming assets
in and out of memory, all of this has a fundamental role in providing
the player with a smooth playing experience. In this 4th devlog, we’re going to take a look at the techniques we
employed to package the content of “Chop Chop”, an open-source action-adventure game demo
we are making as part of the Open Projects initiative. As usual, the whole game is available for
you to explore on the Github repository, and you will find the link in
the description of this video. Before we talk about the way we
packaged content for Chop Chop, let’s take a small step back to understand
the problem we were trying to solve. In Unity you structure a game
by breaking it down into scenes. When not using AssetBundles, scenes are then listed one by one in the Build Settings which defines what ends up being packed into the build. If a Prefab, a texture, or a material is referenced
into one of those scenes, it will end up in the game build. We call these “direct references”. This doesn’t give much control over memory since when we load a scene,
we also load all of its direct references. In addition to that, all assets that sit in
the folders named Resources are also included in one big compressed file included in the build. Even though we decide when to load these assets,
we have limited control on when to unload them. In addition to this, this file cannot be bigger than 4GB. Moreover, there can be a hiccup at the beginning of the game when the information about this big archive is first loaded. This might be imperceptible on a PC, but on a console it can make a difference between succeeding
or being rejected during a certification process. On mobile, it could mean a frustrated player
who uninstalls the game - and a bad review! For all these reasons, we decided to use
AssetBundles to pack our content and load it at runtime, only when needed. On top of that, we use the Addressable Assets package to easily
configure, pack, build, and load these bundles. So the first step is defining the content strategy. In Chop Chop, the game begins with a main menu scene. From there, we can reach the gameplay phase which happens in scenes that we call Locations, always loaded one at a time. In addition to these, there are special scenes
that we use for global managers, in charge of running cross-scene operations like saving and loading progress, playing music, and loading other scenes. So we decided that every scene mentioned so
far is going to be packed into bundles. This way, we ensure that the initial loading of
the game is very short. To do this, we simply have to mark scenes and their assets
as Addressable and make sure they are in the right group. Groups are a functionality of Addressables that
allows to divide assets into logical units, to be then packed into
AssetBundles when building content. One important note: assets in the same Group can be built into multiple AssetBundles by choosing Pack Separately, so that they are as independent as possible from each other. However, Unity always requires at least one
scene in the build, which is designated as the entry point of the whole game. For this reason, we introduced a new,
almost-empty scene called Initialisation which, as you can notice, is the only scene in the Build Settings. Initialisation is a one-use scene. Its only function is to load another scene called PersistentManagers, which is in charge of loading other scenes
such as the main menu or Locations. At this point, Initialisation can unload itself
and PersistentManagers takes over. From here, we’re in AssetBundle territory. Every new scene loading request will automatically
find and load the correct AssetBundle that contains the scene and all of its dependencies. To reference these scenes we use
a tool coming from the Addressable Assets package called “AssetReference”. AssetReference is a special type of field,
which looks a lot like a direct reference, but it’s more like… the promise of a reference. This way we can have a reference to any asset
which can be fulfilled at a later stage, when we actually need that asset. You can think of AssetReference fields as weak links
that are not immediately connected. We have control over when to connect them, and when we need to, the Addressable system will find all of the necessary dependencies
and load them too - if they’re not already loaded. It’s quite convenient. When you remove these links by unloading references,
the asset and its dependencies can be unloaded, freeing memory up. Now let’s talk about an important topic
that can be confusing at first: asset duplication. While dividing the content into bundles, we ran into a potential issue
- as noted by some of the project’s contributors. Our game heavily relies on some ScriptableObjects
to carry messages across-scenes. If we packed our content the wrong way,
these ScriptableObjects would get duplicated in two or more different bundles. If that happened the messages wouldn’t travel anymore, because at runtime the sender and the receiver would be
effectively communicating over different objects in memory! What happens here is that these ScriptableObjects
are being packed in the build implicitly, meaning that while not initially marked as Addressables,
they were pulled into bundles due to them being a dependency. This can cause duplication if multiple objects
in different bundles are referencing these ScriptableObjects. So the solution in this case was to just
explicitly declare these assets as Addressables. The Addressable system will track the dependencies, realise that the needed assets are already in a bundle,
and not pack them again. This avoids the duplication,
and solves the problem with our event system. Just a side note. Duplication might be wanted in some instances. Consider the case of two characters
using the same gradient texture, and let’s assume these two characters
never appear in the game at the same time. In this case, it might be OK to pack the texture twice
in the same bundles as the characters, so that they are available without having
to load an additional, shared, AssetBundle. So ultimately, look at the structure and gameplay
of your game, and design the packing and loading strategy
that makes sense for you. As for us, the next step is to better structure the Addressable Groups in a way that allows us to load a level,
and its dependencies, without pulling in unnecessary assets. This might be something we will do closer to the end of the project, since - luckily - the Addressables system doesn’t care
where an asset is located, so we can move assets between Groups later on
and we won’t need to change anything in our code. The development of Chop Chop continues,
and it’s not too late to join us on this journey. To get up to speed, check out the forums
and the wiki on the Github repository, linked below. Thanks for watching, and we hope you are enjoying
following the development of Open Projects!