Set up Hiro with Nakama

This guide walks you through installing the Hiro Unity SDK and connecting your Unity game to Nakama. You’ll set up the NakamaSystem, Hiro’s connection manager that handles authentication, sessions, and network connectivity. Once you complete this guide, you’ll be ready to add Hiro’s game systems to your project.

Get started with sample projects

Dive into the codebase right away with hands-on demos and sample projects.

Explore sample projects 🚀

Install the Hiro Unity package #

The Unity client is available from Heroic Labs. Contact us to request a Hiro.unitypackage file.

Once you have the package:

  1. Drag Hiro.unitypackage into your Unity project or use Assets > Import Package > Custom Package.
  2. Set your API Compatibility Level to .NET Standard 2.1:
    • Open Edit > Project Settings
    • Select Player from the sidebar
    • Navigate to Other Settings > Configuration
    • Set API Compatibility Level to .NET Standard 2.1
API Compatibility Level
While Hiro targets .NET Standard 2.1, the .NET Framework option will also work if your project requires it.
  1. If you plan to use in-app purchases, install the In App Purchasing package:
    • Open Window -> Package Manager
    • Switch to Unity Registry
    • Search for “In App Purchasing”
    • Click Install

What is the NakamaSystem? #

The NakamaSystem is one of Hiro’s core systems. It manages your connection to the Nakama server and handles:

  • Authentication: Logs players in using device IDs, email, social accounts, or custom authentication.
  • Session management: Keeps track of auth tokens and automatically refreshes them before they expire.
  • Connection monitoring: Detects when you’re online or offline and reconnects when needed.
  • Client access: Provides your game with access to the Nakama client, session, and socket objects.

The NakamaSystem is the foundation that other Hiro systems (like Economy, Inventory, and Achievements) build on top of. Without it, your game can’t communicate with the Nakama server.

Understand Hiro’s deterministic startup #

Hiro uses a structured approach to initialize your game using a deterministic startup. Instead of scattering initialization code throughout your project, you inherit from the HiroCoordinator class and configure everything in one place.

This approach gives you:

  • Predictable initialization order: Systems start up in the sequence you define.
  • Centralized configuration: All system setup lives in one file.
  • Easy debugging: You can see exactly how your game initializes.
  • Clean architecture: Separates game systems from game logic.

The HiroCoordinator is a MonoBehaviour that stays alive throughout your game’s lifetime. You’ll create your own coordinator class that inherits from it and override the CreateSystemsAsync() method to configure your systems.

Create your game coordinator #

Let’s create the coordinator that will initialize Hiro and set up the NakamaSystem.

Set up the GameObject #

  1. In your Unity scene, create a new empty GameObject:

    • Right-click in the Hierarchy panel.
    • Select Create Empty.
    • Name it GameCoordinator.
  2. Create a new C# script:

    • Right-click in your Project panel.
    • Select Create > C# Script.
    • Name it GameCoordinator.
  3. Attach the script to your GameObject:

    • Select the GameCoordinator GameObject.
    • Drag the GameCoordinator script onto it in the Inspector.

Write the authentication function #

Your authentication function needs to handle logging players in and keeping them logged in between sessions. This function will:

  • Store authentication tokens in PlayerPrefs so users don’t have to log in every time.
  • Check if a saved session exists and is still valid.
  • Refresh sessions before they expire (adding a one-hour buffer).
  • Create new accounts automatically using device authentication.
  • Return cached sessions when offline (won’t work for first-time users).

Open your GameCoordinator script and add the following code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
using System;
using System.Threading.Tasks;
using Hiro;
using Hiro.Unity;
using Nakama;
using UnityEngine;

public class GameCoordinator : HiroCoordinator
{
    private const string PlayerPrefsAuthToken = "nakama.AuthToken";
    private const string PlayerPrefsRefreshToken = "nakama.RefreshToken";
    private const string PlayerPrefsDeviceId = "nakama.DeviceId";

    private static NakamaSystem.AuthorizerFunc NakamaAuthorizerFunc(INetworkMonitor monitor)
    {
        return async client =>
        {
            // Try to restore an existing session from saved credentials
            var authToken = PlayerPrefs.GetString(PlayerPrefsAuthToken);
            var refreshToken = PlayerPrefs.GetString(PlayerPrefsRefreshToken);
            var session = Session.Restore(authToken, refreshToken);

            // Check if we have a valid session that doesn't need refreshing
            var expiredDate = DateTime.UtcNow.AddHours(1);
            if (session != null && (!monitor.Online || !session.HasRefreshExpired(expiredDate)))
            {
                return session;
            }

            // No valid session, so create a new one using device authentication
            var deviceId = PlayerPrefs.GetString(PlayerPrefsDeviceId, SystemInfo.deviceUniqueIdentifier);
            session = await client.AuthenticateDeviceAsync(deviceId);

            // Save the session credentials for next time
            PlayerPrefs.SetString(PlayerPrefsAuthToken, session.AuthToken);
            PlayerPrefs.SetString(PlayerPrefsRefreshToken, session.RefreshToken);
            PlayerPrefs.SetString(PlayerPrefsDeviceId, deviceId);

            if (session.Created)
            {
                Debug.LogFormat("New user account '{0}' created.", session.UserId);
            }

            return session;
        };
    }
}

Initialize NakamaSystem #

Now you need to initialize the NakamaSystem by overriding the CreateSystemsAsync() method. This method will:

  1. Configure your Nakama server connection (host, port, and scheme).
  2. Set up network monitoring to detect when your internet connection goes up or down.
  3. Create the NakamaSystem with your server settings and authentication function.
  4. Listen for session updates and save new tokens when sessions refresh automatically.
  5. Create the systems container that holds all your Hiro systems.

Add the CreateSystemsAsync() method to your GameCoordinator class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
protected override Task<Systems> CreateSystemsAsync()
{
    // Configure your Nakama server connection
    const string scheme = "http";
    const string host = "127.0.0.1";
    const int port = 7350;
    const string serverKey = "defaultkey";

    var logger = new Hiro.Unity.Logger();

    // Set up network connectivity monitoring
    var nakamaProbe = new NakamaClientNetworkProbe(TimeSpan.FromSeconds(60));
    var monitor = new NetworkMonitor(InternetReachabilityNetworkProbe.Default, nakamaProbe);
    monitor.ConnectivityChanged += (_, args) =>
    {
        Instance.Logger.InfoFormat($"Network is online: {args.Online}");
    };

    // Create the NakamaSystem with your configuration
    var nakamaSystem = new NakamaSystem(logger, scheme, host, port, serverKey, NakamaAuthorizerFunc(monitor), nakamaProbe);

    // Listen for session updates and save them
    nakamaSystem.Client.ReceivedSessionUpdated += session =>
    {
        PlayerPrefs.SetString(PlayerPrefsAuthToken, session.AuthToken);
        PlayerPrefs.SetString(PlayerPrefsRefreshToken, session.RefreshToken);
    };

    // Create the systems container and add NakamaSystem
    var systems = new Systems("HiroSystemsContainer", monitor, logger);
    systems.Add(nakamaSystem);

    // You'll add other systems here later (Economy, Inventory, etc.)

    return Task.FromResult(systems);
}

Server connection settings:

  • scheme: Use "http" for local development, "https" for production servers.
  • host: Your Nakama server address (127.0.0.1 for local, your domain for production).
  • port: Nakama’s HTTP API port (default is 7350).
  • serverKey: Your server key from the Nakama configuration (change this in production!).

Complete example #

Here’s the complete script with both functions:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
using System;
using System.Threading.Tasks;
using Hiro;
using Hiro.Unity;
using Nakama;
using UnityEngine;

public class GameCoordinator : HiroCoordinator
{
    private const string PlayerPrefsAuthToken = "nakama.AuthToken";
    private const string PlayerPrefsRefreshToken = "nakama.RefreshToken";
    private const string PlayerPrefsDeviceId = "nakama.DeviceId";

    protected override Task<Systems> CreateSystemsAsync()
    {
        // Nakama server address
        const string scheme = "http";
        const string host = "127.0.0.1";
        const int port = 7350;
        const string serverKey = "defaultkey";

        var logger = new Hiro.Unity.Logger();

        // Set up network connectivity probes
        var nakamaProbe = new NakamaClientNetworkProbe(TimeSpan.FromSeconds(60));
        var monitor = new NetworkMonitor(InternetReachabilityNetworkProbe.Default, nakamaProbe);
        monitor.ConnectivityChanged += (_, args) =>
        {
            Instance.Logger.InfoFormat($"Network is online: {args.Online}");
        };

        var nakamaSystem = new NakamaSystem(logger, scheme, host, port, serverKey, NakamaAuthorizerFunc(monitor), nakamaProbe);

        nakamaSystem.Client.ReceivedSessionUpdated += session =>
        {
            PlayerPrefs.SetString(PlayerPrefsAuthToken, session.AuthToken);
            PlayerPrefs.SetString(PlayerPrefsRefreshToken, session.RefreshToken);
        };

        // Create our systems container
        var systems = new Systems("HiroSystemsContainer", monitor, logger);
        systems.Add(nakamaSystem);

        // Add other Hiro systems here as you need them
        // Example: systems.Add(new EconomySystem(...));

        return Task.FromResult(systems);
    }

    private static NakamaSystem.AuthorizerFunc NakamaAuthorizerFunc(INetworkMonitor monitor)
    {
        return async client =>
        {
            var authToken = PlayerPrefs.GetString(PlayerPrefsAuthToken);
            var refreshToken = PlayerPrefs.GetString(PlayerPrefsRefreshToken);
            var session = Session.Restore(authToken, refreshToken);

            // Add an hour buffer, so we refresh tokens before they expire
            var expiredDate = DateTime.UtcNow.AddHours(1);
            if (session != null && (!monitor.Online || !session.HasRefreshExpired(expiredDate)))
            {
                return session;
            }

            var deviceId = PlayerPrefs.GetString(PlayerPrefsDeviceId, SystemInfo.deviceUniqueIdentifier);
            session = await client.AuthenticateDeviceAsync(deviceId);
            PlayerPrefs.SetString(PlayerPrefsAuthToken, session.AuthToken);
            PlayerPrefs.SetString(PlayerPrefsRefreshToken, session.RefreshToken);
            PlayerPrefs.SetString(PlayerPrefsDeviceId, deviceId);

            if (session.Created)
            {
                Debug.LogFormat("New user account '{0}' created.", session.UserId);
            }

            return session;
        };
    }
}

Test your setup #

  1. Make sure your Nakama server is running.

  2. Press Play in the Unity Editor.

  3. Check the Console for these log messages:

    • Network is online: True (confirms network monitoring works)
    • New user account '[user-id]' created. (on first run only)
    • No errors about connection failures

If you see errors, verify your server connection settings.

Use the NakamaSystem in your game #

Now that the NakamaSystem is initialized, you can access it from any MonoBehaviour in your game.

Access the system #

From any script, use the GetSystem() extension method:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
using Hiro;
using UnityEngine;

public class MyGameScript : MonoBehaviour
{
    async void Start()
    {
        var nakamaSystem = this.GetSystem<NakamaSystem>();
        if (nakamaSystem == null)
        {
            Debug.LogError("NakamaSystem not initialized");
            return;
        }

        // Now you can use the NakamaSystem
        Debug.Log($"Logged in as: {nakamaSystem.Session.Username}");
    }
}

Refresh system data #

Hiro systems cache data locally for performance. Call RefreshAsync() to fetch the latest data from the server:

1
await nakamaSystem.RefreshAsync();

You typically call this when:

  • Your game starts.
  • The player returns from being offline.
  • You want to ensure you have the latest server data.

Access account information #

The NakamaSystem provides quick access to the current user’s account:

1
2
3
4
var account = nakamaSystem.Account;
Debug.Log($"Username: {account.User.Username}");
Debug.Log($"User ID: {nakamaSystem.UserId}");
Debug.Log($"Display name: {account.User.DisplayName}");

Read storage objects #

You can read Nakama storage objects directly through the NakamaSystem:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var storageObject = await nakamaSystem.ReadStorageObjectAsync(
    collection: "player_data",
    key: "preferences",
    userId: nakamaSystem.UserId
);

if (storageObject != null)
{
    Debug.Log($"Data: {storageObject.Value}");
}

Access the Nakama client directly #

For operations not wrapped by the NakamaSystem, access the Nakama client directly:

1
2
3
4
5
6
7
8
9
// Get the raw Nakama client
var client = nakamaSystem.Client;

// Example: Search for users
var result = await client.GetUsersAsync(nakamaSystem.Session, new[] { "user-id-1", "user-id-2" });
foreach (var user in result.Users)
{
    Debug.Log($"Found user: {user.Username}");
}

Next steps #

Now that you’ve set up Hiro with Nakama, next you can:

Initialize other Hiro systems to add economy, inventory, achievements, and other game features to your game.

Explore sample projects for complete ready-to-run examples that demonstrate Hiro in action.