# Set up Hiro with Nakama

**URL:** https://heroiclabs.com/docs/hiro/unity/getting-started/nakama-system/
**Summary:** Learn how to initialize and use the `NakamaSystem` in Hiro.
**Keywords:** set up hiro with nakama, nakama system
**Categories:** hiro, unity, nakama-system

---


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

{{<sample-projects-banner>}}

## Install the Hiro Unity package

The Unity client is available from Heroic Labs. [Contact us](mailto:contact@heroiclabs.com) 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

{{< note "important" "API Compatibility Level" >}}
While Hiro targets .NET Standard 2.1, the .NET Framework option will also work if your project requires it.
{{< / note >}}

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

```csharp
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:

```csharp
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:

```csharp
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:

```csharp
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:

```csharp
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:

```csharp
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`:

```csharp
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:

```csharp
// 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](../../../../sample-projects/) for complete ready-to-run examples that demonstrate Hiro in action.
