As a consultant on the Customer Success team, I often get asked “Have we built our asset bundles right?” or a variation of this question. My answer is always the same: It depends on the project. I then chat with customers about the specifics. Although this answer is accurate, it doesn’t provide any insights to aid future projects.
While a common dilemma, I struggle to find a more general answer to the question (despite our existing guidelines and best practices for addressables). Some users don’t know if bundles are correct to start with, and my time is often limited to examining projects for memory optimization. This means I can’t tell the intended use for every asset in a project. Additionally, as a project scales, it can become impossible for any one person to answer the same question for each and every asset.
Eventually, a realization hit me: The question of building asset bundles correctly limits the perspective to find a more general answer. Let me explain.
An asset bundle occupies memory to load, so if assets are bundled together with other assets that don’t get loaded and unloaded at the same time, you’re not using memory as optimally as you could. Therefore, asking if an asset is in the right bundle or if the bundles have the right assets in them is essentially the question of whether you’re loading the right assets at the right time. The answer to when specific assets should be loaded is what the intended use of that asset is, which is why the answer is usually, “It depends on the project.” In practice, I’ve found that learning the intended use of the asset reveals how the assets are loaded in memory, and how they should be bundled.
So, “intended use” is the key phrase: Who knows what the intended use for an asset is? How do you communicate that intent to everyone? And, finally, when should this happen?
In my opinion, there is one moment where the answer to this question is crystal clear: upon asset creation and modification. Whether it’s a specific texture for a character, a global lighting shader, or a tree mesh for use in multiple game levels, the creator knows its intended use and, thus, they are best suited to communicate that intent. Artists can communicate this intent by implementing a consistent file naming convention and grouping files with the same intended uses in the same folder.
Programmers and other team members can then use this information to decide when and if an asset should be bundled with other assets that will be loaded simultaneously. Because of this, the intended use of an asset must be made very clear – at a glance – for the duration of a project, and the file directory acts as the source of truth for all team members.
In this blog post, I will look at some best practices and common edge cases that will hopefully help you better structure future projects. First, let’s discuss some of the common folder structures and their issues.
From my experience, there are four types of folder structures: random, by asset type, by feature, and by purpose. Of these, the last one is the best by far, because it conveys the intent and is therefore most suitable for an optimal bundling strategy.
Random is not one I see often. This mostly happens with solo developers who might be unfamiliar with software development, this becomes untenable, for obvious reasons, as a project grows in size or complexity. A lack of structure, or a random structure, comes with many problems – assets are hard to find, and understanding the intended use is practically impossible.
Structuring by asset type is quite common as this is how many artists work on assets before importing them into the engine. If you know the asset type, finding its location is easy but everything else about it is obscured. Even with a great naming convention, it can be hard to tell if a character, environment, UI, or any combination of the three requires a specific shader, texture, mesh, etc. An appropriate file directory should not obscure information, but reveal it.
Folder structures by feature are rare, but seem sensible at first glance. Many companies divide into feature teams, so why not group the data similarly? Well, that is not how the game uses the data. In the past, I have seen examples, such as shaders and audio, that should be bundled together but because they are authored by different teams this truth becomes obscured.
A file directory with a clear naming convention driven by the assets’ purpose bypasses these issues. To illustrate this, I will use a fictitious game example called “Dinosaur Brawl.”
For the purposes of this example, Dinosaur Brawl is a third-person 3D action-adventure game where the player chooses one dinosaur to control across a vast open world with multiple biomes, fighting other dinosaurs and prehistoric creatures in an attempt to grow stronger and pass their genes to the next generation – all in a giant struggle to survive the coming Ice Age. The game is designed for mobile, and some of the data will be distributed as part of the original application. The rest will be downloaded from a CDN as needed.
We can devise a general folder structure for the whole project from the above synopsis. Since the player can select and fight other dinosaurs, it makes sense to create a folder per dinosaur that will hold all assets specific to that dinosaur: meshes, sound effects, textures, animations, particle effects, etc. I'll categorize these as Unique Assets.
Since it is an action game, it will have environments that are separate biomes. We should therefore create a single folder for each biome, or level, in the project: plains, deserts, tundra, swamps, volcanos, etc.
Of course, there will also be assets whose presence will be required for whole sections of the game. One such set is UI elements. You can think of these assets as Global Assets. Just like we made one folder for all Unique Assets, there should be a global folder that sits on top of the folder hierarchy. For example, a Global UI folder, a Global Dinosaur folder, a Global Environment folder, and so on. This way, all things shared by these game sections are stored in one place.
This category of assets is defined as being shared by some Unique Assets but not all. As such, they fit in neither the Global or Unique Asset categories. For Dinosaur Brawl, an example of this type of shared asset could be those that are present in all flying dinosaurs, such as particles, shaders, and sound effects required to give the sensation of soaring through the air.
What often happens is that these assets are put in the folder for the first dinosaur that needs them. Unfortunately, this does not accurately describe how they are meant to be used and therefore muddles their intent. In the worst-case scenario, assets get duplicated into each flying dinosaur bundle which is inefficient for memory, debugging, and application size.
The best solution is to create a new folder with a name that indicates its intended use, such as Flying Dinosaurs. The specifics of deciding the location are trickier; there is no standard. I prefer to put these in a subfolder at the same level as the global and unique dinosaur folders, but putting them with other Unique folders is just as suitable.
A typical edge case with this convention is when the project requirements change and an asset that was originally intended to be Unique becomes Shared. In our Dinosaur Brawl example, to save development time, the decision is made to use the Velociraptor prefab as the base for all other Raptors (such as the Utahraaptor, Dokataraptor, etc.).
What the developers don’t realize, though, is that when the Velociraptor prefab is added into a bundle, all the Velociraptor assets will be downloaded when all other raptors are loaded, increasing download times despite only the prefab being used.
This happened because the intent of the asset was changed and the folder structure no longer reflects that. When a change in intent happens, the asset(s) location and name should be updated to reflect this, in order to maintain consistency and accuracy in the system. This communicates to the team creating the bundles which assets should be in a “Shared Raptor” bundle and which should stay in the Unique Velociraptor model.
One of the most common and hard-to-fix edge cases is when an asset is unintentionally used in a way it was never intended. When this happens, it is usually an accident. For example, someone has a deadline to meet and they use an already existing asset in the project to finish the job quickly.
Take this scenario: An artist is adding a tutorial for an upcoming expansion of Dinosaur Brawl and finds a “gold glow” shader to accentuate when players can do a counterattack on elite enemies. What the artist doesn’t know is that this shader is end-game content against a massive T-Rex – it’s a unique boss that has a lot of assets bundled together. Now all of these assets will be downloaded during the tutorial, in a place where most of the assets are not used. This bundle is enormous, so the system that usually downloads minor assets like this in the middle of a game is stressed, causing anything from performance spikes to crashes because the asset can’t be downloaded quickly enough by players with poor connections.
The above is an extreme, but entirely realistic, example and one that I have seen happen in more than one project. This is why, in addition to folder structure, all assets should have a name that communicates their intent. If the shader were called gold_glow_trex_endgame, for example, it would be apparent what the intended use was. Then, upon debugging, it would be obvious that this asset should not be loaded during the tutorial.
If you are familiar with Addressables, you may know that Groups and Labels are used to group and label assets in much the same way I have suggested in the game example above – by using folders and sensible naming conventions. You may wonder, “Why bother with all of this if you can do this using Groups and Labels?”
My answer is that you should do both. As I explained at the beginning, as the number of assets in a project grows, it becomes harder and eventually impossible for one person to know how all assets are meant to be used. Knowing the Addressables Groups should match the folder structure can be used as a way to confirm that they are set up correctly.
I have seen many of our clients that use Asset Bundles without Addressables code complex systems as a solution to this problem. For instance, they will create and maintain a master list that is used to create the bundles, or check version control commits to compare changes in bundles, etc. My experience has been that these solutions are not cost-effective in the long term. It is yet another system to develop and maintain, which creates additional points of failure. As the project scales, they buckle under a myriad of exceptions and edge cases that long-lived projects naturally accrue. Worst of all, at a fundamental level, they fail because they don’t have a remedy for user error.
A well-structured folder system and file naming convention should result in a 1-to-1 match for asset bundles and addressables groups. Categorizing files into logical groupings and subfolders ensures that all team members interpret and locate files uniformly, mitigating potential misunderstandings and discrepancies as personnel change and project requirements evolve. Asset creators can facilitate easy access and navigation, sparing other team members from the time-consuming task of hunting down specific assets. A systemic approach saves valuable time and minimizes the likelihood of errors and oversights. Becoming an enduring reference point, a source of truth, easing the onboarding process for new team members and ensuring the project's viability over time.