Amazon GameLift Fleet Management using the Nakama GameLift Plugin #

This guide details the integration process between Nakama’s powerful social matchmaking system and Amazon GameLift’s session management, enabling developers to efficiently scale multiplayer games.

Follow along to learn how to configure AWS services, set up SQS and SNS queues, install and configure the Nakama-GameLift plugin, and manage game sessions. By the end, you’ll be equipped to deploy a robust multiplayer environment powered by Nakama and Amazon GameLift.

Prerequisites #

Nakama Version
The Nakama-GameLift plugin is compatible with Nakama 3.21 and above.

Before proceeding ensure that you have:

Architecture overview #

An overview of how Nakama orchestrates the GameLift fleet.
An overview of how Nakama orchestrates the GameLift fleet.

Maintaining state synchronization #

In order to facilitate integration between Nakama and Amazon GameLift, a number of components need to be setup and configured within your AWS account: an AWS GameLift Fleet with a Placement Queue, an SNS Topic, and an SQS Queue.

Additionally, it is required that each GameLift Game Session updates Nakama on certain status changes to allow it to maintain it’s own state representation of the active Amazon GameLift instances. This is required so that Nakama knows in near real-time the current state of your fleet and all of the instances within it, allowing this data to be queryable and drive your matchmaking needs.

Important!
These updates to Nakama are to be sent via two RPC endpoints (Update/Delete) exposed by the Nakama-GameLift Plugin. For further information please refer to the section titled “Notifying Nakama of game session updates”.

Creating the Headless Unity server #

This guide covers just the required steps to get your headless Unity server configured to work with Nakama and GameLift. It does not cover synchronizing game state between the headless server and the client, for this please see the documentation for your chosen networking framework (e.g. Nakama, Unity Netcode for GameObjects, Mirror, etc).

Installing the Nakama SDK #

To install the Nakama SDK into the Unity project:

  1. Download the latest Nakama Unity SDK package.
  2. Double-click the Nakama.unitypackage and install it in your Unity project.

Install the GameLift Managed Servers SDK #

To install the GameLift SDK into the Unity project:

  1. Download the GameLift Managed Servers SDK v5.0.0 or higher.
  2. Extract the zip file.
  3. Create a Plugins folder in your Unity project’s Assets folder.
  4. Copy the .DLL files from the extracted zip’s GameLift-CSharp-ServerSDK-5.0.0/src/GameLiftServerSDK/bin/Release folder into it. (You may need to remove Newtonsoft.Json.dll if it is already in use in your project).

Initialize GameLift #

Where you choose to initialize GameLift depends on your game. It is recommended that you place the following code snippets somewhere within the initialization script of your game.

It is important that during the OnProcessTerminate lifecycle event, Nakama must be informed that the game session is ending via the RPC defined by the Nakama-GameLift plugin as shown in the code example below.

  1. Create an InitializeGameLift method that will handle initializing the GameLift lifecycle events:
 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
private void InitializeGameLift()
{
  Debug.Log("Initializing GameLift");

  // Pass null values here as these are configured automatically by AWS GameLift; only used when using GameLift Anywhere
  var serverParameters = new ServerParameters(null, null, null, null, null);

  // Initialize the GameLift SDK
  var initOutcome = GameLiftServerAPI.InitSDK(serverParameters);

  if (!initOutcome.Success)
  {
    Debug.LogError($"Failed to initialize GameLift: {initOutcome.Error}");
    return;
  }

  // Configure process callbacks (methods defined below)
  var processParameters = new ProcessParameters(
    OnStartGameSession,
    OnUpdateGameSession,
    OnProcessTerminate,
    OnHealthcheck,
    7778, // Port depends on your networking framework (Netcode for GameObjects uses 7778)
    new LogParameters(new List<string>
    {
      "/local/game/logs/server.log" // Define where GameLift should grab the logs from
    })
  );

  // Tell GameLift the server is ready
  var processReadyOutcome = GameLiftServerAPI.ProcessReady(processParameters);

  if (!processReadyOutcome.Success)
  {
    Debug.LogError($"GameLift ProcessReady failed: {processReadyOutcome.Error}");
  }
  else
  {
    Debug.Log("GameLift initialized successfully");
  }
}
  1. Create the methods to handle the various GameLift callbacks defined above, making sure to call the appropriate Nakama RPCs to maintain game session state integrity between Nakama and Amazon GameLift.
 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
// Called when GameLift creates a game session and sends an activation request, here you can get information about the particular game session
private void OnStartGameSession(GameSession gameSession)
{
  Debug.Log($"Game session started.\nIP: {gameSession.IpAddress}\nPort: {gameSession.Port}");

  // You must call ActivateGameSession once the server is ready to accept incoming player connections
  GameLiftServerAPI.ActivateGameSession();

  // It is recommended that you cache a copy of the game session so that you can reference metadata later on.
  _gameSession = gameSession;

  Debug.Log("Game session activated");
}

// Called when a game session is updated
private void OnUpdateGameSession(UpdateGameSession updateGameSession)
{
  // Not used in this example
}

// Called before shutting down an instance hosting this server
private void OnProcessTerminate()
{
  // Tell Nakama that the GameLift session is ending
  var payload = new Dictionary<string, string> {{ "id", GameLiftServerAPI.GetGameSessionId().Result}};
  _nakamaClient.RpcAsync(_httpKey, "delete_instance_info", payload.ToJson());

  // Tell GameLift this process is ending
  GameLiftServerAPI.ProcessEnding();
}

// Called periodically (every ~60 seconds) to check the health of the server
private bool OnHealthCheck()
{
  // Return true here to confirm the server is healthy
  return true;
}
  1. Call the InitializeGameLift method from the Start method of your chosen script:
1
2
3
4
private void Start()
{
  InitializeGameLift();
}

Accepting GameLift player sessions #

Any player session created in GameLift needs to be validated by the server. This can be achieved by calling the ApprovePlayerSession GameLift API method. You should call this when a player has connected.

1
2
3
// Accept the player session in GameLift and update Nakama
var acceptOutcome = GameLiftServerAPI.AcceptPlayerSession(playerSessionId);
UpdateNakamaGameSessionData();

Removing GameLift player sessions #

When a player disconnects from the server, the GameLift player session should be removed.

1
2
3
// Remove the player session in GameLift and update Nakama
GameLiftServerAPI.RemovePlayerSession(playerSessionId);
UpdateNakamaGameSessionData();

Notifying Nakama of game session updates #

When a player connects or disconnects from the game server, or when game session meta data has changed, Nakama must be informed via the Update RPC that the Nakama-GameLift plugin exposes.

 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
public void UpdateNakamaGameSessionData()
{
  // Class used as a payload to the Nakama Fleetmanager Update RPC
  private class NakamaUpdateRequest
  {
      public string id;
      public int player_count;
      public Dictionary<string, object> metadata;
  }

  // Get the total current player count from GameLift
  var req = new DescribePlayerSessionsRequest()
  {
    GameSessionId = gameSessionId
  };

  var sessions = GameLiftServerAPI.DescribePlayerSessions(req);
  if (sessions.Error != null)
  {
    Debug.Log($"Error getting sessions from GameLiftServerAPI: {sessions.Error?.ErrorMessage}");  
  }

  var playerCount = 0;
  foreach (var s in sessions.Result.PlayerSessions)
  {
    if (s.Status == PlayerSessionStatus.ACTIVE || s.Status == PlayerSessionStatus.RESERVED)
    {
      playerCount++;
    }
  }

  // Notify Nakama of the updated information about this game session.
  // These values can be retrieved from the Game Session object cached earlier if required.
  // If these values are omitted, the existing values will persist.
  var metadata = new Dictionary<string, object> {
    { "GameSessionData", "<game_session_data>" },
    { "GameSessionName", "<game_session_name>" },
    { "GameProperties": new Dictionary<string, string> {
      { "key1", "value1" },
      { "key2", "value2" }
    }
  };

  var payload = new NakamaUpdateRequest()
  {
    id = GameLiftServerAPI.GetGameSessionId().Result,
    player_count = playerCount,
    metadata = metadata
  };

  _nakamaClient.RpcAsync(_httpKey, "update_instance_info", payload.ToJson());
}

Ending the GameLift session #

If your headless server process exits for any reason (e.g. after a match) you should explicitly tell GameLift that the process is ending to ensure the game session ends appropriately.

1
GameLiftServerAPI.ProcessEnding();

You should also explicitly call Destroy when the application quits.

1
2
3
4
private void OnApplicationQuit()
{
  GameLiftServerAPI.Destroy();
}

Creating the build #

Create a build that you can upload to AWS.

  1. Go to the File -> Build Settings menu.
  2. Choose the Dedicated Server option.
  3. For Target Platform select either Windows or Linux.
  4. Build your project.
  5. Zip up the contents of your build and name it appropriately (e.g. 1.0.0.zip) Do not include the _DoNotShip folder.

Configuring AWS Services #

The AWS requirements for setting up the GameLift service are as follows:

  • An SNS Topic for publishing events to
  • An SQS Queue for reading events from
  • An S3 bucket for storing server builds
  • An IAM Role to allow GameLift access to the S3 bucket
  • An IAM Role to allow the Nakama server to authenticate and access GameLift APIs
  • The GameLift service including placement queue, builds, fleets, and an alias

Creating the Topic in Amazon Simple Notification Service (SNS) #

A Topic must be created for notifications/events to be published to via Amazon Simple Notification Service (SNS). This Topic will be used by the Fleet Placement Queue to publish placement status updates. Nakama will receive these events through a Simple Queue System (SQS) subscribed to this topic.

To create the Topic:

  1. Navigate to the Amazon SNS Console.
  2. Go to Topics and click Create topic.
  3. Choose FIFO for the Type.
  4. For the Name enter: fleet-game-session-placement.fifo.
  5. Enter a Display Name such as Fleet Game Session Placement.
  6. Make sure you configure your Access Policy to allow the SNS Topic to be published to from GameLift.
 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
{
  "Version": "2008-10-17",
  "Id": "__default_policy_ID",
  "Statement": [
    {
      "Sid": "__default_statement_ID",
      "Effect": "Allow",
      "Principal": {
        "AWS": "*"
      },
      "Action": [
        "SNS:GetTopicAttributes",
        "SNS:SetTopicAttributes",
        "SNS:AddPermission",
        "SNS:RemovePermission",
        "SNS:DeleteTopic",
        "SNS:Subscribe",
        "SNS:ListSubscriptionsByTopic",
        "SNS:Publish"
      ],
      "Resource": "arn:aws:sns:your_region:your_account:your_topic_name",
      "Condition": {
        "StringEquals": {
          "AWS:SourceAccount": "your_account"
        }
      }
    },
    {
      "Sid": "__console_pub_0",
      "Effect": "Allow",
      "Principal": { 
        "Service": "gamelift.amazonaws.com" 
      },
      "Action": "sns:Publish",
      "Resource": "arn:aws:sns:your_region:your_account:your_topic_name",
      "Condition": {
        "ArnLike": {
          "aws:SourceArn": "arn:aws:gamelift:your_region:your_account:gamesessionqueue/your_queue_name"
        }
      }
    }
  ]
}
  1. Click Create topic and note down the ARN that you are given.

For more information please see the official AWS documentation for setting up an SNS topic.

Creating the Queue in Amazon Simple Queue Service (SQS) #

This queue will subscribe to the SNS topic created above in order to make GameLift fleet events available to Nakama. This is necessary as SNS events are unavailable outside of AWS without first pushing them to an accessible SQS queue.

To create the SQS queue that will be read from via Nakama:

  1. Navigate to Amazon SQS.
  2. Click on Create queue.
  3. For Type choose FIFO.
  4. For Name enter: fleet-placement-events.fifo.
  5. Configuration and Encryption can be left at default values.
  6. Ensure the Access policy is configured to allow access to the SNS topic.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
  "Version": "2012-10-17",
  "Id": "__default_policy_ID",
  "Statement": [
    {
      "Sid": "__owner_statement",
      "Effect": "Allow",
      "Principal": {
        "Service": "sns.amazonaws.com"
      },
      "Action": "SQS:*",
      "Resource": "arn:aws:sqs:your_region:your_account:fleet-placement-events.fifo"
    }
  ]
}
  1. Click Create queue.
  2. Click Subscribe to Amazon SNS Topic and choose the fleet-game-session-placement.fifo topic created earlier.

You can confirm that everything is configured correctly by verifying that the subscription to the SNS Topic shows up under the SNS Subscriptions section.

Creating the S3 bucket #

There are two options for uploading game server builds to AWS GameLift: via the CLI or an S3 bucket. For simplicity this guide uses the S3 approach.

  1. Navigate to the S3 service.
  2. Create a new bucket with a unique name (such as my-organisation-gamelift-builds) and give it default permissions.
  3. Upload the server build Zip file you created in the previous section.
Important!
When creating the S3 bucket, ensure that it is created in the same region as the GameLift Fleet you intend to create.

Creating the IAM Role for S3 access #

For GameLift to be able to access the S3 bucket storing the server builds it needs to be given the appropriate permissions via a Role.

  1. Navigate to the IAM service and go to Policies.
  2. Create a new Policy called GameLiftS3AccessPolicy.
  3. For permissions give it the following JSON (where the resource name matches your S3 bucket ARN):
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Statement1",
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-organisation-gamelift-builds/*"
    }
  ]
}
  1. Navigate to Roles.
  2. Create a new Role called GameLiftS3AccessRole.
  3. Attach the GameLiftS3AccessPolicy to the role via the Permissions tab.
  4. In the Trust relationships tab edit the Trust Policy with the following:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Statement1",
      "Effect": "Allow",
      "Principle": {
        "Service": "gamelift.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

This role will now allow the AWS GameLift service to access the S3 bucket and download the headless server builds stored there when creating a new GameLift Build.

Creating the IAM Role for GameLift access #

In order for Nakama to access GameLift services there needs to be an IAM User created with an access token. This access token (and secret access token) will be passed to Nakama via environment variables so that it can interact with the GameLift APIs to search for and create new game sessions.

  1. In the IAM section navigate to Policies.
  2. Create a new Policy called GameLiftAPIAccess.
  3. For the permissions JSON enter the following:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Statement1",
      "Effect": "Allow",
      "Action": [
        "gamelift:CreateGameSession",
        "gamelift:DescribeGameSessionDetails",
        "gamelift:UpdateGameSession",
        "gamelift:CreatePlayerSession",
        "gamelift:DescribePlayerSessions",
        "gamelift:SearchGameSessions",
        "gamelift:GetGameSessionLogUrl",
        "gamelift:CreatePlayerSessions",
        "gamelift:DescribeGameSessions"
      ],
      "Resource": "*"
    }
  ]
}

This Policy will allow the IAM User to interact with various GameLift APIs. You can add or remove API functions from this policy depending on your needs.

  1. Navigate to the Users section.
  2. Create a new User called NakamaGameLiftUser.
  3. Attach the GameLiftAPIAccess policy under the Permissions tab.

Now create access tokens for this user:

  1. Go to the Security Credentials tab.
  2. Under the Access Keys section click Create access key.
  3. Follow the instructions and then note down the Access Key and Secret Access Key for later.

Creating a GameLift Build #

The first step in launching a fleet of headless game servers with GameLift is to create a Build. A Build defines the executable binary that GameLift will spin up as part of a fleet. You should specify the operating system that you expect the build to run on as well as the GameLift Server SDK version your build is using.

  1. Navigate to the GameLift service and go to the Builds section.
  2. Give your build a Name (e.g. my-server-1.0.0).
  3. Specify a Version number (e.g. 1.0.0).
  4. Choose the Operating System of your build (e.g. Linux 2).
    • Note that the Linux option does not support the 5.0.0 server SDK
  5. Choose 5.0.0 for the Server SDK Version.
  6. Under the Game Server Build option, choose your server build Zip file.
  7. From the IAM Role dropdown choose the GameLiftS3AccessRole.
  8. Click Create to create your build.

Creating a GameLift Fleet #

Once you have created a build you’re ready to create a fleet. A fleet is a group of game servers that can be configured to scale up and down as demand calls for it.

To create your fleet:

  1. Navigate to the Fleets section and click Create Fleet.
  2. Choose Managed EC2 for Compute Type.
  3. Give your fleet a Name (e.g. build_1_0_0_fleet_1).
  4. Keep the Binary Type as Build.
  5. Select the Build you just created from the Build dropdown.
  6. Leave the Additional Details section blank and click Next.
  7. Choose a Location for your Fleet and click Next.
  8. For Instance Type choose On Demand.
  9. Select the most appropriate instance type for your needs (for this guide the recommendation is c4.large) and click Next .
  10. For Launch Path enter the path to your server binary in the Zip file you uploaded (e.g. server.x86_64)
  11. For Launch parameters enter at least the following:
1
-batchmode -nographics -logFile /local/game/logs/server.log
  1. Go to the EC2 Port Settings section.
  2. Add an entry for both TCP and UDP with the following:
  • Port Range: enter 7000-8000
  • IP Address Range: enter 0.0.0.0/0
  1. Click on Next.
  2. Leave the Tags section blank unless you wish to add them to your fleet.
  3. Finally click on Submit.

Once your fleet is created it will go through several stages which can be viewed via the Events tab. If any errors occur during these stages you will be notified here.

When your fleet status changes to FLEET_STATE_ACTIVE it will begin scaling up your fleet as per the scaling configuration, which by default will spin up 1 instance.

Creating a GameLift Alias #

An alias is a pointer to a fleet that can be changed at any point. This allows you to dynamically change which fleet a client connects to without needing to rebuild and republish the client itself.

To create an alias:

  1. Navigate to the Aliases section and click Create Alias.
  2. Give your alias a Name (e.g. alias-1).
  3. Select the Simple option for Routing Type.
  4. Select the Fleet the alias should point to.
  5. Click Create and take note of the Alias ID.

Creating the GameLift Game Session Placement Queue #

In order for Nakama to be able to effectively create game sessions within Amazon GameLift, a game session placement queue must be configured. Requests made to this queue signals to Amazon GameLift that a new game session is required and allows them to be asynchronously processed. Whenever a new game session is required, Nakama will make a request to this queue, and once the request is fulfilled by Amazon GameLift, a callback will be triggered within Nakama containing the information about the newly created game session.

To create a game session placement queue:

  1. Navigate to Amazon GameLift.
  2. Click on Queues.
  3. Click Create queue.
  4. For Name enter: game-session-placement
  5. Change the Timeout value to 30 (This ensures Nakama does not wait for more than 30 seconds when requesting a new game session).
  6. For Destination order, be sure to select the Region of the Alias created earlier and then select the alias name from the dropdown.
  7. For Event notifications, choose the Region of the SNS Topic created earlier and then choose fleet-game-session-placement.fifo.

Installing the Nakama-GameLift Plugin #

This guide assumes you:

  • Have a Nakama server project up and running using the Go runtime. Follow the Introduction to Nakama Go Runtime documentation to get started, if needed.
  • Are familiar with hosting a Nakama instance. See our Heroic Cloud documentation for details on how to launch an instance of Nakama in the cloud.

This guide focuses on implementing the necessary functionality to communicate with AWS GameLift in order to search for and create game sessions and receive match results.

The Nakama-GameLift plugin provides an Amazon GameLift implementation of Nakama’s Fleet Manager interface and is available as an Open-Source plugin via GitHub and

To install the plugin in your Nakama project, run the following command in the project folder:

1
go get github.com/heroiclabs/nakama-gamelift/fleetmanager

Once installed, a new FleetManager instance can be created within the InitModule function.

 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
// It is recommended that you pass in the necessary AWS configuration values via ENV vars.
// e.g.
// 
// env, ok := ctx.Value(runtime.RUNTIME_CTX_ENV).(map[string]string)
// if !ok {
//   return fmt.Errorf("expects env ctx value to be a map[string]string")
// }
// awsAccessKey, ok := env["GL_AWS_ACCESS_KEY"]
// if !ok {
//   return runtime.NewError("missing GL_AWS_ACCESS_KEY environment variable", 3)
// }

// Create the GameLift configuration object using appropropriate string values for AWS resources.
cfg := fleetmanager.NewGameLiftConfig(awsAccessKey, awsSecretAccessKey, awsRegion, awsAliasId, awsPlacementQueueName, awsGameLiftPlacementEventsQueueUrl)

// Create a new Fleet Manager instance.
fm, err := fleetmanager.NewGameLiftFleetManager(ctx, logger, db, initializer, nk, cfg)
if err != nil {
    return err
}

// Register the Fleet Manager with Nakama.
if err = initializer.RegisterFleetManager(fm); err != nil {
    logger.WithField("error", err).Error("failed to register aws gamelift fleet manager")
    return err
}

To access the fleet manager elsewhere after it has been registered:

1
fm := nk.GetFleetManager()

Using the Fleet Manager API #

The Fleet Manager API exposes several functions that can be used to Create, List, Retrieve, Join and Delete game sessions within Amazon GameLift. These are covered below. For full documentation please visit the nakama-gamelift repository on GitHub.

Create #

The Create function is used to create a new game session within the Amazon GameLift fleet. The process is asynchronous, so the function takes a callback as part of the signature. This callback is invoked once the creation process succeeds, times-out or fails. The callback can be used to notify any interested parties on the status of the creation process:

 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
var callback runtime.FmCreateCallbackFn = func(status runtime.FmCreateStatus, instanceInfo *runtime.InstanceInfo, sessionInfo []*runtime.SessionInfo, metadata map[string]any, createErr error) {
// createErr is not nil only if status is != runtime.CreateSuccess.
// the original AWS Placement Event can be retrieved from `metadata` under the 'event' key.
switch status {
    case runtime.CreateSuccess:
        // Create was successful, instanceInfo contains the instance information for player connection.
        // sessionInfo contains Player Session info if a list of userIds is passed to the Create function.
        info, _ := json.Marshal(instanceInfo)
        logger.Info("GameLift instance created: %s", info)
        // Notify any interested party. 
        return
    case runtime.CreateTimeout:
        // AWS GameLift was not able to successfully create the placed Game Session request within the timeout
        // (configurable in the placement queue).
        // The client should be notified to either reattempt to find an available Game Session or retry creation.
        info, _ := json.Marshal(instanceInfo)
        logger.Info("GameLift instance created: %s", info)
        // Notify any interested party. 
        return
    default:
        // The request failed to be placed.
        logger.WithField("error", createErr.Error()).Error("Failed to create GameLift instance")
        // Notify any interested party.
        return
    }
}

maxPlayers := 10 // Maximum number of players that will be able to connect to the Game Session
playerIds := []string{userId} // Optional - Reserves a Player Session for each userId. The reservation expires after 60s if the client doesn't connect.
metadata := map[string]any // Optional - Metadata containing GameProperties or 
err = fm.Create(ctx, maxPlayers, playerIds, metadata, callback)

List #

The list function enables querying of existing game sessions and uses the same query syntax found elsewhere in Nakama:

1
2
3
4
query := "+value.playerCount:2" // Query to list Game Sessions currently containing 2 Player Sessions. An empty query will list all Game Sessions.
limit := 10 // Number of results per page (does not apply if a query is != ""
cursor := "" // Pagination cursor
instances, nextCursor, err := fm.List(ctx, query, limit, cursor)

A note on querying game sessions #

The following properties can be queried for game sessions:

  • values.create_time a date value
  • values.player_count a numeric value
  • values.metadata.GameSessionData a string value
  • values.metadata.GameSessionName a string value
  • values.metadata.GameProperties a string:string dictionary value

An example query which looks for game sessions with a name prefix of season and a game mode of freeforall would be:

1
query := "+values.metadata.GameSessionName:/(season).+/ +values.metadata.GameProperties.mode:freeforall"

Get #

The get function gets information for a specific game session:

1
2
id := "<Game Session ARN>"
instance, err := fm.Get(ctx, id)

Join #

The join function reserves a seat in an existing game session and retrieves the corresponding player session data needed for a client to connect:

1
2
3
4
5
6
id := "<game session ARN>"
userIds := []string{userId}
metadata := map[string]string{
  userId: "<player data>",
} // metadata is optional. It can be used to set an arbitrary string that can be retrieved from the Game Session. Each key needs to match an userId present in userIds, otherwise it is ignored.
joinInfo, err := fm.Join(ctx, id string, userIds []string, metadata map[string]string)

Delete #

Delete should be used to delete an InstanceInfo data regarding a Game Session that was terminated on GameLift:

1
2
id := "<game session ARN>"
err := fm.Delete(ctx, id)

Example: Finding/Creating a Game Session via Nakama Matchmaking #

One common use case for creating an Amazon GameLift game session via Nakama is to combine it with Nakama’s powerful social matchmaking features. In the following client/server code examples, a player will use Nakama’s matchmaking to find a match from their friends list and Nakama will then find or create an Amazon GameLift game session in the configured fleet before sending the connection details to the matched players.

  1. On the server, register a MatchmakerMatched hook that will find or create an Amazon GameLift game session for the matched players and, when the game session is available, send the connection details to the matched players.
  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
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// Define notification codes
notificationConnectionInfo := 111
notificationCreateTimeout := 112
notificationCreateFailed := 113

// Register the Matchmaker Matched hook to find/create a GameLift game session
if err := initializer.RegisterMatchmakerMatched(func(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, entries []runtime.MatchmakerEntry) (string, error) {
		// Define the maximum amount of players per game
    maxPlayers := 10

    // Find existing GameLift game sessions
    fm := nk.GetFleetManager()
    query := fmt.Sprintf("+values.playerCount:<=%v", maxPlayers - len(entries)) // Assuming a max match size of 10, find a match that has enough player spaces for the matched players
    limit := 1
    cursor := ""
    instances, _, err := fm.List(ctx, query, limit, cursor)
    if err != nil {
      logger.WithField("error", err.Error()).Error("failed to list gamelift instances")
		  return "", ErrInternalError
    }

    // If an instance was found, tell GameLift the players are joining the instance and then notify players with the connection details
    if len(instances) > 0 {
      instance := instances[0]

      userIds := make([]string, 0, len(entries))
      for _, entry := range entries {
        userIds = append(userIds, entry.Presence.UserId)
      }

      joinInfo, err := fm.Join(ctx, instance.Id, userIds, nil)
      if err != nil {
        logger.WithField("error", err.Error()).Error("failed to join gamelift instance")
			  return "", ErrInternalError
      }

      // Send connection details notifications to players
      for _, userId := range userIds {
        // Get the user's GameLift session ID
        sessionId := ""
        for _, sessionInfo := range joinInfo.SessionInfo {
          if sessionInfo.UserId == userId {
            sessionId = sessionInfo.SessionId
            break
          }
        }

        subject := "connection-info"
        content := map[string]interface{}{
          "IpAddress": joinInfo.InstanceInfo.IpAddress,
          "DnsName": joinInfo.InstanceInfo.DnsName,
          "Port": joinInfo.InstanceInfo.Port,
          "SessionId": sessionId,
        }
        code := notificationConnectionInfo
        senderId := "" // System sender
        persistent := false
        nk.NotificationSend(ctx, userId, subject, content, code, senderId, persistent)
      }

      // We don't pass a Match ID back to the user as we are not creating a Nakama match
      return "", nil
    }

    // If no instance was found, ask GameLift to create a new one and, when it is available, notify the players with the connection details
    // First establish the creation callback
    var callback runtime.FmCreateCallbackFn = func(status runtime.FmCreateStatus, instanceInfo *runtime.InstanceInfo, sessionInfo []*runtime.SessionInfo, metadata map[string]any, createErr error) {
      switch status {
      case runtime.CreateSuccess:
        joinInfo, _ := json.Marshal(instanceInfo)
        logger.Info("GameLift instance created: %s", joinInfo)

        // Send connection details notifications to players
        for _, userId := range userIds {
          // Get the user's GameLift session ID
          sessionId := ""
          for _, sessionInfo := range joinInfo.SessionInfo {
            if sessionInfo.UserId == userId {
              sessionId = sessionInfo.SessionId
              break
            }
          }

          subject := "connection-info"
          content := map[string]interface{}{
            "IpAddress": joinInfo.InstanceInfo.IpAddress,
            "DnsName": joinInfo.InstanceInfo.DnsName,
            "Port": joinInfo.InstanceInfo.Port,
            "SessionId": sessionId,
          }
          code := notificationConnectionInfo
          senderId := "" // System sender
          persistent := false
          nk.NotificationSend(ctx, userId, subject, content, code, senderId, persistent)
        }
        return
      case runtime.CreateTimeout:
        logger.WithField("error", createErr.Error()).Error("Failed to create GameLift instance, timed out")

        // Send notification to client that game session creation timed out
        for _, userId := range userIds {
          subject := "create-timeout"
          content := map[string]interface{}{}
          code := notificationCreateTimeout
          senderId := "" // System sender
          persistent := false
          nk.NotificationSend(ctx, userId, subject, content, code, senderId, persistent)
        }
      default:
        logger.WithField("error", createErr.Error()).Error("Failed to create GameLift instance")

        // Send notification to client that game session couldn't be created
        for _, userId := range userIds {
          subject := "create-timeout"
          content := map[string]interface{}{}
          code := notificationCreateFailed
          senderId := "" // System sender
          persistent := false
          nk.NotificationSend(ctx, userId, subject, content, code, senderId, persistent)
        }
        return
      }
    }

    // Game session metadata as described by AWS GameLift Documentation
    // https://docs.aws.amazon.com/gamelift/latest/apireference/API_GameSession.html
    // These properties can be queried by the Fleet Manager when listing existing game sessions.
    // These properties can also be updated by calling the Update RPC from the headless server.
    metadata := map[string]interface{}{
      "GameSessionData": "<game_session_data>",
      "GameSessionName": "<game_session_name>",
      "GameProperties": map[string]string {
        "key1": "value1",
        "key2": "value2",
      },
    }

    if err = fm.Create(ctx, maxPlayers, userIds, metadata, callback); err != nil {
      logger.WithField("error", err.Error()).Error("failed to create new fleet game session")
      return "", ErrInternalError
    }

    // We don't pass a Match ID back to the user as we are not creating a Nakama match
		return "", nil
	}); err != nil {
		logger.Error("unable to register matchmaker matched hook: %v", err)
		return err
	}
  1. On the client, use matchmaking to match with friends:
1
2
3
4
5
6
7
var minPlayers = 2;
var maxPlayers = 10;
var query = "properties.player:/(<friendId1>|<friendId2>|<friendId3>)/";
var stringProperties = new Dictionary<string, string>() {
  {"player", "<userId>"}
};
var matchmakerTicket = await socket.AddMatchmakerAsync(query, minPlayers, maxPlayers, stringProperties);
  1. On the client, register to listen for notifications from the server:
 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
socket.ReceivedNotification += notification => {
  const int notificationConnectionInfo = 111;
  const int notificationCreateTimeout = 112;
  const int notificationCreateFailed = 113;

  switch (notification.Code)
  {
    case notificationConnectionInfo:
      var data = notification.Content.FromJson<Dictionary<string, string>>();
      Debug.Log("Game session created, connection info:");
      Debug.Log($"IP: {data["IpAddress"]}");
      Debug.Log($"DnsName: {data["DnsName"]}");
      Debug.Log($"Port: {data["Port"]}");
      Debug.Log($"SessionId: {data["SessionId"]}");

      // Connect to the Amazon GameLift headless server with your chosen networking framework and the connection info.
      break;
    case notificationCreateTimeout:
      Debug.LogError("Game session creation timed out");
      break;
    case notificationCreateFailed:
      Debug.LogError("Failed to create game session");
      break;
  }
};

FAQ #

My SNS/SQS/GameLift instance cannot access X/Y/Z

Be sure to check the Access Policies and Permissions associated with each AWS service to make sure it has been configured to correctly have access to the relevant ARNs.

My Nakama instance is not receiving events from the SQS queue

Similarly to above, double check that the policies and permissions are correct, particularly within the SNS Topic and SQS queue.

My Nakama instance cannot interact with the AWS APIs

As above, ensure that the policies and permissions within AWS are correct.

Game sessions aren’t being created

Check the Fleet configuration to ensure scaling has been configured correctly, in particular check the max instances setting. If you are still experiencing issues, please refer to the Amazon GameLift documentation.

I cannot connect to the headless game server

Be sure to check that the appropriate Ports have been configured when creating the fleet. This will depend on your chosen networking framework, for example, Unity Netcode For GameObjects uses port 7778 by default.

If this does not resolve the issue, be sure to check the Debug logs of both the client and the server for more information.

I’m having an issue with X/Y/Z

If the issue you’re experiencing is not covered above, then there are a few things you can do to help diagnose the issue:

  1. Check the Nakama server logs
  2. Check the Game Session logs to look for client side issues
  3. Check the Nakama Console for any potential issues