View as Markdown

Analytics in Unity

Hiro’s analytics system lets your Unity client capture player behavior as events and forward those events to one or more analytics providers: services like Satori , Firebase, GameAnalytics, or Smartlook that receive your events. Each provider is a Hiro integration you add to your systems, so you can send the same event to several services without writing separate tracking code for each.

This page covers the client-side analytics that your Unity game sends. Hiro also emits server-side events from systems such as economy, achievements, and challenges. To learn more, see About Hiro analytics .

How it works #

You send analytics through a single object, systems.AnalyticsGroup. Call one of its methods and it forwards the event to every provider you’ve registered, so you write your tracking once and it reaches all of them. Register your providers once at startup, then call AnalyticsGroup from anywhere in your game.

Always send through AnalyticsGroup
Send events through systems.AnalyticsGroup, not through a provider directly. That reaches every provider at once and gives you the convenience methods like ScreenViewed. Use a provider instance directly only for provider-specific features, for example SatoriSystem for Satori feature flags and live events.

Register analytics providers #

Create each provider and add it to Systems. The examples below assume you’ve already done the initial scaffolding as shown in Set up Hiro with Nakama .

1
2
3
4
5
var satoriSystem = new SatoriSystem(logger, scheme, host, port, serverKey, fn: null, useCache: true);
systems.Add(satoriSystem);

// Access the multiplexed group once your providers are registered.
var analytics = systems.AnalyticsGroup;

Hiro ships with four built-in analytics providers: Satori, Firebase, GameAnalytics, and Smartlook. Firebase, GameAnalytics, and Smartlook wrap Unity-native SDKs, so add each one only after you install and configure the corresponding SDK in your project.

Satori #

SatoriSystem sends events to the Satori LiveOps platform , where it powers player segmentation, remote configuration, metrics, and more. It takes a Satori server key and an authorization function that returns a Satori session. Pass null to authorize with the default strategy, or supply your own function to reuse an existing session.

1
2
3
4
5
6
7
var scheme = "https";
var host = "<hostAddress>";
var port = 443;
var serverKey = "<serverKey>";

var satoriSystem = new SatoriSystem(logger, scheme, host, port, serverKey, fn: null, useCache: true);
systems.Add(satoriSystem);

Set useCache to true to store events locally while the device is offline and send them once connectivity returns.

Firebase #

FirebaseSystem sends events into a configured Firebase instance and also handles Firebase Remote Config and Crashlytics. Provide a Firebase configuration JSON file in your project. The constructor takes the Remote Config fetch interval in seconds.

1
2
var firebaseSystem = new FirebaseSystem(logger, fetchIntervalSecs: 43200);
systems.Add(firebaseSystem);

GameAnalytics #

GameAnalyticsSystem sends events into a configured GameAnalytics instance.

1
2
var gameAnalyticsSystem = new GameAnalyticsSystem(logger);
systems.Add(gameAnalyticsSystem);

Smartlook #

SmartlookSystem sends events into a configured Smartlook instance for session recording and analytics.

1
2
var smartlookSystem = new SmartlookSystem(logger, apiKey: "<apiKey>");
systems.Add(smartlookSystem);
Adjust ships with the Unity package as an attribution and lifecycle integration, not as an analytics provider. It does not implement IAnalyticsSystem and does not receive events through AnalyticsGroup. AppsFlyer is not a built-in analytics provider. To send events to either service, create a custom provider .

Send events #

Call a convenience method on the group to send a preformatted event to every registered provider:

1
2
3
analytics.AppLaunched();
analytics.GameStarted("Level1");
analytics.CurrencyGranted("gold", 100);

Every convenience method has a synchronous variant and an Async variant. Use the synchronous variant for fire-and-forget events, and await the Async variant when you need the send to complete:

1
2
3
4
5
6
7
8
await analytics.PurchaseCompletedAsync(
    id: purchaseId,
    product: "starter_pack",
    sku: "com.game.starterpack",
    store: "GooglePlay",
    transactionId: transactionId,
    testPurchase: false,
    source: "shop_popup");

Default client events #

Each convenience method sends a fixed event name and a set of properties. Use these across your game so every provider receives a consistent, predictable schema.

Explicitly send default events
Default events are not automatically fired by the SDK. Your game client must fire them to reach your registered providers.
MethodEvent nameProperties
AchievementClaimedachievementClaimedid
AchievementUpdatedachievementUpdatedid, count
AdImpressionadImpressionformat, source, platform, unitName, id, currency, amount, test
AdPlacementStartedadPlacementStartedid
AdPlacementSucceededadPlacementSucceededid
AdPlacementFailedadPlacementFailedid
AdStartedadStartedid
AppLaunchedappLaunched(none)
CurrencyGrantedcurrencyGrantedname, amount
CurrencySpentcurrencySpentname, amount
EnergyGrantedenergyGrantedname, amount
EnergySpentenergySpentname, amount
GameStartedgameStartedname
GameFinishedgameFinishedname
ItemGranteditemGrantedname, amount
ItemSpentitemSpentname, amount
ItemConsumeditemConsumedname, amount
PurchaseIntentpurchaseIntentid, product, sku, store, testPurchase, source
PurchaseCompletedpurchaseCompletedid, product, sku, store, transactionId, testPurchase, source
ScreenViewedscreenViewedscreenName, screenBeforeName
StatUpdatedstatUpdatedname, amount
TutorialAcceptedtutorialAcceptedname
TutorialDeclinedtutorialDeclinedname
TutorialStartedtutorialStartedname
TutorialStepCompletedtutorialStepCompletedname, stepIndex
TutorialCompletedtutorialCompletedname
TutorialAbandonedtutorialAbandonedname

Add metadata to a default event #

Every convenience method accepts an optional metadata dictionary. Hiro merges those entries into the event alongside the standard fields, so you can attach extra context without leaving the standard event set:

1
2
3
4
5
analytics.CurrencySpent("gold", 100, new Dictionary<string, object>
{
    { "source_screen", "shop_ui" },
    { "player_level", 42 }
});

Custom events #

Call Event or EventAsync to send an event that isn’t one of the built-in events:

1
2
3
4
5
6
7
8
9
var properties = new Dictionary<string, object>
{
    { "deviceId", SystemInfo.deviceUniqueIdentifier },
    { "deviceType", SystemInfo.deviceType },
    { "deviceOS", SystemInfo.operatingSystem },
    { "deviceModel", SystemInfo.deviceModel }
};

analytics.Event("deviceInfo", properties);

Create a custom provider #

To send events to a service Hiro doesn’t ship with, implement IAnalyticsSystem. Extend BaseAnalyticsSystem to inherit every convenience method and implement only EventAsync and SetUser:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class MyAnalyticsSystem : BaseAnalyticsSystem, IInitializeSystem
{
    public override Task EventAsync(string name, IReadOnlyDictionary<string, object>? properties)
    {
        if (!Enabled)
        {
            return Task.CompletedTask;
        }

        // Forward the event to your analytics service here.
        return Task.CompletedTask;
    }

    public override void SetUser(IUserSystem userSystem)
    {
        // Identify the current player in your analytics service here.
    }
}

Add your provider to the Systems container like any other, and it joins AnalyticsGroup automatically:

1
systems.Add(new MyAnalyticsSystem());

Verify your events #

To confirm your events arrive, open the dashboard of whichever provider you registered and check its incoming event stream.

For Satori, each event appears in the player’s event history, and its name must match an entry under Taxonomy > Events to be retained. To learn how Satori structures events and turns them into properties and metrics, read Understand events .

See also #