Note: This article was written using the GameLift Server SDK version 3.4.1.
Code examples in this article will be in C++. GameLift offers their SDK for C++ and C#.
brainCloud Lobbies and Hosting
This article will not detail Lobby, Matchmaking and brainCloud typical server configurations. It is recommended to read this article first:
For an example of a brainCloud custom server, view this code example:
If you wish instead to host your servers directly with brainCloud (rather than GameLift), please refer to these articles instead:
GameLift
In certain situations, GameLift may be a better choice as a hosting solution for your game. For example, brainCloud uses Docker images to run its game server instances and while it is simpler to setup, it can add run-time overhead.
For more in-depth documentation about GameLift, see this link: https://docs.aws.amazon.com/gamelift/index.html
brainCloud integration with GameLift can be accomplished in two ways:
Your game can use instances of the freely available brainCloud relay server.
Your game can use instances of a custom executable written by yourself.
If you choose to use the freely available brainCloud relay server, jump to the brainCloud Relay Server section.
If you choose the custom executable route, jump to the Custom Server section.
brainCloud Relay Server
The relay server instances employed by brainCloud are instances of a Linux-based executable developed in-house. The protocol and various details about the implementation can be found here:
In order to load that executable, you need to upload it as a build to your GameLift region of choice. The steps to do so are as follows:
Download artifacts
Download the executable and install script to a directory on your local machine. The tarball containing both can be found here.
2. Upload as a build to AWS
We'll use the AWS CLI to demonstrate how to upload a build. Open a terminal with the AWS CLI installed and execute the following commands:
# Setup awscli credentials and login
> aws configure set default.region <your-default-region>
> aws configure set aws_access_key_id <your-aws-access-key-id>
> aws configure set aws_secret_access_key <your-aws-secret-access-key>
> aws ecr get-login
# Upload custom server to GameLift (NOTE: you need to repeat this for every region you support)
> aws gamelift upload-build --name <your-build-name> --build-version <your-build-version> --build-root <path-to-artifacts> --operating-system AMAZON_LINUX_2 --region <your-target-region-id>
# Example (assuming I've downloaded the artifacts in step 1 to /Users/greg/tmp/bcrelayserver )
# aws gamelift upload-build --name RelayServer --build-version 4.13.0 --build-root /Users/greg/tmp/bcrelayserver --operating-system AMAZON_LINUX_2 --region eu-west-1
3. Your build should now be available for inclusion in your fleet definition:
You can now proceed to the GameLift Configuration section for the next steps.
Custom Server
This is the more labor intensive option for adding GameLift support to your game however it's also the most customizable. Prior to getting into the nitty gritty code details, here are some additional points worth mentioning to lay the groundwork for how your executable will work with brainCloud.
brainCloud S2S
To be able to communicate with brainCloud from your game server, you will need to add brainCloud S2S (Server to Server) dependency to your code. Depending on the language/engine you are using, here are the different libraries:
Server Flow
brainCloud will notify GameLift that a match should start, and GameLift will callback into your server instance to let you know it's ready.
brainCloud will pass information about the Lobby and how to connect to the S2S through GameLift's Game Properties. Properties are:
Property Name | Meaning |
APP_ID | your brainCloud game Id |
SERVER_HOST | URL for brainCloud S2S |
SERVER_PORT | Port for brainCloud S2S |
SERVER_NAME | As configured in the brainCloud Portal, My Servers section. |
SERVER_SECRET | Found in the brainCloud Portal, My Servers section. |
LOBBY_ID | Unique ID for the Lobby |
Using this information, your server is now able to fetch the entire Lobby from brainCloud using S2S. This will allow you to know who is in the game, who is the owner of the Game, and more. It will also contains temporary passcodes that you can use to validate players coming in. A typical Lobby JSON looks like this:
{
"cRegions" : [ "ca-central-1" ],
"connectData" : {},
"id" : "23649:CursorPartyGameLift:150",
"isRoomReady" : false,
"legacyLobbyOwnerEnabled" : false,
"lobbyType" : "CursorPartyGameLift",
"lobbyTypeDef" : {
"desc" : "Uses gamelift for server hosting",
"lobbyTypeId" : "CursorPartyGameLift",
"rules" : {
"allowEarlyStartWithoutMax" : true,
"allowJoinInProgress" : false,
"disbandOnStart" : true,
"earliestStartSecs" : 1,
"everyReadyMinNum" : 1,
"everyReadyMinPercent" : 0,
"forceOnTimeStartWithoutReady" : true,
"onTimeStartSecs" : 600,
"tooLateSecs" : 600
},
"teams" : {
"all" : {
"autoAssign" : true,
"code" : "all",
"maxUsers" : 8,
"minUsers" : 1
}
}
},
"members" : [
{
"cxId" : "23649:76a7b033-def2-4115-8dc4-183dbf33de6b:6c2dcmojq3r8dlnc7nash2mkke",
"extra" : {
"colorIndex" : 3
},
"isReady" : true,
"name" : "david",
"passcode" : "807826",
"pic" : "",
"profileId" : "76a7b033-def2-4115-8dc4-183dbf33de6b",
"rating" : 0,
"team" : "all"
}
],
"numMembers" : 1,
"ownerCxId" : "23649:76a7b033-def2-4115-8dc4-183dbf33de6b:6c2dcmojq3r8dlnc7nash2mkke",
"rating" : 0,
"round" : 1,
"settings" : {},
"state" : "starting",
"timetable" : {
"createdAt" : 1632405469055,
"early" : 1632405470055,
"onTime" : 1632406069055,
"tooLate" : 1632406069055
},
"version" : 1
}
With this, you can now move on and Activate the Game Session using the GameLift SDK, and then also notify brainCloud that the server is ready using SYS_ROOM_READY.
Then do your normal gameplay from there, and don't forget to properly shutdown and exit 0 from your server once finished.
Code Example
In this example, we show how to initialize GameLift and brainCloud S2S, and how to shutdown properly. We omitted any gameplay and networking code with players.
#include <condition_variable>
#include <mutex>
#include <sstream>
#include <string>
#include <vector>
#include <aws/gamelift/server/GameLiftServerAPI.h>
#include <aws/gamelift/server/LogParameters.h>
#include <aws/gamelift/server/ProcessParameters.h>
#include <brainclouds2s.h>
#include <json/json.h>
using namespace std;
using namespace Aws::GameLift;
using namespace Aws::GameLift::Server;
using namespace Aws::GameLift::Server::Model;
// Prototypes
void onStartGameSession(GameSession gameSession);
void onUpdateGameSession(UpdateGameSession updateGameSession);
void onProcessTerminate();
bool onHealthCheck();
Json::Value sendLobbyRequest(const string &sysCall);
// Game Properties passed to us from brainCloud, through GameLift.
static string APP_ID;
static string SERVER_HOST;
static string SERVER_PORT;
static string SERVER_NAME;
static string SERVER_SECRET;
static string LOBBY_ID;
// The port used for players to connect to your server. We should pass
// this as an argument to the server launch.
static int port = 0;
// Synchronisation objects used to wait on Game Session Activation
static bool gameReady = false;
static mutex gameReadyMutex;
static condition_variable gameReadyCV;
// brainCloud S2S
static S2SContextRef s2s;
static Json::Value lobbyJson;
// Main server entrance
int main(int argc, char **argv)
{
// Grab the players port from the arguments
if (argc > 1) port = atoi(argv[argc - 1]);
// Initialize GameLift SDK.
// Some GameLift calls seem to return an error with
// ALREADY_INITIALIZED. So make sure to check for that too.
auto initOutcome = InitSDK();
if (!initOutcome.IsSuccess() &&
initOutcome.GetError().GetErrorType() !=
GAMELIFT_ERROR_TYPE::ALREADY_INITIALIZED)
{
return 1;
}
// Create a list of log files that GameLift will copy after the
// server Shutdown.
vector<string> logPaths;
logPaths.push_back("log_file.txt");
// Game process parameters and callbacks
ProcessParameters processParameters(onStartGameSession,
onUpdateGameSession,
onProcessTerminate,
onHealthCheck,
port,
LogParameters(logPaths));
// Let GameLift know that the process is ready.
// Some GameLift calls seem to return an error with
// ALREADY_INITIALIZED. So make sure to check for that too.
auto readyOutcome = ProcessReady(processParameters);
if (!readyOutcome.IsSuccess() &&
readyOutcome.GetError().GetErrorType() !=
GAMELIFT_ERROR_TYPE::ALREADY_INITIALIZED)
{
return 1;
}
// Wait for game ready
{
unique_lock<mutex> lock(gameReadyMutex);
gameReadyCV.wait(lock, []() -> bool { return gameReady; });
}
// Initialize brainCloud S2S
{
auto url = "https://" + SERVER_HOST +
":" + SERVER_PORT + "/s2sdispatcher";
s2s = S2SContext::create(APP_ID,
SERVER_NAME,
SERVER_SECRET,
url,
true);
s2s->setLogEnabled(true);
}
// Get Lobby from S2S
lobbyJson = sendLobbyRequest("GET_LOBBY_DATA")["data"];
// Initialize your socket listeners here, load level, etc.
// ... TCP/UDP/Whatever
// Now we are fully ready to accept players' connections,
// let GameLift know.
ActivateGameSession();
// Now let brainCloud know through S2S. This will disband the
// lobby and send players our way.
sendLobbyRequest("SYS_ROOM_READY");
// Game main loop.
bool done = false;
while (!done)
{
// ...
}
// Once gameplay is done, send stats to brainCloud through S2S
// ... collect and send player stats, leaderboard, etc.
// If the Lobby is backfille (Join in progress), we need to let
// brainCloud know we're stopping the server. As a simple rule,
// always do this.
sendLobbyRequest("SYS_ROOM_STOPPED");
// Let GameLift know we're about to shutdown. After this,
// GameLift will give us about 30sec to cleanup properly and
// then exit with code 0.
ProcessEnding();
return 0;
}
// Called by GameLift, after brainCloud created a new game session.
// Here we should grab the Game Properties.
void onStartGameSession(GameSession gameSession)
{
// Get passed properties
const auto &gameProperties = gameSession.GetGameProperties();
for (const auto &game_property : gameProperties)
{
const auto &key = game_property.GetKey();
const auto &value = game_property.GetValue();
if (key == "APP_ID") APP_ID = value;
if (key == "SERVER_HOST") SERVER_HOST = value;
if (key == "SERVER_PORT") SERVER_PORT = value;
if (key == "SERVER_NAME") SERVER_NAME = value;
if (key == "SERVER_SECRET") SERVER_SECRET = value;
if (key == "LOBBY_ID") LOBBY_ID = value;
}
// Notify our gameloop that we are ready
unique_lock<mutex> lock(gameReadyMutex);
gameReady = true;
gameReadyCV.notify_one();
}
void onUpdateGameSession(UpdateGameSession updateGameSession) { }
// If GameLift decides to terminate your server
void onProcessTerminate()
{
// .. do any shuting down steps required
exit(0); // Terminate cleanly
}
bool onHealthCheck()
{
return true; // Make sure we let GameLift know we're ok
}
// Helper function to serialize a lobby request and return its result
Json::Value sendLobbyRequest(const string &sysCall)
{
Json::Value messageJson(Json::ValueType::objectValue);
Json::Value dataJson(Json::ValueType::objectValue);
// Construct our request JSON
dataJson["lobbyId"] = LOBBY_ID;
messageJson["service"] = "lobby";
messageJson["operation"] = sysCall;
messageJson["data"] = dataJson;
// Convert JSON to string
stringstream requestStream;
requestStream << messageJson;
string request = requestStream.str();
// Send request sync. This is for simplicity of this example.
// It is recommended to use async requests and rely on callbacks.
string result = s2s->requestSync(request);
// Convert string result to JSON
Json::Value resultJson;
stringstream resultStream(result);
resultStream >> resultJson;
return resultJson;
}
Assuming you've built your custom executable and uploaded it to GameLift as a Build, you can now proceed to the GameLift Configuration section for the next step in the process.
GameLift Configuration
Best Practices
Without going into too much detail on how GameLift works, we would like to provide a bit of insight on the best practices that can save you money and downtime.
The two types of Fleet available are Spots and On-Demand. Spots are the cheapest, but not always guaranteed to be available. On-demands are guaranteed to always be available but have a higher cost. The best is to use both. With spots having higher priority, and an on-demand fleet as a backup plan if no spots are available.
Here is an example of the priority setup in a Queue:
#1 is the Spot fleet, and #2 is the on-demand fleet.
We also recommend having two multi-region queues set up in different home regions. The idea is if a region goes down at AWS, then the other one can get the relay. If both are active, brainCloud will randomly pick one.
AWS Integration
Under Design -> Integrations -> Manage Integrations, enter your IAM Access Key and the IAM Secret Key:
This will allow brainCloud to communicate with your GameLift Queues.
Lobby config
Lobby configuration in the brainCloud Portal doesn't change from a normal brainCloud hosted server.
Server Config
When configuring your server, make sure to select "GameLift Room Server" from the Server Type dropdown. Then select the regions where you have Queues setup.
Overview