Hosting Persistent World Games on Amazon GameLift
Persistent world use cases have become increasingly popular within multiplayer games, as well as across industries for all kinds of realtime virtual world applications… There are two main games server hosting architectures: session-based and persistent world games… Persistent world games,…
Persistent world use cases have become increasingly popular within multiplayer games, as well as across industries for all kinds of realtime virtual world applications. There are two main games server hosting architectures: session-based and persistent world games. Session-based games group players together through matchmaking for a single short-lived game session. Persistent world games, on the other hand, run the world simulation continuously, with players joining and leaving the world over time. This often requires persisting the state of the world, as well as the state of a player in a specific world.
On AWS, customers can host persistent world games in multiple ways, including directly on virtual machines on Amazon EC2, or containerized on Amazon Elastic Container Service or Amazon Elastic Kubernetes Service. These approaches require you to build your own world orchestration service, as well game session and player session management. They are great options when you want full control of the game session orchestration, and you can handle the operational complexity.
Amazon GameLift is a service that deploys and manages dedicated game servers across locations. For customers looking for a more managed solution to reduce the operational effort, it is a great option for both session-based and persistent world games. On Amazon GameLift, you can easily host a single game server fleet globally, where the underlying infrastructure, scaling, and game session management are handled by the service. For session-based games on Amazon GameLift, there are multiple solutions to get started with in the AWS Solutions Library. In this post, we’ll focus on the persistent world use case, and we will dive deeper into a new solution guidance: Guidance for Persistent World Games on AWS.
Solution overview
The solution consists of four key components:
- World manager: a backend service to manage the deployment and status of the worlds
- Backend APIs: API endpoints for the game client to list and join worlds
- Amazon GameLift fleet: A multi-region fleet for hosting the game servers across locations
- Sample game client / server: A Unity sample game client and server implementation
World manager
The world manager, like all backend components of the solution, is built completely with a serverless architecture to reduce operational complexity. The world manager is an AWS Lambda function, which is invoked by Amazon EventBridge every minute to make sure the worlds configured in the WorldsConfiguration Amazon DynamoDB table are running. The world manager updates the state of the current worlds to another DynamoDB table called WorldSessions. This allows the Backend APIs to access the world information directly from DynamoDB.
Worlds can be configured with the hosting location, world name, maximum player count, and world map to load. This is an example of the world configuration:
Once you have configured the worlds, the world manager Lambda function ensures worlds are running if they are not scheduled for termination. The game server process will receive the world configuration information, including the world map, when the world manager provisions the world using the Amazon GameLift API CreateGameSession. The process then loads the correct map and activates itself to allow player sessions to be created for the world.
The event flow for the world manager includes the following steps:
- World manager Lambda function is invoked every 1 minute by Amazon EventBridge. The function checks existing worlds’ status through the Amazon GameLift API
- World manager then stores the current state of the sessions/worlds to DynamoDB for faster backend access
- World Manager queries configured worlds from DynamoDB and creates any worlds that are not running by calling the the Amazon GameLift API CreateGameSession
Dynamic worlds
The solution also supports dynamic worlds, where multiple instances of the same world are created as previous are filled with players. This works well for use cases such as lobbies where players gather before joining a session-based game. As the backend is fully customizable, customers can modify the logic of dynamic worlds to allow joining the same world as a friend for example, or returning to the same world after playing a session with friends.
Dynamic worlds are defined in the WorldsConfiguration table, and the world manager will create new instances of the world when there’s less than one full world of available player sessions left. The dynamic world game server processes will terminate themselves if A) there’s no players for 5 minutes, and B) there’s another copy of the world running (to avoid terminating the final instance of the world). World manager will keep an up to date count of the worlds in the WorldConfiguration table. Here’s an example configuration for dynamic worlds in DynamoDB.
Backend APIs and the sample client
The sample game client is built in Unity, but you can integrate with the Backend API from any game engine. Refer to this blog post on how to integrate Unreal with AWS SDK as well as Amazon GameLift on Unreal to build a similar integration in the Unreal engine.
The client leverages the AWS SDK for .NET with Unity to create a guest identity within an Amazon Cognito Identity Pool, and to then sign requests for Amazon API Gateway with the credentials of that identity. The supported requests include listing existing worlds within a specific AWS region (Amazon GameLift fleet location), and joining a selected world. The backend APIs access the latest world session information in the WorldSessions table and use the Amazon GameLift API to create player sessions.
The event flow for the Backend APIs includes the following steps:
- Game client requests an identity and credentials from Amazon Cognito Identity Pool to sign API requests
- Game client requests the world list through Amazon API Gateway. API Gateway triggers a Lambda function that checks game session information in the defined region from DynamoDB.
- Game client requests to join a specific world in a specific region by calling a Lambda function through API Gateway
- Lambda function creates a player session for the player, increases the amount of players for that world in DynamoDB, and sends the connection info back to the client
- Client connects to Amazon GameLift session directly over TCP and sends the player session ID.
Amazon GameLift fleet and game server
The Amazon GameLift fleet is hosted as a multi-region fleet. The game server build is uploaded only once, and it’s automatically distributed across the fleet locations. Each location also has their own individual scaling, which can follow utilization automatically when you configure Target-based auto scaling.
The game server process is a headless Unity server build. It validates the player session ID to authenticate the player, and stores player data (only the location in our example) in the WorldPlayerData DynamoDB table. It also polls the WorldsConfiguration table for information on possible requested termination, as well as for the current count of dynamic worlds (if the world is dynamic). Amazon CloudWatch is used to collect realtime logs and process-level metrics from the game server. It can also be used to collect custom metrics from the game server process, which is implemented in the sample with a simple StatsD client communicating with the CloudWatch agent.
The game server runs the full simulation of the game world and has the same world geometry loaded as the clients. Clients only send input to the server. Server runs the simulation and sends the state of the world back to the clients. This is done 30 times per second with the interval reduced as player count grows.
The event flow for the game server includes the following steps:
- Amazon GameLift session validates the player session ID with the Amazon GameLift server SDK
- Game server reads world specific player data from DynamoDB and updates the player data as needed. It will store the latest player location after player leaves.
- Amazon GameLift session checks DynamoDB for scheduled termination and terminates if requested
- Game server sends logs and metrics to Amazon CloudWatch using the CloudWatch Agent
Solution deployment
Now that you know the components of the solution, let’s look at how the sample is deployed!
Prerequisites
To deploy the solution, you are expected to create an AWS account, create an Identity and Access Management (IAM) User, and configure AWS credentials on your local system. As the solution leverage the Unity game engine, a basic level of knowledge of Unity is expected as well, though all the steps of using Unity are covered in the deployment instructions.
Preliminary setup for the backend and Unity
First, clone the GitHub repository. After that, install the tools for the infrastructure deployment following the Preliminary setup for the backend. This includes AWS Cloud Development Kit (AWS CDK) as well as npm and typescript. AWS CDK is a powerful tool to define and deploy your infrastructure as code, and it’s used to deploy all the components of the solution.
The sample client and server are built using Unity. Install Unity first (the instructions here are for Unity 2020, but newer versions work too, refer to the Readme for details), and then install .NET build tools for the build scripts. After installing the scripts, run either ./downloadAndSetupUnityDependencies.sh (MacOS/Linux)
or downloadAndSetupUnityDependencies.ps1
(Windows) to download and build the AWS SDK, Amazon GameLift Server SDK and other dependencies, which are also copied automatically to our Unity project. Refer to Preliminary setup for Unity for details on configuring your Unity environment and installing the dependencies.
Deploying the backend with AWS CDK
Now that you have the preliminary setup done, you can start deploying the components! Do this by following the Deployment with AWS CDK instructions. We will use the default region configuration with home region in US-East-1 and additional locations in US-West-2 and EU-West-1 so you can skip the region configuration steps.
To install all the dependencies of the AWS CDK app, run the following command in the Backend folder:
npm install --force
After that, run the following script to bootstrap CDK in your AWS account. The number value is your account ID followed by the deployment region.
cdk bootstrap aws://111122223333/us-east-1
Then, open the project in Unity 2020 and build the game server binary. First you need to set the project to game server configuration. Select the following menu option:
GameLift → SetAsServerBuild
Then, select the following menu option to build the game server:
GameLift → BuildLinuxServer
Select the LinuxServerBuild folder as the target location.
Now you have everything ready for backend deployments. Run the following command to synthesize the AWS CDK templates:
cdk synth
Now you’re ready to deploy the first stack that creates the Fleet IAM role (which is used to access DynamoDB and CloudWatch from the game server). Run the following command:
cdk deploy PersistentWorldFleetRoleStack
Once that is deployed, copy the output for the IAM Role Amazon Resource Name (ARN), and replace the CloudWatch agent role_arn value in LinuxServerBuild/amazon-cloudwatch-agent.json
Now you’re ready to deploy the rest of the resources including the world manager, backend APIs and the Amazon GameLift fleet. Run the following command:
cdk deploy PersistentWorldGameliftStack
This deployment can take up to 40 minutes as the Amazon GameLift fleet is created across the different locations. Once that is done, you’re ready to set up some worlds!
Configuring the worlds
Now that you have all the resources deployed, you can configure some worlds to host on the fleet. This is done in the WorldsConfiguration table. Open the DynamoDB table, and select Explore table items.
Then, select Create item to create new table items. It’s worth noting the CurrentDynamicWorldCount is automatically updated by the world manager and you don’t need to define it. All of the other fields are type String except MaxPlayers which is a Number. Set the following configuration for the worlds:
Now you have a few dynamic worlds (the lobbies) and a few static worlds (the test worlds) configured and once the world manager is invoked the next time (in about a minute), all these worlds are created on your fleet.
This demonstrates that the dynamic Lobby-worlds have a timestamp-based suffix, as you might have multiple instances of the same world at any given time.
Testing with the Unity sample client
Now that you have some worlds running, test the client! Open the MainMenu scene in the Unity project. Select the following menu option to configure the project as a client build:
GameLift → SetAsClientBuild
Then, open the AWS CloudFormation console to find the outputs of the stack for client endpoint configuration:
Copy these values to configure the Client object in the MainMenu scene:
Now you’re ready to start listing worlds in the different locations by running the client!
The dynamic worlds are indicated with a yellow color in the UI. If you configure a higher amount of instances for your fleet locations in the Amazon GameLift console, new dynamic worlds will be created as you create player sessions on the first instance of the dynamic world.
Select “Join” to join one of the worlds, and then move around, leave it, and join again. Previous location was stored in the WorldPlayerData table and you can review the table items in the DynamoDB console. You will also get a message when your previous location was restored by the game server.
Load testing with bot clients on AWS Fargate
Now that you have the solution set up end to end with one client, let’s add some load! The guidance comes with a simple load testing configuration that hosts headless bot game clients on AWS Fargate. AWS Fargate is a serverless, pay-as-you-go compute engine, and it’s perfect for short term load testing.
To get started, make sure you have Docker installed and running. Then, configure your AWS account ID in configuration.sh
in the UnityBotClient folder. After this, select the following menu option to configure the build as a bot client in Unity:
Gamelift → SetAsBotClientBuild
You can now test how the bot operates by running the client. It will pick a random world in a random location and connect to it. The bot will move the player around for several minutes, log out, and connect to another world.
Next, select the following menu options to build the headless bot client:
Gamelift → BuildLinuxBotClient
Select UnityBotClient/Build folder as the destination for the build.
Then, run the script ./buildSetupAndRunBots.sh
to deploy the bots to AWS Fargate. It will create the repository for the container image, upload the image, and deploy 10 AWS Fargate Tasks as a Fargate service with 4 clients each, totaling 40 clients. You can easily increase the amount of bots in the Amazon Elastic Container Service console by modifying the service, or by modifying the numberoftasks
value in the deployment script and redeploying.
Now you can log in to the game with a normal client and note the bots moving around the worlds and changing worlds over time. You can also view the Amazon GameLift console, DynamoDB console, as well as API Gateway, Lambda and CloudWatch consoles to view activity, metrics and logs as the bots play the game.
Resources clean up
Clean up the AWS resources you created by following the Resource clean up instructions in the project.
Conclusion
We have covered how you can run different types of persistent world scenarios on Amazon GameLift leveraging our solution guidance. As all games are unique, the solution guidance comes with full source code, and you can start modifying it to your needs and build the multiplayer features that make your game special! In some scenarios, you might decide not to run on Amazon GameLift, and the considerations section in the repository explains some of these situations where a more custom solution is more appropriate.
We look forward to seeing the multiplayer experiences you build for your players!
Author: Juho Jantunen