GS2-Matchmaking

Matchmaking function

This function groups players based on criteria. This can be used to find opponents.

Gathering

A Gathering is a group of players who have been grouped together by Matchmakers. The lifecycle of a gathering is explicitly created by the first player. Other players perform matchmaking and join the gathering if they meet the criteria.

When the number of players specified when the gathering was created is reached, players are notified of the matchmaking result and the gathering is removed from the waiting list.

Attributes

Players can have up to 5 attributes. When creating a Gathering, specify a range of attribute values for each player you want to recruit to create the Gathering.

For example, suppose you have a player with the following attribute values

Attribute NameAttribute Value
GameMode1
Stage10
Level5

GameMode is the game mode you want to use, whether it is matchmaking for all players in the game (1) or for each region (2=JP, 3=US, 4=EU). Let Stage be the type of stage in the game you want to use for matchmaking. Finally, Level is the skill level of the player.

If this player wants to create a gathering to find opponents, let’s consider what kind of conditions would be appropriate to set for the gathering.

Attribute NameAttribute Value (Min)Attribute Value (Max)
GameMode11
Stage1010
Level28

A range setting like the one shown above is recommended. GameMode and Stage must match exactly or the player will not be able to play under the conditions he/she desires. The level should be 3 players before or after the player’s level.

By creating a gathering, players who meet the criteria will join the gathering when they submit a matchmaking request.

Roles

When you create a gathering, you must set the number of players. When setting the number of recruits, you can set the number of recruits for each role. If there is no specific role in the game, you can set a role called Default and specify the number of people you want to gather in that role’s recruiting pool.

The case where you need to specify a role is in a game where there are roles in the game. For example, in MMORPGs, there are in-game roles such as tank, healer, and dps, depending on the player’s profession. And as a result of matchmaking, four healers cannot conquer the content of the game. Each role must be balanced in the matchmaking process.

In this specification, the following roles and the number of recruiters are set.

Role NameNumber of Recruits
Tank1
Healer1
DPS2

Then, when a player submits a matchmaking request, he/she sets not only the attribute values, but also his/her role. In this way, each role will manage the quota of applicants and matchmaking will be performed.

Role Aliases

Here is an example of role-based matchmaking based on more complex condition settings. Suppose there are two types of DPS: melee DPS and ranged DPS. The basic matchmaking conditions can be as explained above, but the Gathering Creator is particular about them. Let’s say you want a melee DPS and a ranged DPS to come in.

In this case, the condition settings would be as follows.

Role NameNumber of Recruits
Tank1
Healer1
Melee DPS1
Ranged DPS1

The player will then specify “melee DPS” or “ranged DPS” instead of “DPS” when matchmaking. However, as it stands, matchmaking will no longer be possible with those who say, “I don’t care if it’s melee or ranged as two DPSs get together.”

Role NameAliasNumber of Recruits
Tank[]1
Healer[]1
DPS[Melee DPS, Ranged DPS]2

This will allow players who are “melee DPS” or “ranged DPS” in their roles to be included in the DPS recruitment slots.

Rating Calculation

There is a function to calculate the rating value, which expresses the strength of a player. The initial rating value is 1500, and the value goes up or down depending on the results of the game.

When players with large differences in rating play against each other and the player with the higher rating loses, the rating of the losing player with the higher rating will decrease significantly, while the rating of the winning player with the lower rating will increase significantly. This feature allows the calculation of ratings that reflect the players’ abilities.

Voting

To change the rating values, a voting process is required. Once the game is over and the standings are determined, each player sends the results of the game to the server. The server will accept votes for each match and close the poll after all players have voted or 5 minutes have passed since the first player voted.

The server uses a majority of the ballots to determine the final result. This process ensures that any malicious players who vote incorrectly will not be able to influence the results. Based on the ranking determined by the majority vote, a new rate value is calculated and reflected.

Fragmentation of Voting Results

If the server attempts a majority vote and the result is a tie and the final result cannot be determined, no rate calculation will be performed. This makes it difficult to determine the correct rate value in a 1v1 game. To solve this problem, it is necessary to devise a solution such as matchmaking a third player who is not directly involved in the game behind the scenes and having that player vote from a third party perspective.

Expiration of Gathering

Once a gathering is created, it will not be deleted until matchmaking is complete or all players have left the gathering. If a player leaves the game without calling the Cancel API during matchmaking, especially in a game with few players If a player leaves the game during matchmaking without calling the Cancel API, another player may submit a matchmaking request, and even if the matchmaking is successful, there may be no players who have already created a gathering.

A simple solution to this problem is to set the validity period of the gathering at the time of creation. By specifying a specific time, or an elapsed time from the time of creation, the gathering will be deleted at that time. Players who have already joined the gathering will be automatically removed from the gathering.

Example of implementation

Creating a Gathering

    var result = await gs2.Matchmaking.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).CreateGatheringAsync(
        player: new Gs2.Unity.Gs2Matchmaking.Model.EzPlayer {
             Attributes = new [] {
                new Gs2.Unity.Gs2Matchmaking.Model.EzAttribute {
                    Name = "stage",
                    Value = 1,
                },
                new Gs2.Unity.Gs2Matchmaking.Model.EzAttribute {
                    Name = "level",
                    Value = 10,
                },
            },
        },
        attributeRanges: new [] {
            new Gs2.Unity.Gs2Matchmaking.Model.EzAttributeRange {
                Name = "stage",
                Min = 1,
                Max = 1,
            },
            new Gs2.Unity.Gs2Matchmaking.Model.EzAttributeRange {
                Name = "level",
                Min = 0,
                Max = 10,
            },
        },
        capacityOfRoles: new [] {
            new Gs2.Unity.Gs2Matchmaking.Model.EzCapacityOfRole {
                RoleName = "default",
                Capacity = 4,
            },
        },
    );
    var item = await result.ModelAsync();
    const auto Future = Gs2->Matchmaking->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->CreateGathering(
        MakeShared<Gs2::Matchmaking::Model::FPlayer>()
            ->WithAttributes([]
            {
                const auto v = MakeShared<TArray<TSharedPtr<Gs2::Matchmaking::Model::FAttribute>>>();
                v->Add(MakeShared<Gs2::Matchmaking::Model::FAttribute>()
                    ->WithName(TOptional<FString>("stage"))
                    ->WithValue(TOptional<int32>(1)));
                v->Add(MakeShared<Gs2::Matchmaking::Model::FAttribute>()
                    ->WithName(TOptional<FString>("level"))
                    ->WithValue(TOptional<int32>(10)));
                return v;
            }()),
        []
        {
            const auto v = MakeShared<TArray<TSharedPtr<Gs2::Matchmaking::Model::FAttributeRange>>>();
            v->Add(MakeShared<Gs2::Matchmaking::Model::FAttributeRange>()
                ->WithName(TOptional<FString>("stage"))
                ->WithMin(TOptional<int32>(1))
                ->WithMax(TOptional<int32>(1)));
            v->Add(MakeShared<Gs2::Matchmaking::Model::FAttributeRange>()
                ->WithName(TOptional<FString>("level"))
                ->WithMin(TOptional<int32>(0))
                ->WithMax(TOptional<int32>(10)));
            return v;
        }(), // attributeRanges
        []
        {
            const auto v = MakeShared<TArray<TSharedPtr<Gs2::Matchmaking::Model::FCapacityOfRole>>>();
            v->Add(MakeShared<Gs2::Matchmaking::Model::FCapacityOfRole>()
                ->WithRoleName(TOptional<FString>("default"))
                ->WithCapacity(TOptional<int32>(4)));
            return v;
        }(), // capacityOfRoles
        nullptr, // allowUserIds
        nullptr, // expiresAt
        nullptr // expiresAtTimeSpan
    );
    Future->StartSynchronousTask();
    if (!TestFalse(WHAT, Future->GetTask().IsError())) return false;

Perform matchmaking process

    var items = await gs2.Matchmaking.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).DoMatchmakingAsync(
        player: new Gs2.Unity.Gs2Matchmaking.Model.EzPlayer {
             Attributes = new [] {
                new Gs2.Unity.Gs2Matchmaking.Model.EzAttribute {
                    Name = "stage",
                    Value = 1,
                },
                new Gs2.Unity.Gs2Matchmaking.Model.EzAttribute {
                    Name = "level",
                    Value = 10,
                },
            },
        },
    ).ToListAsync();
    const auto It = Gs2->Matchmaking->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->DoMatchmaking( // player
    );
    for (auto Item : *It)
    {

    }

Exit from Gathering

    var result = await gs2.Matchmaking.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Gathering(
        gatheringName: "gathering-0001"
    ).CancelMatchmakingAsync(
    );
    const auto Future = Gs2->Matchmaking->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Gathering(
        "gathering-0001" // gatheringName
    )->CancelMatchmaking(
    );
    Future->StartSynchronousTask();
    if (!TestFalse(WHAT, Future->GetTask().IsError())) return false;

Get rate value

    var item = await gs2.Matchmaking.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Rating(
        ratingName: "rating-0001"
    ).ModelAsync();
    const auto Domain = Gs2->Matchmaking->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Rating(
        "rating-0001" // ratingName
    );
    const auto item = Domain.Model();

Retrieve Ballot

    var item = await gs2.Matchmaking.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Ballot(
        ratingName: "rating-0001",
        gatheringName: "gathering-0001",
        numberOfPlayer: 4,
        keyId: "grn:gs2:{region}:{yourOwnerId}:key:namespace-0001:key:key-0001"
    ).ModelAsync();

    var body = result.Body;
    var signature = result.Signature;
    const auto Domain = Gs2->Matchmaking->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Ballot(
        "rating-0001", // ratingName
        "gathering-0001", // gatheringName
        4, // numberOfPlayer
        "key-0001" // keyId
    );
    const auto item = Domain.Model();

Run Poll

    var result = await gs2.Matchmaking.Namespace(
        namespaceName: "namespace-0001"
    ).VoteAsync(
        ballotBody: "ballotBody",
        ballotSignature: "ballotSignature",
        gameResults: new [] {
            new Gs2.Unity.Gs2Matchmaking.Model.EzGameResult
            {
                Rank = 1,
                UserId = "user-0001",
            },
            new Gs2.Unity.Gs2Matchmaking.Model.EzGameResult
            {
                Rank = 2,
                UserId = "user-0002",
            },
            new Gs2.Unity.Gs2Matchmaking.Model.EzGameResult
            {
                Rank = 2,
                UserId = "user-0003",
            },
            new Gs2.Unity.Gs2Matchmaking.Model.EzGameResult
            {
                Rank = 3,
                UserId = "user-0004",
            },
        }
    );
    const auto Future = Gs2->Matchmaking->Namespace(
        "namespace-0001" // namespaceName
    )->Vote(
        "ballotBody",
        "ballotSignature",
        []
        {
            const auto v = MakeShared<TArray<TSharedPtr<Gs2::Matchmaking::Model::FGameResult>>>();
            v->Add({'rank': 1, 'userId': 'user-0001'});
            v->Add({'rank': 2, 'userId': 'user-0002'});
            v->Add({'rank': 2, 'userId': 'user-0003'});
            v->Add({'rank': 3, 'userId': 'user-0004'});
            return v;
        }() // gameResults
    );
    Future->StartSynchronousTask();
    if (!TestFalse(WHAT, Future->GetTask().IsError())) return false;

Frequently Asked Questions.

I would like to list the existing gatherings and let the player select one.

GS2-Matchmaking does not offer this feature. This is not for technical reasons, but because it is our policy as a game developer not to spoil the player’s experience.

If it is in the form of a choice among the gatherings that exist, it can present the best way for the player to participate in the gathering. However, there is a delay caused by user operations from the time the Gathering list is retrieved to the time the user actually performs the join operation. This delay makes it inevitable that the Gathering is already full when the user selects the Gathering he/she wants to join.

The GS2 developers have played many such games in the past and found them extremely frustrating. GS2-Matchmaking prides itself on providing a mechanism that automatically finds the best Gathering without requiring players to select their own Gathering. For this reason, we do not offer this feature.

I want to set a password for the Gathering.

Set the password as an attribute value and make it part of the matchmaking criteria.

Detailed Reference.