Appearance
kids.kapish.addressables — Developer Guide
unity
Package identity
| Field | Value |
|---|---|
| Name | kids.kapish.addressables |
| Display name | Carrot.Addressables |
| Version | 0.1.0 |
| Unity | 6000.0+ |
| License | MIT |
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 dependency —
AssetLoaderis a static class. Composable from anywhere. - Separate text path —
LoadTextAsyncextracts and releases in one shot because holding aTextAssethandle for the sake of a string is wasteful.
Dependencies
| Package | Version | Purpose |
|---|---|---|
kids.kapish | 0.1.0 | Carrot core runtime and precompiled assemblies |
com.unity.addressables | 2.9.1 | Unity 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 scopeLoading 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));