Skip to content

kids.kapish.addressables — Developer Guide

unity

Package identity

FieldValue
Namekids.kapish.addressables
Display nameCarrot.Addressables
Version0.1.0
Unity6000.0+
LicenseMIT

Assembly structure

kids.kapish.addressables/
  Runtime/
    Carrot.Addressables.asmdef      → Carrot.Addressables namespace
    IAddressableId.cs
    AssetAddress.cs
    ContentAddress.cs
    AssetFamily.cs
    AssetHandle.cs
    AssetLoader.cs
  Editor/
    Carrot.Addressables.Editor.asmdef  → Editor-only (scaffold)

Runtime references

Carrot, Carrot.Precompiled, Unity.Addressables, Unity.ResourceManager

Editor references

Carrot, Carrot.Precompiled, Carrot.Editor, Carrot.Addressables, Unity.Addressables, Unity.Addressables.Editor, Unity.ResourceManager

Architecture

The IAddressableId abstraction

The package's core pattern is the IAddressableId interface — a single method GetAddressableId() returning a string key. All address structs implement it, and AssetLoader accepts it on every method. This means:

  • Custom address types are trivially composable.
  • Nothing forces consumers into a single address format.
  • The loader doesn't care how the key was constructed.

Address hierarchy

AssetAddress is the simplest — a raw key string. ContentAddress adds structure (content/{type}/{schema}/{name}) for authored content flowing from the web platform. AssetFamily groups assets by label for bulk operations. Both ContentAddress and AssetFamily convert to AssetAddress or string implicitly.

Handle lifecycle

AssetHandle<T> implements IDisposable to release the underlying AsyncOperationHandle<T>. This is the package's primary defence against Addressables reference leaks — use using or call Dispose() explicitly.

TextHandle is a deliberate exception: the TextAsset is released immediately during loading, and only the extracted string is carried forward. No disposal needed.

Awaitable, not async Task

All AssetLoader methods return Awaitable<T> (Unity 6+), not Task<T>. This keeps allocations lower and plays nicely with the Unity player loop. CancellationToken is supported on all methods that perform loads.

Design decisions

  • Readonly structs for addresses — zero allocation, value semantics, safe to pass around.
  • Implicit conversions to string — so address types work anywhere a raw key is expected without ceremony.
  • No MonoBehaviour dependencyAssetLoader is a static class. Composable from anywhere.
  • Separate text pathLoadTextAsync extracts and releases in one shot because holding a TextAsset handle for the sake of a string is wasteful.

Dependencies

PackageVersionPurpose
kids.kapish0.1.0Carrot core runtime and precompiled assemblies
com.unity.addressables2.9.1Unity Addressables system

Usage Guide

Loading a single asset

Use AssetLoader.LoadAsync<T> and dispose the handle when you're done with the asset.

csharp
using Carrot.Addressables;
using UnityEngine;

public class IconLoader : MonoBehaviour
{
    private AssetHandle<Sprite> _iconHandle;

    public async Awaitable LoadIcon(string key)
    {
        _iconHandle?.Dispose();
        _iconHandle = await AssetLoader.LoadAsync<Sprite>(new AssetAddress(key));
        GetComponent<SpriteRenderer>().sprite = _iconHandle.Asset;
    }

    void OnDestroy() => _iconHandle?.Dispose();
}

Scoped loading with using

For short-lived assets, using keeps things clean:

csharp
using var handle = await AssetLoader.LoadAsync<AudioClip>(new AssetAddress("sfx/click"));
audioSource.PlayOneShot(handle.Asset);
// handle released automatically at end of scope

Loading text content (JSON, CSV, etc.)

LoadTextAsync extracts the text and releases the TextAsset immediately — no handle management needed.

csharp
var result = await AssetLoader.LoadTextAsync(new AssetAddress("config/settings"));
var settings = JsonUtility.FromJson<GameSettings>(result.Text);

Structured content addresses

For content authored in the Carrot web platform, use ContentAddress to build deterministic keys:

csharp
// Resolves to: content/dialogue/v2/intro_scene
var address = new ContentAddress("dialogue", "v2", "intro_scene");
var json = await AssetLoader.LoadTextAsync(address);

Segment-based address construction

AssetAddress accepts params string[] to join path segments:

csharp
// Resolves to: "ui/icons/star"
var address = new AssetAddress("ui", "icons", "star");

Bulk loading with AssetFamily

Load or preload all assets that share a label:

csharp
var sfx = new AssetFamily("sfx_combat");

// Load all AudioClips with that label
var handle = sfx.LoadAllAsync<AudioClip>();
await handle.Task;

foreach (var clip in handle.Result)
{
    Debug.Log($"Loaded: {clip.name}");
}

Preload (download) dependencies without loading into memory:

csharp
var textures = new AssetFamily("menu_textures");
var op = textures.DownloadDependenciesAsync();
await op.Task;
Debug.Log("Menu textures cached.");

Checking if an address exists

csharp
bool exists = await AssetLoader.ExistsAsync(new AssetAddress("optional/feature"));

if (exists)
{
    using var handle = await AssetLoader.LoadAsync<GameObject>(new AssetAddress("optional/feature"));
    Instantiate(handle.Asset);
}

Instantiating prefabs

csharp
var go = await AssetLoader.InstantiateAsync(
    new AssetAddress("prefabs/enemy_grunt"),
    parent: transform);

go.transform.localPosition = Vector3.zero;

Cancellation

All load methods accept a CancellationToken:

csharp
private CancellationTokenSource _cts = new();

async Awaitable LoadWithCancellation()
{
    using var handle = await AssetLoader.LoadAsync<Texture2D>(
        new AssetAddress("ui/background"),
        _cts.Token);

    // Won't reach here if cancelled
    ApplyTexture(handle.Asset);
}

void OnDestroy() => _cts.Cancel();

Custom address types

Implement IAddressableId to create domain-specific address types:

csharp
public readonly struct LevelAddress : IAddressableId
{
    public readonly int World;
    public readonly int Stage;

    public LevelAddress(int world, int stage)
    {
        World = world;
        Stage = stage;
    }

    public string GetAddressableId() => $"levels/world_{World}/stage_{Stage}";
}

// Works with all AssetLoader methods
var level = await AssetLoader.LoadAsync<GameObject>(new LevelAddress(3, 7));

Carrot