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. By Ajay Venkat.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Introduction to Modding Unity Games With Addressables
35 mins
- Getting Started
- Understanding Addressables
- Advantages of Addressables
- Looking Under Addressables’ Hood
- Exploring the Project
- Understanding the Addressables’ Settings
- Setting up the Mod
- Renaming and Building Addressable Assets
- Loading Mods Using Addressables
- Creating the Reference Lookup Manager
- Setting up the Reference Lookup Manager
- Instantiating Assets Into the Scene
- Removing Assets From Memory
- Modifying Player Scripts
- Handling Bullet Spawning
- Handling Debris Spawning
- Creating the Mod Manager
- Loading Mods From the Mod Directory
- Loading New Catalogs Into Addressables
- Loading Mods With a Button
- Changing the Currently Loaded Mod
- Finding Assets Within the Mod
- Creating the Default Mod
- Testing and Profiling the Game
- Using the Addressables Event Viewer
- Where to Go From Here?
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:
- Format the name of the mod from:
mod_name.json
toMod Name
. - Use
LoadCatalog
to return theIResourceLocator
of the current mod. You can only use theawait
keyword within anasync
method, it essentially hangs the thread while the result loads. - Store the information in
ModInfo
and add it to the list of currentmods
.
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:
- You initialize the default mod and store it in a struct called
ModInfo
. - The script searches the given mod directory for catalog files. Then, for every catalog file, the script stores the mod's
IResourceLocator
. - You update the UI, then map the buttons to each of the loaded mods.
- When you click a button, the script removes the current mod from memory and
ReferenceLookupManager
updates theinstances
dictionary with the new mod's assets. - When a script uses
Instantiate
fromReferenceLookupManager
, 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.
Finally, select Build ▸ New Build ▸ Default Build Script.