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.

Amba for Unity is distributed as a UPM (Unity Package Manager) package: com.layers.amba. Add it once; every supported Unity target works.

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

Supported platforms: iOS, Android, macOS, Windows, and Linux. WebGL builds support events only — auth, collections, storage, and push aren't available on that platform yet. Events are buffered 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#v1.0.0

Option B — manifest.json

Edit Packages/manifest.json:

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

Option C — local tarball (offline / private mirrors)

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

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

The bundled 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 = ""; // assign in Inspector from Project Settings
 
    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]).

Which key goes here

For a Unity game, always use the client key that npx @layers/amba init issued for you (printed as clientKey in the CLI output; visible in the amba console under your project's API keys tab). The client key is designed to ship to end-user devices.

Never put a server key in a Unity build. Server keys are for trusted server contexts only — a server-side cron job, a backend endpoint, an edge function — and grant elevated privileges that would let anyone unpacking your IPA/APK impersonate your tenant. The SDK rejects server keys at configure time with InvalidOperationException("server key not allowed in client SDK").

3. Verify the SDK reached amba

After pressing Play (or installing a build), open the Unity Console (Window → General → Console) and look for the SDK's startup line:

amba: configured, anonymousId=usr_anon_01HZX9...

For a stronger verify — one that proves the network round-trip succeeded, not just that local configure ran — extend the bootstrap to call Amba.Diagnostics.Ping() and log the server-echoed envelope:

private async void Awake()
{
    DontDestroyOnLoad(gameObject);
    try
    {
        await Amba.ConfigureAsync(apiKey: apiKey);
        var ping = await Amba.Diagnostics.Ping();
        if (!ping.Ok)
        {
            Debug.LogError($"amba: ping returned ok=false — {ping.Error}");
            return;
        }
        Debug.Log($"amba: connected, project={ping.ServerProjectId} env={ping.Environment} key={ping.KeyFingerprint} latency={ping.LatencyMs}ms");
    }
    catch (System.Exception e)
    {
        Debug.LogError($"amba: verify failed — {e.Message}\n{e.StackTrace}");
    }
}

If you see amba: connected, project=prj_... in the Console, your client key resolved to a real project on the server and the wire round-tripped end-to-end. Compare ServerProjectId against the project id you think you configured — a mismatch means the wrong Inspector field is loading. If you don't see the line:

  • No amba: log at allConfigureAsync threw and was swallowed. Make sure your bootstrap GameObject is in the initial scene's hierarchy (not disabled, not in a sub-scene loaded later).
  • AmbaException: unauthorized — the apiKey Inspector field is empty, malformed, or revoked. Re-run npx @layers/amba init to mint a fresh one.
  • HttpRequestException after >10s — the editor or device can't reach api.amba.dev. On an editor running offline, that's expected; on device, check the platform's network permission (Android INTERNET is granted by default; iOS App Transport Security blocks plain http:// so always use https://).

For machine-checkable verify in tests, assert on Ok plus the resolved project id:

var ping = await Amba.Diagnostics.Ping();
Assert.IsTrue(ping.Ok, $"amba round-trip failed — {ping.Error}");
Assert.IsFalse(
    string.IsNullOrEmpty(ping.ServerProjectId),
    "amba round-trip failed — no project id from server");

4. 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.

5. 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" },
});

6. 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).)

7. 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

8. 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 });

9. 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());

Coroutine vs Task (when to use which)

Every SDK method returns a Task — they're awaitable from any async method, but Unity's coroutine system uses IEnumerator, not Task. Two patterns work:

Pattern A — async void from MonoBehaviour event methods (simplest; fine for fire-and-forget):

private async void OnTriggerEnter(Collider other)
{
    await Amba.Events.TrackAsync("zone_entered", new Dictionary<string, object>
    {
        { "zone", other.tag },
    });
}

Use async void only on Unity's event methods (Awake, Start, OnTriggerEnter, button onClick handlers). Exceptions inside async void are unhandled — wrap risky calls in try/catch.

Pattern B — bridge from a coroutine via IEnumerator:

private IEnumerator TrackInCoroutine()
{
    var task = Amba.Events.TrackAsync("level_complete");
    yield return new WaitUntil(() => task.IsCompleted);
    if (task.IsFaulted) Debug.LogError($"amba: {task.Exception}");
}

Use Pattern B when the surrounding logic is already a coroutine (e.g. you're inside yield return new WaitForSeconds(...) chains).

Don't use .Wait() or .Result — they block the main thread and hang the game loop in the Editor's Play mode.

Mobile build target gotchas

iOS — App Store privacy manifest

When archiving for App Store submission, ensure the SDK's PrivacyInfo.xcprivacy is included in your Xcode project. Unity's iOS build pipeline includes it automatically when Packages/com.layers.amba/Plugins/iOS/PrivacyInfo.xcprivacy is present.

If you have a custom post-process build script that overwrites or filters Plugins files (common in studios that pin Unity to a specific Xcode template), explicitly re-include the file:

using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;
 
public static class AmbaPrivacyManifestInjector
{
    [PostProcessBuild]
    public static void OnPostProcessBuild(BuildTarget target, string pathToBuiltProject)
    {
        if (target != BuildTarget.iOS) return;
        // Re-add Packages/com.layers.amba/Plugins/iOS/PrivacyInfo.xcprivacy
        // to the generated Xcode project's resources if your custom
        // build script stripped it.
    }
}

App Store review will flag missing privacy manifests since iOS 17; the SDK declares its categories (network access, user identifier) so you don't need to re-declare them in your app's manifest.

Android — Gradle and packaging options

Unity 2022.3 LTS uses Gradle 7+ for Android builds. The SDK's bundled binaries land in Packages/com.layers.amba/Plugins/Android/ and Unity's Plugin Importer wires them into the Gradle classpath automatically.

If a second plugin in your project bundles the same artifact (unlikely but possible with mobile ad SDKs), Gradle fails with More than one file was found with OS independent path 'lib/arm64-v8a/libamba_core.so'. Resolve via mainTemplate.gradle:

android {
    packagingOptions {
        pickFirst "**/libamba_core.so"
    }
}

Enable a custom mainTemplate.gradle under Project Settings → Player → Android → Publishing Settings.

For ProGuard / R8 minification: the SDK ships consumer rules in its AAR; no app-side configuration is required.

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 — the WebGL Player has no durable storage the SDK can safely use 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 FakeBindings : IAmbaBindings { /* implement with in-memory fakes */ }
 
[Test]
public async Task Tracks_event_through_client()
{
    var client = new AmbaClient(new FakeBindings());
    await client.InitializeAsync("test");
    await client.Events.TrackAsync("test_event");
    // assert on the fake's recorded calls
}

AmbaClient accepts an IAmbaBindings via the constructor — production code goes through Amba.ConfigureAsync(...) which wires the platform-specific default bindings.

IL2CPP + AOT considerations

The C# wrapper is 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

  • Events silently vanishAmba.Events.TrackAsync queues, batches, and retries by design; a wrong key, wrong env, or revoked credential does not throw at the call site. Always run the verify step in section 3 on every fresh build so a misconfiguration shows up as a loud failure, not silence. This is the failure mode that ends with "I shipped to TestFlight and no events showed up for a week."
  • Wrong env in a build flavour — a clientKey minted for staging will happily configure against the production stack and write to the wrong project. The verify snippet in section 3 prints the server-resolved ServerProjectId + Environment on every cold start; compare against the values your build expects on first launch.
  • 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 at runtime — the platform-specific bundled binary wasn't packaged into your build. In the Plugin Importer (Packages/com.layers.amba/Plugins/<target>/), verify each binary has the matching target checkbox enabled.
  • WebGL build fails with "cannot find amba_core" — expected. WebGL builds don't include the full native artifact; 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 iOS binary is bitcode-free.

See also