How to make a chunk system in Unity using addressables

If you played to Minecraft you already know what chunks are but for the most unfortunate I’ll give you the Minecraft Gamepedia definition:

Chunks are the method used by the world generator to divide maps into manageable pieces.

Don’t worry, it’s not a Minecraft specific tutorial explanation, the comparisons stop here.

Before we start, I’m using Unity version 2020.3.5f1 with the addressables package version 1.16.16, all the 3D assets are made by Kenney.

Link to the github repo

1. Preparation

If it’s not already the case you need to install the addressables package via the package manager.

Once you have everything in place and you have created your level design, you need to convert every piece into a prefab, the size and what you define as pieces depends on your needs.

For example, this is what my demo scene looks like and every hexagon tile has been converted into prefabs:

Left: game view of my level in 3D, Right: project view of my folder containing all my prefabs.

When it’s done, you need to select all your prefabs freshly created inside the Project view and click on the Addressable checkbox in the top left corner of your Inspector view.

Inspector view with all the prefabs selected and the Addressable checkbox checked

Now that all your pieces have been converted to addressables, we don’t need to keep them inside the scene, but we still need to keep the position and rotation references of each piece.

To do this, select all the prefab instances inside your Hierarchy view and unpacked them.
Then, remove every components on those gameObjects and keep only the transform one. You can rename them for more clarity, but it doesn’t matter too much.

Left: hierarchy view with GameObjects unpacked and renamed Tile, Right: Inspector view with only the Transform component

2. Scripting

Let’s continue, we are going to create a monoBehaviour script that will be responsible for loading and releasing the addressable asset, I’m calling it HexagonTile but you can name it what you want.

Open the script with your favorite IDE and copy paste the code below, you need to change the class name if you don’t choose the same as mine.

using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

public class HexagonTile : MonoBehaviour
{
[SerializeField] private AssetReference assetReference;

private AsyncOperationHandle<GameObject> _operationHandle;
private GameObject _tileAsset;

public async Task InstantiateTileAsset()
{
if (_tileAsset != null) return;

_operationHandle = assetReference.InstantiateAsync(transform.position, transform.rotation, transform);

await _operationHandle.Task;

_tileAsset = _operationHandle.Result;
}

public void ReleaseTileAsset()
{
if (_tileAsset == null) return;

Addressables.ReleaseInstance(_operationHandle);
Addressables.ReleaseInstance(_tileAsset);
}
}

If you are not familiar with the SerializeField attribute known that it’s let you pass a value from the Unity editor and serialize it.

I think the rest of the code is self explanatory, but a little bit of precision of how it’s working behind the hood.

When you call the InstantiateAsync method, the addressable’s system are going to load and instantiate your asset at runtime, before that and after you release it this asset doesn’t live in your ram, so it don’t cost anything. That’s all the power of using addressable’s.

The loading and instancing part are in async, meaning that the code is not executed on the main thread, you can read more about async and task on the Microsoft documentation.

Now that’s everything is in place we need to add this script to the tiles, to do that, select all your tiles gameObject and add the script as component.

Left: hierarchy view with the GameObject tiles selected, Right: Inspector view with the HexagonTile script component

As you can see there is an Asset Reference empty slot where we are going to add the reference to all of our prefabs / addressables. You can do it by hand, but you can also do it automatically as long has you didn’t change the name of your gameObjects.

Add this piece of code to the HegaxonTile class, the path value depends on where you saved all your prefabs previously.

[ContextMenu("FindAssetReferenceByName")]
private void FindAssetReferenceByName()
{
var path = $"Assets/Prefabs/HexagonTiles/{transform.name}.prefab";
var asset = AssetDatabase.LoadAssetAtPath(path, typeof(GameObject)) as GameObject;
assetReference.SetEditorAsset(asset);
}

Then get back to the editor with all your gameObject tiles still selected and click on the three dots on the right of your HexagonTile component in the Inspector view and click on FindAssetReferenceByName.

Inspector view with the FindAssetReferenceByName method hovered

We’ve done all we need for the tiles, we are halfway there, we still need to connect the logic to the camera.

Create a new script, I’m calling it HexagonTilesChunkSystem (yes, I’m bad at naming things) and like for the previous script, copy and paste the code below:

using System;
using System.Collections.Generic;
using UnityEngine;

public class HexagonTilesChunkSystem : MonoBehaviour
{
[SerializeField] private List<HexagonTile> tiles;
[SerializeField] private float tileRadius = 0.6f;
[SerializeField] private Camera mainCamera;

private CullingGroup _cullingGroup;
private BoundingSphere[] _boundingSpheres = new BoundingSphere[0];

private void OnEnable()
{
InitCullingGroup();
}

private void OnDisable()
{
_cullingGroup.Dispose();
}

private void InitCullingGroup()
{
_cullingGroup = new CullingGroup();
_boundingSpheres = new BoundingSphere[tiles.Count];

for (var i = 0; i < _boundingSpheres.Length; i++)
{
_boundingSpheres[i].position = tiles[i].transform.position;
_boundingSpheres[i].radius = tileRadius;
}

_cullingGroup.SetBoundingSpheres(_boundingSpheres);
_cullingGroup.targetCamera = mainCamera;

_cullingGroup.onStateChanged = OnStateChanged;
}

private void OnStateChanged(CullingGroupEvent evt)
{
if (evt.hasBecomeVisible)
tiles[evt.index].InstantiateTileAsset();

if(evt.hasBecomeInvisible)
tiles[evt.index].ReleaseTileAsset();
}
}

There is a lot of things happening here so lets me explain, we are going to use cullingGroup and if you are not familiar with this API I recommend you to read the article on riptutorial.com, it’s way more complete than the one on the official documentation.

CullingGroup and specifically the boundingSpheres are useful and efficient when you need to detect if something is visible or not to the camera.

In the InitCullingGroup method I make sure every tile has his boundingSphere twin at the same position and having more or less the same size (here the radius).

You can pass a method which will be triggered every time a boundingSphere visibility changed, mine is called OnStateChanged and has you can see depending on the visibility state the tile is instantiated or released.

Once this is done, you need to create a new gameObject and add this HexagonTilesChunkSystem script component to it.

Don’t forget to fill the properties, select all your gameObject tiles with the HexagonTile script component and drag & drop those in the tiles field, drag and drop the camera gameObject to the mainCamera field and for the tileRadius, you need to find the right value by yourself.

Inspector view with the HexagonTilesChunkSystem script component fill with properties

3. Testing

Now that everything is in place you can play your Unity scene and move the camera around to see the chunks loading and unloading automaticaly.

Scene view of the chunks loading and unloading
Game view of the chunks loading and unloading

And voilà, this demo is obviously not perfect, but if you understand how this is working you can easily build a better and stronger version, you are now ready to make the next indie open world, thanks for the reading.

Freelance Creative Developer & Game Developer, Former Creative Developer at @makemepulse, Graduate from @Gobelins_Paris

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store