Home Unity Tutorials

Introduction to Modding Unity Games With Addressables

Use Unity Addressables to make it easy to let users create mods, enhancing the user experience and expressing their creativity through your game.

5/5 4 Ratings

Version

  • C# 7.3, Unity 2020.1, Unity

Sometimes, the best content within a game isn’t made by the developers, but by the game’s amazing community. This is possible thanks to mods. A mod is a piece of user-generated content that changes the way a game looks, behaves or sounds, allowing players to express their creativity through your game.

Traditionally, structuring games to support mods has been difficult. Fortunately, Unity is working on a system to address this called the Addressable Asset System, also known as Addressables.

In this tutorial, you’ll learn how to make a simple moddable FPS game that changes its functionality and graphics depending on the mod a user selects. Throughout this process, you’ll get hands-on experience with:

  • Loading and unloading content through Addressables.
  • Setting up a game for modding.
  • Creating a mod that changes models, textures, sounds and functionality.
Note: This tutorial assumes that you’re familiar with Unity development and C#. If you’re new to Unity development, check out our Getting Started in Unity tutorial. If you’re new to Addressables and the process of how to set up and configure them, read Introduction to Addressables.

You’ll need a copy of Unity 2020.1 or newer on your machine to follow along with this tutorial.

Getting Started

Once you’ve installed Unity, download the sample project by clicking the Download Materials button at the top or bottom of this tutorial. Note that there are two starter (and final) projects:

  • Game Starter: You’ll create the moddable game in this project.
  • Content Starter: Here, you’ll create the mods for that game.

Once downloaded, extract the files and open the Game Starter project in Unity. When the project loads, open the RW folder using the Project window and take a look at the folder structure:

List of folders in RW

Here’s what each folder contains:

  • Animations: Basic animations for the weapons.
  • Materials: Simple materials for the prefabs.
  • Models: Base models for the default content.
  • Prefabs: Particle effects for the Player, Target, Bullet and Impact.
  • Scenes: An empty scene.
  • Scripts: All the scripts in the game.
  • SFX: Sound effects for the weapon and impact.
  • Sprites: Crosshair sprites.

And here are the things you’ll allow modders to change in your game:

  • Target model
  • Bullet model
  • Bullet sound effects
  • Gun model
  • Movement speed
  • Bullet speed

So, how do you achieve this with the Addressables system? Before you start coding, take a moment to understand what Addressables do.

Understanding Addressables

So far, letting users mod their games has been difficult for developers. You usually have to tackle these issues:

  • Loading dependencies from user-generated content.
  • Unloading original game content and replacing it with the correct user-generated content.
  • Loading and unloading assets asynchronously.
  • Allowing players to easily generate content for your game.

Addressables use the existing AssetBundles system to provide an easy way to load assets asynchronously using an address. This lets developers abstract the process of retrieving the assets.

You can use AssetBundles directly, but they don’t address any of these problems. :]
Addressables provide an entire content management pipeline, while AssetBundles are simply a way to package content.

Advantages of Addressables

The first thing you should know is that the Addressables system does not support modding out of the box. Rather, they’re a concept that allows you to build a layer of abstraction between the game development and the knowledge of where assets are.

Normally, if you wanted to spawn a bullet, you would write something similar to this:


public GameObject bulletPrefab;

public void Shoot() 
{
    GameObject.Instantiate(bulletPrefab);
}

The caveat of this method is that you have to assign a prefab that exists within the project to bulletPrefab. There are two limitations to this:

  • The project size will become very big.
  • Unity will load every prefab stored in a reference into memory, even if the scene doesn’t use it.

The Addressables system allows you to do something like this, instead:


public string bulletAddress;

public void Shoot() 
{
    Addressables.Instantiate(bulletAddress, instantiationParameters);
}

Now, as long as you know the bulletAddress, the Addressables system will handle retrieving the prefab from its location, then load it into memory asynchronously.

The benefit is that Unity can locate the asset from anywhere. In this case, the asset can either be in memory as part of the original game or in an external mod file.

For testing purposes, Addressables will cache assets. That means that load times are minimal when you emulate a production environment during playtesting, thus reducing development time.

Looking Under Addressables’ Hood

Before you use this system, you need to know how it works. When you build a mod using Addressables, Unity will generate three types of files:

  • Catalog File: This file acts as a dictionary, mapping the address of an asset to its actual location, whether it be on a server or local.
  • Hash File: Determines if a new catalog is available.
  • Asset Bundles: The actual assets bundled using Unity’s Asset Bundles. Addressables separate the Asset Bundles based on the groups you assign to different assets.

Each mod will contain its own version of these three files with different content. To load content from different mods, you have to download its respective catalog file. Once you have the catalog file, you know exactly where all the assets are and you can load them into memory when you need them.

For a player to create a mod, all they need is a base Unity project containing the prefabs in the original game. With this, players can modify the prefabs to their liking without modifying the address of the prefab.

Exploring the Project

Open the Content Starter project, open the RW folder in the Project window and note the folder structure. The folders contain the same type of content from the Game Starter project, with quite a few modifications.

Open RW/Prefabs and note the prefabs. The modder has changed the bullet into a fish and the target into Randy Savage.

Image of the prefabs

It would be wrong to have a tutorial about modding without paying tribute to the legendary Randy Savage mod in Skyrim. And while you’re at it, why not throw some fish at him? You’ll also appreciate the sound effects. :]

Randy Savage mod in Skyrim

Umm, just be warned, this might be the weirdest tutorial you’ve followed. :]

Next, open the Bullet prefab and note the properties of the Bullet component.

Properties of the Bullet component

Note that Addressables support the ability to save the serializable attributes of a component. The limitation, however, is that you can’t change the functionality within a script. So, for the fish to act as a bullet, you’ll need to reduce the Speed to 25 to be more in line with what you’d expect from a fish flying through the sky. For even more realism, you’ll change the impact sound to a water splash effect.

The more variables you make accessible in the scripts, the more power you give modders beyond just changing how objects look. If you want, you can make further modifications to the prefabs before you move on.

For example, you can give these prefabs different models, shapes, sound effects and component values. Addressables will manage all the prefabs’ dependencies, so you can change anything you like.

Understanding the Addressables’ Settings

This project comes with Addressables already installed and configured for you, so you can jump ahead to look at the setup.

To view the settings of the Addressables system, select the AddressableAssetSettings file in Assets/AddressableAssetsData/, then look at the Inspector.

Inspector view of Addressable Asset Settings

There are two important settings:

  1. Profile In Use: The profile of the Addressables system changes the location of the Build Path and Load Path. This dictates where Unity will export the catalog file and asset bundles. View the actual profile settings by selecting Manage Profiles.
  2. Build Remote Catalog: The catalog file tells you where the assets are, so you need to know its location to access the mod. Therefore, you need to tell Addressables to build the catalog along with the assets.

So what are Build Path and Load Path?

  • LocalBuildPath: [UnityEngine.Application.dataPath]/Exported
  • LocalLoadPath: {UnityEngine.Application.dataPath}/Resources/Mods

What does that mean? LocalBuildPath is where Unity will export the mod. Look at the folders in the Project window and you’ll see an empty Exported folder. The mod will build to this folder. UnityEngine.Application.dataPath points to the Assets folder of the Unity project.

It’s important to note that the square brackets denote the current project’s dataPath, while the curly brackets denote the dataPath of the project that’s loading the mod.

In the same way, LocalLoadPath is where you expect the mods to be. Open Game Starter and look at the folders and you’ll observe an Assets/Resources/Mods folder. Your scripts will search for the mods here. It’s a convention to locate the LocalLoadPath in UnityEngine.Application.persistentDataPath but for ease of testing, you’re going to store it in a local directory that’s easily accessible.

Setting up the Mod

Back in the Content Starter project, select all the assets in RW/Prefabs and select Addressable in the Inspector.

Setting the Prefabs as Addressable

By making these assets Addressable, Unity will pack the prefab together with all the dependencies. That’s all it takes, just a simple check!

Now open Window ▸ Asset Management ▸ Addressables ▸ Groups to view the different Addressables groups. Note the assets are under Default Local Group (Default).

The assets in the Default Local Group

Addressables support multiple groups. Each group can be in a different location and the catalog will still be able to retrieve it. For modding purposes, you only need Default Local Group (Default).

Renaming and Building Addressable Assets

The red outlined section is the actual address of each Asset. Select the four assets in that group and right-click to bring up a menu. Within the menu, select Simplify Addressable Names.

Simplifying Addressable Names

These are the addresses that you’ll use to refer to each prefab. Any new mods will have to use the same address so your game knows which Asset to spawn at which location.

Now, select Build ▸ New Build ▸ Default Build Script. That’s all it takes to create a mod for your game! Users can unleash their creativity upon the prefabs, and as long as they stick to the address naming convention, they can replace your game’s assets with anything they want.

Building the mod

These are the generated files within Assets/Exported:

Viewing the generated mod files

The generated files include:

  1. A catalog file
  2. A hash file
  3. Two asset bundles

To rename the created mod, you can give both the catalog file and the hash file a different name. Ensure that they each share the same name since Addressables links them by file name — though you can change this under Addressables Settings. Now, change both of the file names to randy_savage.

Renaming the mod files

To use this mod, open Assets/Exported in your operating system’s file explorer. To open the folder in your operating system, right-click on it and select Show in Explorer.

How to open the mod files in your OS's file explorer

Now, move these mod files to Assets/Resources/Mods in the Game Starter project.

Moving the files to the Game Starter Project

Now that the assets are in the LocalLoadPath, Addressables will be able to find the mods.

Loading Mods Using Addressables

Switch back to the Game Starter project. Open RW/Scenes/Fire Range and note that the scene is empty except for the building. Before you can test the game, you need to select the Mod Manager in the Hierarchy and select Load Mod in the Inspector.

Loading the mod

Now, press Play and enjoy some simple FPS target practice.

Playing with the default content

Once you finish, select Unload Mod within the Inspector. Look at the Hierarchy and you’ll notice a Player Instance Holder. This is an empty GameObject that tells your game where to spawn the Player asset.

Pointing out the Instance Holders in the Hierarchy

The same method is used for all the Target assets. The Load Mod and Unload Mod buttons within the Mod Manager GameObject allow you to visualize where the GameObjects will spawn when the game runs. This helps with level design during the development process. This is one possible pipeline you can use when working with mods.

Select the Player Instance Holder and view the Instance Holder component in the Inspector.

Viewing the Instance Holder in the Inspector

Name Reference tells you the address of the asset to spawn at this location and rotation, while the Placeholder is the visualization GameObject.

Creating the Reference Lookup Manager

This script is responsible for loading an asset by name, depending on the mod that’s currently loaded. Addresables stores the location of an asset in an IResourceLocation. Therefore, mapping the address of an asset to an IResourceLocation lets you find an asset by its address.

ReferenceLookupManager simply provides a central location where the rest of the scripts in the game can get assets, without having to worry about which mod is currently loaded.

Setting up the Reference Lookup Manager

Open RW/Scripts/ReferenceLookupManager.cs and add the following, above the class declaration:

using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.ResourceLocations;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceProviders;

This lets you access the classes that Addressables use.

Next, you need to add the following variables inside the class declaration:

// 1
public Dictionary<string, IResourceLocation> instances = 
    new Dictionary<string, IResourceLocation>();

// 2
private List<AsyncOperationHandle> loadedGameObjects = 
    new List<AsyncOperationHandle>();

Here’s what each of these do:

  1. Maps the address name to the IResourceLocation, letting you load that asset in the future.
  2. Keeps track of all the GameObjects instantiated from this mod. You can use the handle to release the asset from memory when you delete it.

Note that ModManager will fill the instances variable depending on which mod the user has loaded. The requiredAssets list tells the game which assets it needs to function. Here, the list already contains the following addresses: Player, Target, Bullet and Debris.

When a new mod loads, the game will find the requiredAssets addresses within the mod, then add them to the instances dictionary. Once instances populates, other scripts within the game can request the assets by name. This ensures that gameplay scripts don’t have to worry about which mod is currently loaded.

Instantiating Assets Into the Scene

Once you bring an asset into memory, deleting the object from the scene won’t remove it from memory again. You must save the AsyncOperationHandle of that object and manually release it. You’ll retrieve this handle when you instantiate an object by using ReferenceLookupManager.

To handle instantiating new objects, add the following within the class:

// 1
public AsyncOperationHandle<GameObject> Instantiate(string key, Vector3 position, Vector3 facing, Transform parent)
{
    // 2
    if (!instances.ContainsKey(key))
    {
        Debug.LogError("The object you are looking for doesn't exist in this mod pack.");
    }

    // 3
    InstantiationParameters instParams = 
        new InstantiationParameters(position, Quaternion.LookRotation(facing), parent);

    // 4
    AsyncOperationHandle<GameObject> handle = 
        Addressables.InstantiateAsync(instances[key], instParams, true);

    // 5
    loadedGameObjects.Add(handle);

    return handle;
}

This method allows you to instantiate a GameObject by simply using the key, also known as the address of the asset. Here’s a breakdown:

  1. Allows you to instantiate a GameObject by using its key at a certain position, facing direction and with a parent.
  2. Ensures that the provided key exists. Otherwise, Addressables won’t be able to find the asset.
  3. Uses InstantiationParameters to tell Unity how to spawn an object.
  4. Addressables.InstantiateAsync works like the normal instantiate method with the exception that it takes in an IResourceLocation as the object to spawn. It knows which key to use thanks to instances[key].
  5. Adds the handle to the list of loadedGameObjects so you can remove it from memory in the future.

Since the operation is asynchronous, Unity won’t return the instantiated GameObject immediately. Therefore, you return the handle, which you can use to get the GameObject once the operation completes.

Removing Assets From Memory

You need to add one final method to the ReferenceLookupManager to clear the instantiated GameObjects when switching mods:

public void ClearLoadedGameObjects()
{
    foreach (AsyncOperationHandle handle in loadedGameObjects)
    {
        if (handle.Result != null)
        {
            Addressables.ReleaseInstance(handle);
        }
    }

    loadedGameObjects.Clear();
}

This method loops through all the handles acquired in the instantiation phase and uses Addressables.ReleaseInstance to remove the GameObject and its asset from memory.

Save the script, then open RW/Scripts/InstanceHolder.cs to make one minor change. Within ModUpdated(), add the following line:

manager.lookupManager.Instantiate(nameReference, transform.position, transform.forward, this.transform);

The script will call ModUpdated() when you load a new mod. This means that each time the mod changes, InstanceHolder will spawn a GameObject using ReferenceLookupManager at the correct location.

Overall, ReferenceLookupManager takes care of instantiation and the InstanceHolder tells ReferenceLookupManager where and what to instantiate.

Modifying Player Scripts

You still have a few problems to tackle. For example, you have to call all the instantiations in the game through ReferenceLookupManager, including:

  1. Bullet spawning
  2. Debris spawning

You’ll find a better way to handle this now.

Handling Bullet Spawning

Open RW/Scripts/PlayerController.cs and, at the bottom of the file, find the following lines:


instanceQueue.Enqueue
(
    GameObject.Instantiate(gameObjectReference, position, 
        Quaternion.LookRotation(faceDirection), null)
);

This simply instantiates the bullet GameObject and adds it to an instanceQueue that supports some basic object pooling. Now, remove those lines and replace them with the following:


ReferenceLookupManager.instance.Instantiate(
    "Bullet",
    position,
    faceDirection,
    null
    ).Completed += reference =>
    {
        instanceQueue.Enqueue(reference.Result);
    };

Here, you’re telling the ReferenceLookupManager to spawn an asset named Bullet with the given parameters.

Once Addressables complete the AsyncOperation, you can use the Completed callback to get the reference.Result. This is the GameObject that’s then added to the instanceQueue.

Note the reversal in the code structure. That’s because you have to wait until AsyncOperation completes to get the GameObject, then add it to the instanceQueue. Unity has to actually read a file from your disk into the game, which is the trade-off when using Addressables.

Handling Debris Spawning

The same concept applies for the Debris that appear when the bullet hits a surface. Open RW/Scripts/Bullet.cs and find the following lines:

GameObject particleSystem = GameObject.Instantiate(particleSystemInstance, 
    other.GetContact(0).point, 
    Quaternion.LookRotation(other.GetContact(0).normal), 
    null);
Destroy(particleSystem, 1);

This code spawns the particle system at the collision point and then destroys it after one second. Remove this code and replace it with the following:

ReferenceLookupManager.instance.Instantiate(
    "Debris",
    other.GetContact(0).point,
    other.GetContact(0).normal,
    this.transform
    ).Completed += go =>
    {
        go.Result.GetComponent<ParticleSystem>().Play();
        GameObject.Destroy(go.Result, 1);
    };

Here, you’re telling the ReferenceLookupManager to spawn the Debris asset. How it will change depends on the mod. Once you instantiate Debris, you can use its reference to Play() the particle system and then Destroy it after one second.

You’re almost there. Now, you just need to find the mod files and set up the ReferenceLookupManager‘s variables and everything will work perfectly!

Creating the Mod Manager

The ModManager script is responsible for finding and loading the mods.

Open RW/Scripts/ModManager.cs and locate ModInfo at the bottom of the file. That struct contains all the information required to find and store a mod. Here’s what each variable within the struct does:

  • modName: The name of the mod.
  • modAbsolutePath: The file path to the mod.
  • modFile: Class containing file information about the mod.
  • locator: The mod’s IResourceLocator.
  • isDefault: Whether this is the default mod that comes with the game.

There are two important things to note here:

  1. IResourceLocator allows Addressables to find an IResourceLocation, given the address of the asset.
  2. The default mod contains the assets that come with the default game. You’ll set this up soon.

Now, look at the variables at the top of the class scope. path is where you must load the mods. It’s nested within the Assets folder.

To set up Start(), add the following lines:

// 1
Addressables.InitializeAsync().Completed += handle =>
{
    // 2
    ModInfo defaultContent = new ModInfo
    {
        isDefault = true,
        locator = handle.Result,
        modAbsolutePath = "",
        modFile = null,
        modName = "Default"
    };

    // 3
    mods.Add(defaultContent);
    ReloadDictionary();
    activatedMod = "Default";
    LoadCurrentMod();
};

LoadMods();

This can look overwhelming, so here’s the breakdown:

  1. You need this line to initialize the Addressables system, but you can also use it to gather the IResourceLocator of the currently loaded Addressables assets.
  2. Sets up ModInfo with the information provided. handle.Result contains the IResourceLocator of the default mod.
  3. Adds the default mod to the list of mods and makes it the currently activated mod.

This code isn’t fully functional because you haven’t implemented ReloadDictionary(), LoadCurrentMod() or LoadMods() yet. You’ll do that next

Loading Mods From the Mod Directory

Find LoadMods() and add the following code to it:

DirectoryInfo modDirectory = new DirectoryInfo(path);

foreach (FileInfo file in modDirectory.GetFiles())
{
    if (file.Extension == ".json")
    {
       // 1
    }
}

Here, you make LoadMods async so it works with the asynchronous nature of Addressables.

This segment is responsible for first getting the DirectoryInfo to locate the mods, then looping over the FileInfo of each file inside the given directory. When it approaches a file that ends in the .json format, it knows that it’s an Addressables catalog file.

Now, at // 1, add the following:

// 1
string modName = file.Name;
modName = modName.Replace(".json", "");
modName = modName.Replace("_", " ");
modName = System.Threading.Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(modName.ToLower());

// 2
IResourceLocator modLocator = await LoadCatalog(file.FullName);

// 3
ModInfo mod = new ModInfo
{
    modFile = file,
    modAbsolutePath = file.FullName,
    modName = modName,
    isDefault = false,
    locator = modLocator
};
mods.Add(mod);
ReloadDictionary();

Here’s what’s going on in the code above:

  1. Format the name of the mod from: mod_name.json to Mod Name.
  2. Use LoadCatalog to return the IResourceLocator of the current mod. You can only use the await keyword within an async method, it essentially hangs the thread while the result loads.
  3. Store the information in ModInfo and add it to the list of current mods.

For your next step, you need to implement loading the catalogs.

Loading New Catalogs Into Addressables

Below LoadMods, find LoadCatalog. Remove any existing code within the body of the method and replace it with the following:


AsyncOperationHandle<IResourceLocator> operation = 
    Addressables.LoadContentCatalogAsync(path);
// Wait until the catalog file is loaded 
// then retrieve the IResourceLocator for this mod
IResourceLocator modLocator = await operation.Task; 
return modLocator;

This method takes in the path of the catalog file and returns an IResourceLocator. The key method is the Addressables.LoadContentCatalogAsync method. Addressables provides this method to load external catalog files.

So how is the user going to load a new mod into your game? In your next step, you’ll create a button that they can click to add a mod.

Loading Mods With a Button

To load a mod with the click of a button, you’ll use the dictionary. Find ReloadDictionary and add the following to it:

modDictionary.Clear();

for (int i = 0; i < mods.Count; i++)
{
    modDictionary.Add(mods[i].modName, mods[i]);
}

for (int i = 0; i < buttons.Count; i++)
{
    GameObject.Destroy(buttons[i].gameObject);
}

buttons.Clear();

foreach (ModInfo info in mods)
{
    Button newButton = Instantiate(buttonPrefab, buttonParent);
    buttons.Add(newButton);

    newButton.onClick.AddListener(() =>
    {
        ChangeMod(info.modName);
    });

    newButton.GetComponentInChildren<Text>().text = info.modName;
}

So what’s going on in this code?

First, you loop through each mod and add it to the dictionary. The dictionary maps the name to ModInfo. You use this to get the IResourceLocator.

Then, you delete the existing buttons on-screen and replace them with the new buttons. The buttons let the user select a new mod.

By looping through each loaded ModInfo, you instantiate a newButton. You add a click listener to each button to call ChangeMod with the parameter of the current mod’s name. This makes each button load a new mod.

For your next step, you’ll give the user a way to load new mods.

Changing the Currently Loaded Mod

Now, find the ChangeMod method and add the following to its body:

lookupManager.ClearLoadedGameObjects();
activatedMod = newModName;
LoadCurrentMod();

As mentioned before, ClearLoadedGameObjects() removes all the instantiated GameObjects and removes the current mod’s assets from memory to make way for the new mod.

Now, you just need to load the current mod. To do so, find LoadCurrentMod and add the following:

if (modDictionary.ContainsKey(activatedMod))
{
    lookupManager.instances.Clear();
    for (int i = 0; i < lookupManager.requiredAssets.Count; i++)
    {
        lookupManager.instances.Add(
            lookupManager.requiredAssets[i],
            FindAssetInMod(
                lookupManager.requiredAssets[i],
                modDictionary[activatedMod]
                )
            );
    }

    for (int i = 0; i < modUpdateListeners.Count; i++)
    {
        modUpdateListeners[i]();
    }
}

Here's what's going on above:

You need to initialize the ReferenceLookupManager for each mod, and this is where you set it up. The list of requiredAssets is in the ReferenceLookupManager within the activated mod.

First, you loop through all the required assets within the lookupManager. Then, for each requiredAsset[i], you find the IResourceLocation for the asset with that key by using FindAssetInMod().

Finally, you loop through all the modUpdateListeners and inform them of the mod change. modUpdateListeners come from the InstanceHolder components.

The last thing you need to do is make it possible to find specific assets inside the mod.

Finding Assets Within the Mod

To do this, find FindAssetInMod and replace its existing lines with:

IList<IResourceLocation> locs;
if (mod.locator.Locate(key, typeof(object), out locs))
{
    return locs[0];
}

if (mod.modName != mods[0].modName)
{
    return FindAssetInMod(key, mods[0]);
}

Debug.LogError("This asset could not be found, ensure you are using the right key, or that the key exists in this mod");

return null;

Here, you use Locate inside IResourceLocator to get the IResourceLocation of all the assets with the address of key.

If the script can't find the asset within the mod, then you search for it within the default mod. This is where Addressables shine: They give you the ability to mix and match different asset bundles without dependency and loading issues.

Now, let out a sigh of relief because the coding is over! You just created your very own modding pipeline from scratch. To recap, ModManager works like this:

  1. You initialize the default mod and store it in a struct called ModInfo.
  2. The script searches the given mod directory for catalog files. Then, for every catalog file, the script stores the mod's IResourceLocator.
  3. You update the UI, then map the buttons to each of the loaded mods.
  4. When you click a button, the script removes the current mod from memory and ReferenceLookupManager updates the instances dictionary with the new mod's assets.
  5. When a script uses Instantiate from ReferenceLookupManager, it gathers the asset from the currently loaded mod.

There's just one more thing to do: You have to package the existing assets within the game into the Addressables system within the Game Starter project.

Creating the Default Mod

Open Window ▸ Asset Management ▸ Addressables ▸ Groups. Note that the prefabs within the project have already been marked as Addressable and have the correct addresses attached. If you had to do this yourself, you'd follow the same process as in the Content Starter project.

Before you build the default mod, it's important to change the Play Mode Script to Use Existing Build. This ensures that the Addressables system uses fully built catalog files rather than simulating catalog files.

Changing the Play Mode Script to Use Existing Build

Finally, select Build ▸ New Build ▸ Default Build Script.

Testing and Profiling the Game

The time has come at last — press Play and test your own moddable game!

Testing out the mod!

Isn't that mod just beautiful? With only a few small changes, you can already create a Randy Savage-inspired fish throwing simulator, complete with realistic sound effects! This shows you the true power of Addressables. Note how quickly the mods load in and out of memory. Usually, you'd need a loading screen to do this.

Using the Addressables Event Viewer

Using the Addressables Event Viewer, you can see the lifetime of Addressable Assets within the project. Open Window ▸ Asset Management ▸ Addressables ▸ Settings and select Send Profiler Events under General. Tthen open Window ▸ Asset Management ▸ Addressables ▸ Event Viewer.

The Addressables Event Viewer

You can see the moment where the resources stop being allocated to the old mod and are redirected to the new one. The asynchronous nature of Addressables really supports this type of use case.

Where to Go From Here?

You can download the complete project using the Download Materials button at the top or bottom of this tutorial.

In this Introduction to Modding with Addressables tutorial, you learned how to use Addressables to:

  • Create mods.
  • Setup a modding pipeline.
  • Find and load mods.

This project is only the beginning — there's so much more you can add. I'm interested to see what you come up with!

Did you enjoy this tutorial? Want to learn more? Check out our book, Unity Games by Tutorials, which has more info on making games with Unity.

If you want to learn more about other use cases for Addressables, check out this GitHub repository by Unity.

You'll also find some useful information in the official Addressables Developer Guide.

If you have any suggestions, questions or just want to show off what you did to improve this project, join the discussion below.

Average Rating

5/5

Add a rating for this content

4 ratings

More like this

Contributors

Comments