AWS for Games Blog

Fitting the Pattern: Serverless Custom Matchmaking with Amazon GameLift

One crucial component for success in session-based multiplayer game titles is how smartly and efficiently they can put together competitive and exciting matches for their users, no matter the skill level, connection speed, or location. Reliability, flexibility and system smarts all play into making a successful multiplayer experience.

At the 2017 Game Developers Conference, Amazon GameLift’s Chris Byskal and Geoff Pare talked about Building Resilient Online Games using Amazon GameLift. In the talk, Chris and Geoff explain how Amazon GameLift can simplify the process of setting up different types of games in the cloud. They also talk about how Amazon GameLift can save thousands of hours of engineering time, significantly reduce idle active servers, protects game servers from DDoS attacks, and provides automated scaling and matchmaking.

This blog post will dive into the player-matching patterns mentioned in Chris and Geoff’s talk, and focus on a matchmaking architecture that uses a custom algorithm to group players and connect them with a server. It will also provide code examples so you can build your own custom matchmaking architecture.

Such a serverless approach provides significant benefits. It reduces the burden of undifferentiated tasks common when running and maintaining highly available server infrastructure in traditional environments. Most importantly, this approach can simplify the creation of a complicated and important back-end process, giving you more time to focus on building the best game possible.

Overview of Player-Matching Patterns

Multiplayer games today tend to come in two flavors. They either connect players for matches using server browser game selection or through matchmaking.

Server browsers are relatively simple to create, presenting players a list of available servers from which players can choose to join or create a specific game.

Game Server Browser Example

Figure 1 – Example of a server browser.

Should a developer want to use this approach, Amazon GameLift provides several options to simplify the implementation, using three API calls within the game client:

  • List all games – Amazon GameLift supports searches that match your player’s search criteria. For a server browser setup, we can either show all current game sessions or just those with available player sessions. For more information about using this API call, review the Amazon GameLift SearchGameSessions() API documentation.
  • Join a specific game – A player also can join a specific game with their clan or group of friends. Once a player has chosen a game session, the system requests that the player be added. If the game session is accepting new players and has an available player slot, Amazon GameLift reserves the slot and responds with connection details.  This approach does mean the game session could become full by the time the player has selected the session and requested to join. Read more on the Amazon GameLift CreatePlayerSession() API Reference.
  • Create a game – Amazon GameLift also can create new game sessions for a player. Once active, the game session can appear in the server browser, where other players may join. Players also may choose to make their game session private, so they aren’t shown in the server browser. For more information, see the Amazon GameLift CreateGameSession() API Reference.

The server browser approach is simple and gives players the opportunity to pick their own game from the list of available options. It’s also relatively simple to implement for developers. But that simplicity may not provide the best experience for players. The lack of matching based on player or team skill, or other important criteria, can create imbalanced matchups that aren’t fun for everyone.

The server browser approach also can make it more difficult to efficiently group players across your infrastructure, because players can join game sessions regardless of how full or empty they are. This can lead to “lumpy” player distribution across servers and unnecessarily higher costs.

Matchmaking takes a different approach. When players request to join a game, a customized algorithm typically locates player matches based on variables like player skill, server latency, friend preferences, and team groupings. Players are more likely to be evenly matched and thus more likely to grouped into competitive games.

This approach also groups players onto servers more efficiently, keeping game sessions full, reducing server instances and lowering cost. Matchmaking’s primary challenge is higher complexity.

Designing a Serverless Matchmaker

Should you decide to take the matchmaker approach, you’ll need to set one up. In this section, we’ll show you how to create a simple serverless matchmaker with a custom player-matching algorithm. Figure 2 (below) describes a multiplayer server architecture that includes:

  • Matchmaking based on custom variables or an algorithm
  • Game server management
  • Game session management
  • Automatic scaling of server instances
  • End-to-end versioning of game-connection flows

This serverless custom matchmaking process takes place in three primary steps.

Serverless Custom Matchmaking Example Architecture

Figure 2- Serverless custom matchmaking architecture.

  1. Requesting to join a game
    In Step 1, the player uses a client to join a game. The game client calls an Amazon API Gateway endpoint, which is backed by a Lambda function that will house our custom matchmaking logic and interact with Amazon GameLift to find a suitable game session for the player. Amazon API Gateway is a managed service that helps developers create, publish, maintain, monitor, and secure APIs. We are using API Gateway here because it creates a level of abstraction between our game client and the AWS Lambda/Amazon GameLift implementation underneath. This offers flexibility for situations such as:

    • Versioning – Game clients are isolated from changes in back-end processing. It’s useful in several ways: to future-proof the game so it can easily incorporate new technologies; to enable seamless cutovers to new Amazon GameLift implementations; to perform A-B testing.
    • Operational metric capture – We suggest using Amazon CloudWatch with API Gateway to monitor performance metrics of our API, and Lambda function to help quickly identify issues with our matchmaking service.
    • Security – With API Gateway, we can use AWS security tools such as Amazon Cognito to authorize access to APIs and control service operation access. This creates a seamless authorization process for players that is easy to configure and maintain.
  2. Finding a game
    In Step 2, we use AWS Lambda functions for matchmaking. Lambda lets you run code without provisioning or managing servers. Deploying to Lambda is as simple as uploading your code, Lambda handles everything needed to run and scale on demand. Your code can be set to automatically trigger from other AWS services or called directly from any web service or app. Lambda is a good choice here because matchmaking calls are short-lived and invoked moderately frequently. Server administration is kept to a minimum with high availability. Lambda usage is charged per execution, per 100ms. You can easily run in parallel multiple versions of processes, providing the flexibility to best support your game clients and players. For this design, let’s define three Lambda functions:
    • Enter Matchmaking – This function handles game-client requests to join a game. The function takes the request, accepts any necessary information passed in by the client (such as server latency), and persists it in an Amazon DynamoDB table of waiting requests to join games. The function notifies the client that matchmaking is in progress so that it can inform the player.
    • Matchmaker – This function runs at short intervals to create groups of closely matched players and prepares each group to join a game session. This is where the custom matchmaking logic goes to work. Each time the function runs, its algorithm iterates over the table of waiting join requests to match players into groups. Once it has created a complete group, the group is marked ready to join a game session. The function persists this information in the DynamoDB table and then terminates. Figure 3 (below) is a code snippet showing example logic for this function.
    • Server Connector – This function periodically checks for groups that are ready to be assigned a server and submits requests for game sessions. It uses an Amazon GameLift feature called game session placements.
def get_unmatched_players():
        table = dynamodb.Table(table_name)
        response = table.scan(
            FilterExpression=Attr('MatchStatus').eq('Open')
        )
        players = response['Items']
        
        print("Number of players watching for matches: " + str(len(players)))
        
        return players
        
def create_groups(players):
    print('Creating Groups')
    groups = list()
    
    # Sort players by skill
    players = sorted(players, key=lambda player: player['Skill'])
    
    # Group players into match sized groups
    while (len(players) >= match_size):
        new_group = {'Players': list()}
        for i in range(0, match_size):
            new_group['Players'].append(players.pop(0))
            
        groups.append(new_group)

    print("Number of groups created: " + str(len(groups)))
    
    return groups

Figure 3 – Example Python code for custom matchmaking logic.

With game session placements, Amazon GameLift looks across multiple fleets and regions for that player group’s best available hosting resources. We also can create a new game session and add our group of players to it with a single request.

In Amazon GameLift, a new game session placement request is added to a game session queue. We’ll need to set up a queue with one or more fleets to host the new game sessions (located in any region) and set a timeout value to limit the time spent fulfilling each request.

When fulfilling a placement request, Amazon GameLift evaluates each fleet associated with the queue until it finds one that can support the placement request or the request times out. By default, Amazon GameLift evaluates fleets in the order they are listed in the queue configuration. This makes it possible to define the order of preference we want Amazon GameLift to use when placing a new game session.

For games that require a low latency experience, Amazon GameLift can use latency data to place a game session in a region with minimal lag for all of that group’s players.

To use this feature, we need to collect latency data for each player in all regions. When Amazon GameLift receives the latency data for multiple players, it reorders the queue’s server fleets to prioritize regions with the lowest average lag.

Figure 4’s code snippet (below) shows a game session placement request. The StartGameSessionPlacement() function accepts a queue name, which we defined in our queue configuration.

Amazon GameLift also requires a unique placement ID. We’ll use this ID to track the request status. The developer defines placement IDs, which can be anything unique. In the example below, it uses a UUID, which is passed to the caller for further processing.

Optionally, our placement request can include game and player IDs for the session. If supplied, you can use the AWS console to view game sessions at the player level, making it easy to see when users are actively playing and for how long.

A new game session placement is created with a PENDING status. The example code in Figure 4 (below) prints the status returned in response to the placement request. Further tasks can be performed, based on the status of the placement request. For example, if the response is PENDING, we can inform the game client that the session is imminent and perform further checks on the status awaiting confirmation. If the placement request times out, we can resubmit the request or try again with a different queue.

def start_game_placement(queue_name, group):
    print("Starting Game Session Placement")
    
    placement_id = str(uuid.uuid4())

    desiredPlayerSessions = list()

    for player in group['Players']:
        desiredPlayerSessions.append({ 
                'PlayerId':  player['PlayerId'],
                'PlayerData':  player['Skill']
        })

    response = gamelift.start_game_session_placement(
	    PlacementId=placement_id,
	    GameSessionQueueName=queue_name,
	    GameProperties=[
	        {
	            'Key': 'Skill_Level',
	            'Value': 'Highest'
	        },
	    ],
	    MaximumPlayerSessionCount=match_size,
	    GameSessionName='My Matched MP Game',
        DesiredPlayerSessions= desiredPlayerSessions
	)

    print("Game Session Status: " + response['GameSessionPlacement']['Status'])
    
    return placement_id
    
def update_players_in_group_for_match(group):
    for player in group['Players']:
        update_player(player, group['PlacementId'], 'Matched')
    
def update_player(player, placement_id, status):
    print('Updating Player with Placement Id and setting status to Matched')
    
    table = dynamodb.Table(table_name)
    
    response = table.update_item(
        Key={
            'PlayerId': player['PlayerId'],
            'StartDate': player['StartDate']
        },
        UpdateExpression="set PlacementId = :g, MatchStatus= :t",
        ExpressionAttributeValues={
            ':g': placement_id,
            ':t': status
        },
        ReturnValues="UPDATED_NEW"
    )

 

Figure 4 – Example Python code for creating Amazon GameLift session placements.

  1.  Connecting to a game
    In Step 3, game clients receive game session details from the Lambda function and Amazon GameLift. Game clients can now connect directly to the game server and start play. The direct connection between the client and the game server means that Amazon GameLift adds no additional latency to gameplay.

In Summary

Multiplayer games continue to grow in popularity. To succeed, they will need rapid, seamless scalability and very high levels of reliability to support millions of players who love these experiences.

Many multiplayer games can benefit from customized matching that brings together the groups of users who will get the most from playing each other. Players in several game genres expect high-quality matchmaking to ensure the best experience possible. Therefore, your player-matching algorithm can be a key ingredient of your title’s success. The serverless matchmaker pattern we’ve discussed here allows you to use any algorithm and variables that match players for your game, while also taking advantage of Amazon GameLift’s benefits to better manage your server fleet.

Amazon GameLift’s game session placement feature is a good fit to support our matchmaker pattern while providing players the best possible gaming experience. As a robust hosting service, Amazon Gamelift also controls all management operations of the server fleet.

It can automatically scale capacity based on player demand and is charged on a pay-as-you-go basis, so you can better control costs while still serving your players. Amazon GameLift also makes it possible to run multiple versions of fleets in parallel and to switch between them via aliases. To get started, you can download the Amazon GameLift Server SDK and manage your operations using the AWS Management Console, AWS CLI, or the Amazon GameLift APIs, which are available in C++, C#, and several other languages.

Amazon GameLift’s ability to handle an unknown level of demand comfortably and automatically can allow developers to focus on making their gameplay experience unique and special. This serverless matchmaker pattern provides an elastically scalable architecture for your server backend that can handle unpredictable demand, and manage many aspects of your infrastructure automatically. The result: a reduced burden for game developers and operators, while giving your customers a superior and seamless experience.

Peter Chapman is a Solutions Architect in the Amazon Gamelift and Lumberyard teams. He has over 12 years of software development and architecture experience. He has designed solutions in many industry sectors including Retail, Healthcare, and Gaming.