Amba

Unity SDK

Unity quickstart for the amba C# SDK — UPM install via git URL or tarball, Amba.ConfigureAsync(), Amba.Events.TrackAsync(), and WebGL events-only caveat.

The amba Unity SDK is distributed as a UPM (Unity Package Manager) package: com.layers.amba. It bundles native amba binaries for every supported Unity target (iOS, Android × 4 ABIs, macOS, Windows, Linux) plus a C# wrapper (Amba.cs).

Supported runtime: Unity 2022.3+ LTS (Mono and IL2CPP). .NET Standard 2.1.

Supported platforms: iOS, Android, macOS, Windows, Linux. WebGL is events-only — the underlying filesystem persistence layer doesn't exist in the browser sandbox, so auth, collections, storage, and push are unavailable. Events are buffered in memory and flushed at exit.

1. Install via Package Manager

Option A — git URL

In the Unity Package Manager (Window → Package Manager) → +Add package from git URL → paste:

https://github.com/layers/amba-sdk-unity.git#v0.1.0

Option B — manifest.json

Edit Packages/manifest.json:

{
  "dependencies": {
    "com.layers.amba": "https://github.com/layers/amba-sdk-unity.git#v0.1.0"
  }
}

Option C — local tarball (offline / private mirrors)

Download the com.layers.amba-0.1.0.tgz tarball from the releases page, drop it in Packages/, and reference it:

{
  "dependencies": {
    "com.layers.amba": "file:com.layers.amba-0.1.0.tgz"
  }
}

The native binaries land under Packages/com.layers.amba/Plugins/<target>/ and Unity's Plugin Importer flags them for the correct target automatically.

2. Configure on startup

Add a bootstrap MonoBehaviour to your initial scene (or use [RuntimeInitializeOnLoadMethod]):

// AmbaBootstrap.cs
using System.Threading.Tasks;
using UnityEngine;
using Layers.Amba;
 
public class AmbaBootstrap : MonoBehaviour
{
    [SerializeField] private string apiKey = "amb_dev_ck_XXXX"; // override in inspector
 
    private async void Awake()
    {
        DontDestroyOnLoad(gameObject);
        try
        {
            await Amba.ConfigureAsync(apiKey: apiKey);
            Debug.Log($"amba: configured, anonymousId={Amba.AnonymousId}");
        }
        catch (System.Exception e)
        {
            Debug.LogError($"amba: configure failed — {e.Message}");
        }
    }
}

Don't ship a hard-coded key in source. Read it from a secrets ScriptableObject, an environment-driven build define, or your secrets framework (Unity Cloud Build supports env-driven defines via [Conditional]).

3. First sign-in (anonymous)

Events.TrackAsync is authenticated server-side — the request needs a session token. Mint one anonymously on first launch:

using Layers.Amba;
 
var session = await Amba.Auth.SignInAnonymouslyAsync();
Debug.Log($"amba: signed in as {session.GetProperty("user").GetProperty("id").GetString()}");

The response is a System.Text.Json.JsonElement — read fields directly with GetProperty(...), or deserialize through your own POCOs with JsonSerializer.Deserialize<MyType>(session.GetRawText()). The session token persists across game launches.

4. First event

Once a session exists, track gameplay events:

using System.Collections.Generic;
using Layers.Amba;
 
await Amba.Events.TrackAsync("game_started", new Dictionary<string, object>
{
    { "level", 1 },
    { "difficulty", "normal" },
});

5. Sign in with Apple (iOS) / Google (Android)

Unity doesn't ship platform OAuth flows; use a third-party plugin and forward the resulting tokens.

For Sign in with Apple on iOS, the Apple Authentication Asset Store package provides the identity token:

// pseudo — adapt to whichever Apple auth plugin you use
var identityToken = await AppleAuthPlugin.SignInAsync();
await Amba.Auth.SignInWithAppleAsync(identityToken);

For Sign in with Google on Android, use Google Play Games Services for Unity and forward idToken:

var idToken = await GooglePlayPlugin.SignInAsync();
await Amba.Auth.SignInWithGoogleAsync(idToken);

(Same signInWithSocial server endpoint as every other SDK — the wrapper API is Amba.Auth.SignInWithGoogleAsync(idToken).)

6. Register for push

Use Unity's Mobile Notifications package to obtain the device token, then forward to amba:

// iOS — APNs
#if UNITY_IOS
using Unity.Notifications.iOS;
 
var request = await iOSNotificationCenter.RequestAuthorizationAsync(
    AuthorizationOption.Alert | AuthorizationOption.Badge | AuthorizationOption.Sound,
    registerForRemoteNotifications: true);
if (request.Granted && !string.IsNullOrEmpty(request.DeviceToken))
{
    await Amba.Push.RegisterAsync(
        token: request.DeviceToken,
        platform: "apns",
        bundleId: Application.identifier);
}
#endif
 
// Android — FCM
#if UNITY_ANDROID
// Use Firebase Unity SDK to obtain the FCM token, then:
await Amba.Push.RegisterAsync(token: fcmToken, platform: "fcm", bundleId: Application.identifier);
#endif

7. First collection insert

using System.Collections.Generic;
using System.Text.Json;
using Layers.Amba;
 
var inserted = await Amba.Collections.InsertAsync("posts", new Dictionary<string, object>
{
    { "title", "Hello amba" },
    { "body", "first post from Unity" },
});
Debug.Log($"amba: inserted {inserted.GetProperty("id").GetString()}");
 
var response = await Amba.Collections.FindAsync("posts", new Dictionary<string, object>
{
    { "order", new[] { new Dictionary<string, object> { { "column", "created_at" }, { "direction", "desc" } } } },
    { "limit", 20 },
});
var data = response.GetProperty("data");
Debug.Log($"amba: got {data.GetArrayLength()} posts");

For typed reads, deserialize through your own POCOs:

public class Post { public string Id { get; set; } public string Title { get; set; } public string Body { get; set; } }
 
var posts = JsonSerializer.Deserialize<List<Post>>(
    response.GetProperty("data").GetRawText(),
    new JsonSerializerOptions { PropertyNameCaseInsensitive = true });

Server availability: Collections.FindAsync (the read-list endpoint) is currently landing on staging — the call shape above is correct, but expect a 404 until the route ships. InsertAsync / FindOneAsync / UpdateAsync / DeleteAsync round-trip today.

8. AI proxy

var response = await Amba.Ai.Anthropic.Messages.CreateAsync(new Dictionary<string, object>
{
    { "prompt_slug", "support_assistant" },
    { "variables", new Dictionary<string, object> { { "user_query", "How do I cancel?" } } },
    { "max_tokens", 1024 },
});
Debug.Log(response.GetProperty("content").GetRawText());

Server availability: the SDK posts /v1/client/ai/anthropic/messages with { prompt_slug, ... }; the current staging server mounts /v1/client/ai/prompts/:name/invoke with { context?, messages?, max_tokens? }. The two sides are converging — until they align, this call returns 404 / 400. The prompt slug catalog is shared with the rest of the SDKs once the contract lands.

WebGL caveat (events only)

WebGL builds use a different code path:

  • Amba.Events.TrackAsync works — events are buffered in memory and flushed at exit (or by an explicit Amba.Events.FlushAsync if you add one).
  • Amba.Auth.*, Amba.Collections.*, Amba.Storage.*, Amba.Push.* throw NotSupportedException because the underlying Rust persistence layer requires a filesystem.

The underlying reason is that the WebGL Player runs in an emscripten-compiled WebAssembly sandbox with no localStorage-equivalent that amba can write to safely across sessions. For full surface in the browser, use @layers/amba-web instead and bridge from your WebGL game via Unity → JavaScript interop.

Constructor DI for tests

using Layers.Amba;
using NUnit.Framework;
 
public class FakeNativeMethods : INativeMethods { /* implement with in-memory fakes */ }
 
[Test]
public async Task Tracks_event_through_client()
{
    var client = new AmbaClient(new FakeNativeMethods());
    await client.InitializeAsync("test");
    await client.Events.TrackAsync("test_event");
    // assert on the fake's recorded calls
}

AmbaClient accepts an INativeMethods via the constructor — production code goes through Amba.ConfigureAsync(...) which wires DefaultNativeMethods (native binding).

IL2CPP + AOT considerations

The C# wrapper uses System.Text.Json — fully AOT-compatible under IL2CPP from Unity 2022.3 onward. No link.xml preserves are needed for the SDK itself; the included Layers.Amba.asmdef keeps stripping conservative.

If you've enabled aggressive code stripping (managed stripping level High), add a small link.xml:

<linker>
  <assembly fullname="Amba" preserve="all" />
</linker>

Common pitfalls

  • Amba.ConfigureAsync not awaited before first Amba.* access throws InvalidOperationException("Amba.ConfigureAsync must be called before accessing Amba.*"). Configure in a bootstrap MonoBehaviour with DontDestroyOnLoad.
  • DllNotFoundException: amba_core at runtime — the platform-specific native library wasn't packaged. In the Plugin Importer, verify each Plugins/<target>/libamba_core.* has the matching target checkbox enabled.
  • WebGL build fails with "cannot find amba_core" — expected. WebGL doesn't ship libamba_core; use the events-only code path and gate non-events calls behind #if !UNITY_WEBGL.
  • iOS bitcode warning — if your Xcode project has bitcode enabled (default in Unity 2022.3 LTS is off), strip it before archiving. The shipped libamba_core.a is bitcode-free.

See also