Unity client guide

The official Unity client handles all communication in realtime with the server. It implements all features in the server and is compatible with Unity 2017 or newer. To work with our Unity client you'll need to install and setup Unity engine.

With the Nakama 2.x release we've split out the Unity client into a lower level client for .NET and a new wrapper client which will require Unity 2017 or greater.

  1. Nakama .NET client library: A lower level .NET library that you can use directly inside your Unity Project. This will give you complete control over sending and receiving responses from the server, updating your scene and more. You can look at the source code on the GitHub repo.
  2. Nakama Unity client library: A wrapper around the .NET client library that integrates more tightly into the Unity Engine, and will take of client reconnection, messag queuing and interacting with Unity lifecycle events. This is currently going through final API design and will be made available soon to the public.

Nakama 2.x release

With the Nakama 2.x release we've split out the Unity client into a lower level client for .NET and a new wrapper which will require Unity 2017 or greater. The new Unity client is not released yet but will be announced on our blog and Twitter. In the meantime you can use the .NET client directly and is available here.

Download

The client is available on the Unity Asset store and also on GitHub releases. You can download "Nakama.unitypackage" which contains all source code and DLL dependencies required in the client code.

Install and setup

When you've downloaded the "Nakama.unitypackage" file you should drag or import it into your Unity editor project to install it. In the editor create a new C# script via the Assets menu with "Assets > Create > C# Script" and create an INClient.

The client object is used to execute all logic against the server.

using Nakama;
using System.Collections;
using UnityEngine;

public class NakamaSessionManager : MonoBehaviour {
  void Start() {
    var client = new Client("defaultkey", "127.0.0.1", 7350, false);
  }

  void Update() {
  }
}

Unity uses an entity component system (ECS) which makes it simple to share the client across game objects. Have a read of Controlling GameObjects Using Components for examples on how to share a C# object across your game objects.

Authenticate

With a client object you can authenticate against the server. You can register or login a user with one of the authenticate options.

To authenticate you should follow our recommended pattern in your client code:

   1. Build an instance of the client.

var client = new Client("defaultkey", "127.0.0.1", 7350, false);

   2. Authenticate a user. By default, Nakama will try and create a user if it doesn't exist.

Use the following code to store the session:

const string email = "hello@example.com";
const string password = "somesupersecretpassword";
var session = await client.AuthenticateEmailAsync(email, password);
PlayerPrefs.SetString("nk.session", session.AuthToken);
Debug.LogFormat("Authenticated successfully. User id {0}:", session.user_id);

In the code above we use AuthenticateEmailAsync but for other authentication options have a look at the code examples.

A full example class with all code above is here.

Send messages

When a user has been authenticated a session is used to connect with the server. You can then send messages for all the different features in the server.

This could be to add friends, join groups, or submit scores in leaderboards. You can also execute remote code on the server via RPC.

The server also provides a storage engine to keep save games and other records owned by users. We'll use storage to introduce how messages are sent.

var object = new WriteStorageObject = {
  "collection" = "collection",
  "key" ="key1",
  "value" = "{\"jsonKey\": \"jsonValue\"}"
};
const storageWriteAck = await client.WriteStorageObjectsAsync(session, objects);
Debug.LogFormat("Storage write was successful: {0}", storageWriteAck);

Have a look at other sections of documentation for more code examples.

Realtime data exchange

You can connect to the server over a realtime WebSocket connection to send and receive chat messages, get notifications, and matchmake into a multiplayer match. You can also execute remote code on the server via RPC.

You first need to create a realtime socket to the server:

// Updated example TBD

Then proceed to join a chat channel and send a message:

// Updated example TBD

You can find more information about the various chat features available here.

Handle events

A client socket has event listeners which are called on various events received from the server.

// Updated example TBD

Some events only need to be implemented for the features you want to use.

Callbacks Description
ondisconnect Handles an event for when the client is disconnected from the server.
onerror Receives events about server errors.
onnotification Receives live in-app notifications sent from the server.
onchannelmessage Receives realtime chat messages sent by other users.
onchannelpresence It handles join and leave events within chat.
onmatchdata Receives realtime multiplayer match data.
onmatchpresence It handles join and leave events within realtime multiplayer.
onmatchmakermatched Received when the matchmaker has found a suitable match.
onstatuspresence It handles status updates when subscribed to a user status feed.
onstreampresence Receives stream join and leave event.
onstreamdata Receives stream data sent by the server.

Logs and errors

The server and the client can generate logs which are helpful to debug code. To log all messages sent by the client you can enable "Trace" when you build an "IClient".

var client = new Client("defaultkey");
#if UNITY_EDITOR
  client.Trace(true);
#endif

The #if preprocessor directives is used so trace is only enabled in Unity editor builds. For more complex directives with debug vs release builds have a look at Platform dependent compilation.

Full example

An example class used to manage a session with the Unity client.

using Nakama;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class NakamaSessionManager : MonoBehaviour {
  private IClient _client;
  private ISession _session;

  private Queue<IEnumerator> _executionQueue;

  public NakamaSessionManager() {
    _client = new Client("defaultkey");
    _executionQueue = new Queue<IEnumerator>(1024);
  }

  private async void Awake() {
    _session = RestoreSession();
    if (_session == null) {
      Authenticate();
    }
  }

  private Session RestoreSession() {
    // Lets check if we can restore a cached session.
    var sessionString = PlayerPrefs.GetString("nk.session");
    if (string.IsNullOrEmpty(sessionString)) {
      return null; // We have no session to restore.
    }

    var session = new Session(sessionString);
    if (session.IsExpired) {
      return null; // We can't restore an expired session.
    }

    return session;
  }

  private async void Authenticate() {
    // See if we have a cached id in PlayerPrefs.
    var id = PlayerPrefs.GetString("nk.id");
    if (string.IsNullOrEmpty(id)) {
      // We'll use device ID for the user. See other authentication options.
      id = SystemInfo.deviceUniqueIdentifier;
      // Store the identifier for next game start.
      PlayerPrefs.SetString("nk.id", id);
    }

    // Use whichever one of the authentication options you want.
    _session = await client.AuthenticateDeviceAsync($"{id}");
    Debug.LogFormat("Session: '{0}'.", session.AuthToken);
  }

  private void Update() {
    lock (_executionQueue) {
      for (int i = 0, len = _executionQueue.Count; i < len; i++) {
        StartCoroutine(_executionQueue.Dequeue());
      }
    }
  }

  private void OnApplicationQuit() {}

  private void Enqueue(Action action) {
    lock (_executionQueue) {
      _executionQueue.Enqueue(ActionWrapper(action));
      if (_executionQueue.Count > 1024) {
        Debug.LogWarning("Queued actions not consumed fast enough.");
        _client.Disconnect();
      }
    }
  }

  private IEnumerator ActionWrapper(Action action) {
    action();
    yield return null;
  }
}