# 使用 C++ 集成

> Use the Matchmaker Subsystems and API to add matchmaking functionality to your Unreal Engine game in C++.

以下部分展示如何使用 [Unreal Engine 子系统](https://docs.unrealengine.com/4.27/en-US/ProgrammingAndScripting/Subsystems/)与 Matchmaker SDK 集成。

Unity Gaming Services SDK 提供两个 Matchmaker 接口：

* [Matchmaker Client Subsystem](#matchmaker-client-subsystem)

* [Matchmaker Server Subsystem](#matchmaker-server-subsystem)

### 添加 Matchmaker SDK 作为依赖项##add-the-matchmaker-sdk-as-a-dependency

继续下一步前，请将 `MatchmakerSDK` 添加为模块的公共依赖项，然后将插件头文件包含到您的类中，如下所示。

将 `MatchmakerServer` 和 `MatchmakerClient` 作为模块的依赖项添加到您的 Unreal 项目版本文件 (`YourProjectName.Build.cs`)：

```cpp
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" });
PublicDependencyModuleNames.AddRange(new string[] { "MatchmakerClient", "MatchmakerServer" });
PublicDependencyModuleNames.AddRange(new string[] { "Json", "JsonUtilities" });

```

将希望访问的插件头文件包含到您的类中：

```cpp
#include "MatchmakerClientSubsystem.h"
#include "MatchmakerServerSubsystem.h"
```

### Matchmaker Client Subsystem##matchmaker-client-subsystem

> **Note:**
>
> **注意**：在访问 Matchmaking Client Subsystem 之前，请确保您已设置 [Authentication](/authentication-unreal.md)。

Matchmaker Client Subsystem 控制关于匹配和寻找匹配玩家的客户端部分。此部分包括创建、删除和轮询匹配工单。

您可以通过获取 `UMatchmakerClientSubsystem` 子系统的引用来访问 Matchmaker Client Subsystem。`UMatchmakerClientSubsystem` 属于 `UGameInstanceSubsystem`，可在 `UGameInstance` 中检索。

```cpp
UWorld* GameWorld = GetWorld();
UGameInstance* GameInstance = GameWorld->GetGameInstance();
UMatchmakerClientSubsystem* MatchmakerClientSubsystem = GameInstance->GetSubsystem<UMatchmakerClientSubsystem>();
```

#### CreateTicket##createticket

使用 `CreateTicket()` 方法可为客户端创建匹配工单以启动匹配过程。

调用此方法会将 `Players` 数组中所有有效玩家以一个组的形式添加到匹配队列中，并作为同一个团队的成员加入游戏匹配。

您可以传递多个参数，但唯一必需的参数是 \*\*Players（玩家）\*\*列表（至少一名使用有效 ID 的玩家）。其他可选参数包括 **Queue Name（队列名称）**、\*\*Qos Results（Qos 结果）\*\*和自定义玩家数据。

```cpp
// Create Players array
TArray<FMatchmakerPlayer> Players;

// Create player definition
FMatchmakerPlayer SamplePlayer;
SamplePlayer.Id = TEXT("SamplePlayer");
// Adding custom player data
TSharedPtr<FJsonObject> CustomData = MakeShared<FJsonObject>();
CustomData->SetNumberField("Skill", 100);
CustomData->SetStringField("PreferredMap", "Dune");
SamplePlayer.CustomData = CustomData;

Players.Add(SamplePlayer);

// Setup Ticket Options
FCreateTicketOptions Options;
Options.QueueName = TEXT("default-queue");

MatchmakerClientSubsystem->CreateTicket(Players, options, Unity::Services::Core::THandler<FCreateTicketResponse>::CreateLambda([this](FCreateTicketResponse Response)
{
	// Your response logic here (response includes Ticket Id)
}));
```

您可以使用接受 `FCreateTicketResponse` 的 [THandler](/authentication-unreal/sdk-api/services-core/utils/services-utils.md#thandler) 来处理 SDK 的响应。

#### GetTicketStatus##getticketstatus

使用 `GetTicketStatus()` 方法向 Matchmaker Service 轮询客户端的工单状态。

您应该使用循环不断轮询，直到检索到表明匹配过程成功或失败的响应。

为了实现这一目标，请使用 `FTimerDelegate` 启动计时器，如下例所示：

```cpp
// Create the Timer Delegate and set the function to call
FTimerDelegate PollTicketDelegate = FTimerDelegate::CreateUObject(this, &UMyCustomClass::PollTicket, Response.TicketId);

// Start the Timer that runs every 5 seconds, cache PollTicketTimerHandler to use for stopping the timer later		GEngine->GameViewport->GetWorld()->GetTimerManager().SetTimer(PollTicketTimerHandle, PollTicketDelegate, 5, true);
```

然后，您的轮询匹配函数将类似于以下示例：

```cpp
void UMyCustomClass::PollTicket(FGuid TicketId)
{
	MatchmakerClientSubsystem->GetTicketStatus(TicketId, THandler<FGetTicketStatusResponse>::CreateLambda([this, TicketId](FGetTicketStatusResponse Response)
	{
		if (Response.bWasSuccessful)
		{
                  FString TicketIdStr = *TicketId.ToString(EGuidFormats::DigitsWithHyphens).ToLower();

		      switch (Response.Status)
			{
			case StatusEnum::Failed:
				UE_LOG(LogTemp, Log, TEXT("polling for ticket has failed, response was: %s"), *Response.ErrorMessage);
				break;
			case StatusEnum::Timeout:
				UE_LOG(LogTemp, Log, TEXT("polling for ticket has timed out, response was: %s"), *Response.ErrorMessage);
				break;
			case StatusEnum::InProgress:
				UE_LOG(LogTemp, Log, TEXT("polling for ticket %s is still in progress."), *TicketId.ToString(EGuidFormats::DigitsWithHyphens).ToLower());
				return;
			case StatusEnum::Found:
				UE_LOG(LogTemp, Log, TEXT("polling for ticket has completed, match found with ip: %s"), *Response.Ip);
				break;
			default:
				UE_LOG(LogTemp, Log, TEXT("unknown status received for ticket: %s), *TicketId.ToString(EGuidFormats::DigitsWithHyphens).ToLower());
				break;
			}

			// If we made it this far, we got a status other than InProgress so we can end the polling timer.
			GEngine->GameViewport->GetWorld()->GetTimerManager().ClearTimer(PollTicketTimerHandle);

			// Delete ticket here		
            }
		else
		{
			UE_LOG(LogTemp, Log, TEXT("Failed to poll Ticket status with ticket ID: %s"), *TicketId.ToString(EGuidFormats::DigitsWithHyphens).ToLower());
		}
	}));
}

```

您可以使用接受 `FGetTicketStatusResponse` 的 [THandler](/authentication-unreal/sdk-api/services-core/utils/services-utils.md#thandler) 来处理 SDK 的响应。

#### DeleteTicket##deleteticket

使用 `DeleteTicket()` 方法删除匹配工单并取消匹配。此操作通常是在完成玩家匹配（无论成功或失败）之后或当客户端不再希望参与匹配过程时进行的。

您可以在接受 `FDeleteTicketResponse` 的响应处理程序中处理 SDK 的响应。

```cpp
MatchmakerClientSubsystem->DeleteTicket(TicketId, Unity::Services::Core::THandler<FDeleteTicketResponse>::CreateLambda([](FDeleteTicketResponse Response)
{
	// Your response logic here
}));

```

您可以使用接受 `FDeleteTicketResponse` 的 [THandler](/authentication-unreal/sdk-api/services-core/utils/services-utils.md#thandler) 来处理 SDK 的响应。

### Matchmaker Server Subsystem##matchmaker-server-subsystem

Matchmaker Server Subsystem 控制关于匹配的服务器部分。此部分包括创建、审批、删除和更新回填工单。

使用 `UMatchmakerServerSubsystem` 前，您必须在如下代码片段中检索它：

```cpp
UWorld* GameWorld = GetWorld();
UGameInstance* GameInstance = GameWorld->GetGameInstance();
UMatchmakerServerSubsystem* MatchmakerServerSubsystem = GameInstance->GetSubsystem<UMatchmakerServerSubsystem>();
```

> **Note:**
>
> **注意**：截至撰写本文时，Matchmaker 只能与 Multiplay 服务器一起使用，因此需要注意的是，以下服务器函数需要与某些 Multiplay SDK 函数一起使用。

如需了解更多详细信息，请参阅[分配有效负载](https://www.rocketscience.gg/multiplay/)（通过 Multiplay 子系统使用 `GetPayloadAllocation()` 进行访问）。这用于最初填充 MatchProperties。

#### CreateBackfillTicket##createbackfillticket

当一名或多名玩家离开完整匹配，因此服务器需要填补空缺时，您需要创建新的回填工单。要为服务器创建新的回填工单，请使用 `CreateBackfillTicket()` 方法。

以下代码片段展示了如何创建新的回填工单：

```cpp
// Setup Backfill Ticket Options
FCreateBackfillTicketOptions CreateBackfillTicketOptions;
CreateBackfillTicketOptions.Connection = TEXT("35.245.90.171:9000");
CreateBackfillTicketOptions.QueueName = TEXT("default-queue");

// Create Match Properties
FMatchProperties MatchProperties;
FGuid BackfillTicketId = FGuid::NewGuid();
MatchProperties.BackfillTicketId = BackfillTicket.Id;
MatchProperties.Region = TEXT("aaaaaaaa-1111-bbbb-2222-cccccccccccc");

// Create Player
FMatchmakerPlayer Player;
Player.Id = FGuid::NewGuid().ToString();

FQosResult QosResult;
QosResult.Latency = 20;
QosResult.PacketLoss = 5;
QosResult.Region = TEXT("aaaaaaaa-1111-bbbb-2222-cccccccccccc");
Player.QoSResults.Add(QosResult);
MatchProperties.Players.Add(Player);

// Create Team
FMatchmakerTeam Team;
Team.TeamName = TEXT("Red");

FGuid TeamId = FGuid::NewGuid();
Team.TeamId = TeamId.ToString(EGuidFormats::DigitsWithHyphens);
Team.PlayerIds.Add(Player.Id);
MatchProperties.Teams.Add(Team);


CreateBackfillTicketOptions.Properties.MatchProperties = MatchProperties;


MatchmakerServerSubsystem->CreateBackfillTicket(CreateBackfillTicketOptions, Unity::Services::Core::THandler<FCreateBackfillTicketResponse>::CreateLambda([this, Player, Team](FCreateBackfillTicketResponse CreateResponse)
{
	// Your response logic here
}));

```

您可以使用接受 `FCreateBackfillTicketResponse` 的 [THandler](/authentication-unreal/sdk-api/services-core/utils/services-utils.md#thandler) 来处理 SDK 的响应。

#### ApproveBackfillTicket##approvebackfillticket

要在创建回填工单后审批回填工单，请使用 `ApproveBackfillTicket()` 方法。审批回填工单可以让新玩家进入服务器。

建议审批回填工单的速度不要超过每秒一次。如果工单经过 20 秒仍未获得审批，Unity Matchmaker 将会删除工单。

以下代码片段展示了如何审批回填工单：

```cpp
MatchmakerServerSubsystem->ApproveBackfillTicket(BackfillTicketId, Unity::Services::Core::THandler<FApproveBackfillTicketResponse>::CreateLambda([this](FApproveBackfillTicketResponse ApproveResponse)
{
	// Your response logic here
}));
```

您可以使用接受 `FApproveBackfillTicketResponse` 的 [THandler](/authentication-unreal/sdk-api/services-core/utils/services-utils.md#thandler) 来处理 SDK 的响应。

#### DeleteBackfillTicket##deletebackfillticket

当匹配人数已满，不再需要服务器接受新玩家时，删除回填工单。当游戏匹配结束后，不再希望服务器接收新玩家时，也应该执行此操作。要停止服务器上的回填，请使用 `DeleteBackfillTicket()` 方法。

以下代码片段展示了如何删除回填工单：

```cpp
MatchmakerServerSubsystem->DeleteBackfillTicket(BackfillTicketId, Unity::Services::Core::THandler<FDeleteBackfillTicketResponse>::CreateLambda([this](FDeleteBackfillTicketResponse DeleteResponse)
{
    // Handle response here
}));
```

您可以使用接受 `FDeleteBackfillTicketResponse` 的 [THandler](/authentication-unreal/sdk-api/services-core/utils/services-utils.md#thandler) 来处理 SDK 的响应。

#### UpdateBackfillTicket##updatebackfillticket

要更新服务器的当前回填工单，请使用 `UpdateBackfillTicket()` 方法。

每当玩家离开服务器或每当玩家从匹配逻辑之外加入服务器时，需要更新回填工单。这些情况可能包括（但不限于）派对邀请、直接连接和好友邀请。

更新回填工单的频率应该不超过每三秒一次，或者在已审批的回填工单发生变化后更新回填工单，以确保至少经过一个匹配周期。过于频繁地更新回填工单可能会导致玩家无法回填到匹配中。请参阅[匹配逻辑样本](/matchmaker/rules-sample.md)以了解更多信息。

```cpp
// From Approve
FBackfillTicket BackfillTicket;

FGuid BackfillTicketId = FGuid::Parse(ApproveResponse.BackfillTicket.Id, BackfillTicketId);

FBackfillTicket BackfillTicket = ApproveResponse.BackfillTicket;

// Setup MatchProperties
FMatchProperties MatchProperties;
MatchProperties.BackfillTicketId = BackfillTicket.Id; // Could be retrieved in Payload allocation's MatchProperties
MatchProperties.Region = TEXT("aaaaaaaa-1111-bbbb-2222-cccccccccccc"); // Could be retrieved in Payload allocation's MatchProperties

// Add a new player to the backfill ticket
FMatchmakerPlayer NewPlayer;
NewPlayer.Id = TEXT("NewPlayer");

TSharedPtr<FJsonObject> CustomData = MakeShared<FJsonObject>();
CustomData->SetNumberField("Skill", 100);
CustomData->SetStringField("PreferredMap", "Dune");
NewPlayer.CustomData = CustomData;

BackfillTicket.Properties.Players.Add(NewPlayer);
BackfillTicket.Properties.[0].PlayerIds.Add(NewPlayer.Id);

// Remove a Player from the backfill ticket
FString PlayerIdToRemove = TEXT("SamplePlayer");

for (FMatchmakerPlayer Player : BackfillTicket.Properties.Players)
{
    if (Player.Id == PlayerIdToRemove)
    {
        BackfillTicket.Properties.Players.Remove(Player);
        break;
    }
}

if (BackfillTicket.Properties.Teams[0].PlayerIds.Contains(PlayerIdToRemove))
{
    BackfillTicket.Properties.Teams[0].PlayerIds.Remove(PlayerIdToRemove);
}

// Call to update backfill ticket
MatchmakerServerSubsystem->UpdateBackfillTicket(BackfillTicketId, BackfillTicket, Unity::Services::Core::THandler<FUpdateBackfillTicketResponse>::CreateLambda([this, BackfillTicketId](FUpdateBackfillTicketResponse UpdateResponse)
{
    // Handle Response here
}));

```

Unity 建议先调用 `ApproveBackfillTicket`，然后使用从 `ApproveBackfillTicket` 返回的 `BackfillTicket` 进行修改并传递到 `UpdateBackfillTicket` 中。

您可以使用接受 `FUpdateBackfillTicketResponse` 的 [THandler](/authentication-unreal/sdk-api/services-core/utils/services-utils.md#thandler) 来处理 SDK 的响应。

### 使用 THandler

SDK 中包括与 Blueprint 兼容的自定义委托。使用 [THandler](/authentication-unreal/sdk-api/services-core/utils/services-utils.md#thandler) 的方式与常规 Unreal Engine 委托类似。

以下代码片段展示了如何进行带有内联响应的调用：

```cpp
Matchmaker::THandler<FCreateTicketResponse>::CreateLambda([ResponseDelegate](FCreateTicketResponse Response)
	{
		if (Response.bWasSuccessful)
		{
			UE_LOG(LogMatchmakerSDK, Log, TEXT("Successfully retrieved Ticket ID: %s"), *Response.TicketId);
		}
		else
		{
			UE_LOG(LogMatchmakerSDK, Error, TEXT("Failed to retrieve Ticket ID: %s"), *Response.ErrorMessage);
		}

		ResponseDelegate.ExecuteIfBound(Response);
	}));
```
