

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 适用于 Unreal 的插件：集成游戏代码
<a name="unreal-plugin-integrate"></a>

在将游戏服务器部署到实例集之前，您需要对游戏代码进行一系列更新，并将游戏组件打包以供 Amazon GameLift Servers 服务使用。

本主题将介绍进行最小集成的步骤。要进行服务器集成，请使用提供的代码示例更新项目的游戏模式。
+ [设置生成包目标和模块规则](#unreal-plugin-anywhere-integrate-setup)
+ [更新游戏服务器代码](#unreal-plugin-anywhere-integrate-simple-server)
+ [整合客户端游戏地图](#unreal-plugin-anywhere-integrate-simple-client)
+ [打包游戏组件](#unreal-plugin-anywhere-integrate-build)

## 设置生成包目标和模块规则
<a name="unreal-plugin-anywhere-integrate-setup"></a>

修改您的游戏项目文件，以正确生成可与 Amazon GameLift Servers 搭配使用的生成包组件。

**要添加客户端和服务器生成包目标，请执行以下操作：**

1. 打开游戏项目的代码文件并找到 `.../Games/{{[your application name]}}Source/{{[your application name]}}Target.cs` 文件。示例：`.../Source/GameLiftUnrealAppTarget.cs`。（如果您使用 Visual Studio，请打开项目的 `.sln` 文件。）

1. 复制此文件，以在 `Source/` 目录中创建两个新的目标文件。
   + 客户端目标：将新文件重命名为 `{{[your application name]}}Client.Target.cs`。编辑内容以更新类名称和目标类型值，如以下示例代码所示：

     ```
     using UnrealBuildTool;
       using System.Collections.Generic;
     
       public class GameLiftUnrealAppClientTarget :  TargetRules
      {
          public GameLiftUnrealAppClientTarget ( TargetInfo Target ) :  base ( Target )
          {
              Type = TargetType.Client;
              DefaultBuildSettings = BuildSettingsVersion.V2;
              IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_1;
              ExtraModuleNames.Add( "GameLiftUnrealApp");
          }
      }
     ```
   + 服务器目标：将新文件重命名为 `{{[your application name]}}Server.Target.cs`。编辑内容以更新类名称和目标类型值，如以下示例代码所示：

     ```
     using UnrealBuildTool;
       using System.Collections.Generic;
     
       public class GameLiftUnrealAppServerTarget :  TargetRules
      {
          public GameLiftUnrealAppServerTarget ( TargetInfo Target ) :  base ( Target )
          {
              Type = TargetType.Server;
              DefaultBuildSettings = BuildSettingsVersion.V2;
              IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_1;
              ExtraModuleNames.Add( "GameLiftUnrealApp");
          }
      }
     ```

1. 重新生成您的项目文件。如果您使用 Visual Studio，可以右键单击游戏项目的 `.uproject` 文件，然后选择**生成 Visual Studio 项目文件**。

**要更新游戏项目模块规则，请执行以下操作：**

更新游戏项目的模块规则，使其依赖于插件。

1. 打开游戏项目的代码文件并找到 `.../Games/{{[your application name]}}Source/{{[your application name]}}.Build.cs` 文件。示例：`.../Source/GameLiftUnrealApp.Build.cs`。（如果您使用 Visual Studio，请打开项目的 `.sln` 文件。）

1. 找到 `ModuleRules` 类并进行更新，如以下示例代码所示：

   ```
   using UnrealBuildTool;
   
     public class GameLiftUnrealApp :  ModuleRules
    {
        public GameLiftUnrealApp ( ReadOnlyTargetRules Target ) :  base ( Target )
        {
            PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
            PublicDependencyModuleNames.AddRange( new string[] {  "Core",  "CoreUObject",  "Engine",  "InputCore",  "HeadMountedDisplay",  "EnhancedInput" });
        // Add the following section
   	   if (Target.Type == TargetType.Server)
   	   {
                  PublicDependencyModuleNames.Add("GameLiftServerSDK");
             }
             else
             {
                  PublicDefinitions.Add("WITH_GAMELIFT=0");
             }
            bEnableExceptions =  true;
        }
    }
   ```

1. 创建新的目标文件并修改模块规则后，重新构建您的游戏项目。

## 更新游戏服务器代码
<a name="unreal-plugin-anywhere-integrate-simple-server"></a>

更新您的游戏服务器代码，以实现游戏服务器进程与 Amazon GameLift Servers 服务之间的通信。您的游戏服务器必须能够响应来自 Amazon GameLift Servers 的请求，例如启动和停止新的游戏会话。

**为 Amazon GameLift Servers 添加服务器代码**

1. 在代码编辑器中，打开游戏项目的解决方案（`.sln`）文件，该文件通常位于项目根文件夹中。例如：`GameLiftUnrealApp.sln`。

1. 打开解决方案后，找到项目游戏模式头文件：`[project-name]GameMode.h` 文件。例如：`GameLiftUnrealAppGameMode.h`。

1. 更改头文件以使其与以下代码保持一致。请务必用您自己的项目名称替换 GameLiftServer “”。这些更新特定于游戏服务器；我们建议您备份原始游戏模式文件副本，以供客户端使用。

### gameMode.h 示例代码
<a name="w2aab9c11b9c19c27c11b7b1"></a>

```
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "GameLiftUnrealAppGameMode.generated.h"

struct FProcessParameters;

DECLARE_LOG_CATEGORY_EXTERN(GameServerLog, Log, All);

UCLASS(minimalapi)
class AGameLiftUnrealAppGameMode : public AGameModeBase
{
    GENERATED_BODY()

public:
    AGameLiftUnrealAppGameMode();

protected:
    virtual void BeginPlay() override;

private:
    void InitGameLift();

private:
    TSharedPtr<FProcessParameters> ProcessParameters;
};
```
+ 打开相关的源文件 `[project-name]GameMode.cpp` 文件（例如 `GameLiftUnrealAppGameMode.cpp`）。更改代码以使其与以下示例代码保持一致。请务必用您自己的项目名称替换 GameLiftUnrealApp “”。这些更新特定于游戏服务器；我们建议您备份原始文件副本，以供客户端使用。

  以下示例代码展示了如何添加服务器与 Amazon GameLift Servers 集成所需的最少元素：
  + 初始化 Amazon GameLift Servers API 客户端。Amazon GameLift Servers Anywhere 实例集需要使用服务器参数进行 `InitSDK()` 调用。当您连接到 Anywhere 实例集时，插件会将服务器参数存储为控制台参数。示例代码可以在运行时访问这些值。
  + 实现所需的回调函数以响应 Amazon GameLift Servers 服务的请求，包括 `OnStartGameSession`、`OnProcessTerminate` 和 `onHealthCheck`。
  + 准备好托管游戏会话时，请使用指定端口调用 `ProcessReady()` 以通知 Amazon GameLift Servers 服务。

### 游戏服务器示例代码
<a name="w2aab9c11b9c19c27c11c11b1"></a>

```
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

#include "GameLiftUnrealAppGameMode.h"

#include "UObject/ConstructorHelpers.h"
#include "Kismet/GameplayStatics.h"

#if WITH_GAMELIFT
#include "GameLiftServerSDK.h"
#include "GameLiftServerSDKModels.h"
#endif

#include "GenericPlatform/GenericPlatformOutputDevices.h"

DEFINE_LOG_CATEGORY(GameServerLog);

AGameLiftUnrealAppGameMode::AGameLiftUnrealAppGameMode() :
    ProcessParameters(nullptr)
{
    // Set default pawn class to our Blueprinted character
    static ConstructorHelpers::FClassFinder<APawn> PlayerPawnBPClass(TEXT("/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter"));

    if (PlayerPawnBPClass.Class != NULL)
    {
        DefaultPawnClass = PlayerPawnBPClass.Class;
    }

    UE_LOG(GameServerLog, Log, TEXT("Initializing AGameLiftUnrealAppGameMode..."));
}

void AGameLiftUnrealAppGameMode::BeginPlay()
{
    Super::BeginPlay();

#if WITH_GAMELIFT
    InitGameLift();
#endif
}

void AGameLiftUnrealAppGameMode::InitGameLift()
{
#if WITH_GAMELIFT
    UE_LOG(GameServerLog, Log, TEXT("Calling InitGameLift..."));

    // Getting the module first.
    FGameLiftServerSDKModule* GameLiftSdkModule = &FModuleManager::LoadModuleChecked<FGameLiftServerSDKModule>(FName("GameLiftServerSDK"));

    //Define the server parameters for a GameLift Anywhere fleet. These are not needed for a GameLift managed EC2 fleet.
    FServerParameters ServerParametersForAnywhere;

    bool bIsAnywhereActive = false;
    if (FParse::Param(FCommandLine::Get(), TEXT("glAnywhere")))
    {
        bIsAnywhereActive = true;
    }

    if (bIsAnywhereActive)
    {
        UE_LOG(GameServerLog, Log, TEXT("Configuring server parameters for Anywhere..."));

        // If GameLift Anywhere is enabled, parse command line arguments and pass them in the ServerParameters object.
        FString glAnywhereWebSocketUrl = "";
        if (FParse::Value(FCommandLine::Get(), TEXT("glAnywhereWebSocketUrl="), glAnywhereWebSocketUrl))
        {
            ServerParametersForAnywhere.m_webSocketUrl = TCHAR_TO_UTF8(*glAnywhereWebSocketUrl);
        }

        FString glAnywhereFleetId = "";
        if (FParse::Value(FCommandLine::Get(), TEXT("glAnywhereFleetId="), glAnywhereFleetId))
        {
            ServerParametersForAnywhere.m_fleetId = TCHAR_TO_UTF8(*glAnywhereFleetId);
        }

        FString glAnywhereProcessId = "";
        if (FParse::Value(FCommandLine::Get(), TEXT("glAnywhereProcessId="), glAnywhereProcessId))
        {
            ServerParametersForAnywhere.m_processId = TCHAR_TO_UTF8(*glAnywhereProcessId);
        }
        else
        {
            // If no ProcessId is passed as a command line argument, generate a randomized unique string.
            FString TimeString = FString::FromInt(std::time(nullptr));
            FString ProcessId = "ProcessId_" + TimeString;
            ServerParametersForAnywhere.m_processId = TCHAR_TO_UTF8(*ProcessId);
        }

        FString glAnywhereHostId = "";
        if (FParse::Value(FCommandLine::Get(), TEXT("glAnywhereHostId="), glAnywhereHostId))
        {
            ServerParametersForAnywhere.m_hostId = TCHAR_TO_UTF8(*glAnywhereHostId);
        }

        FString glAnywhereAuthToken = "";
        if (FParse::Value(FCommandLine::Get(), TEXT("glAnywhereAuthToken="), glAnywhereAuthToken))
        {
            ServerParametersForAnywhere.m_authToken = TCHAR_TO_UTF8(*glAnywhereAuthToken);
        }

        FString glAnywhereAwsRegion = "";
        if (FParse::Value(FCommandLine::Get(), TEXT("glAnywhereAwsRegion="), glAnywhereAwsRegion))
        {
            ServerParametersForAnywhere.m_awsRegion = TCHAR_TO_UTF8(*glAnywhereAwsRegion);
        }

        FString glAnywhereAccessKey = "";
        if (FParse::Value(FCommandLine::Get(), TEXT("glAnywhereAccessKey="), glAnywhereAccessKey))
        {
            ServerParametersForAnywhere.m_accessKey = TCHAR_TO_UTF8(*glAnywhereAccessKey);
        }

        FString glAnywhereSecretKey = "";
        if (FParse::Value(FCommandLine::Get(), TEXT("glAnywhereSecretKey="), glAnywhereSecretKey))
        {
            ServerParametersForAnywhere.m_secretKey = TCHAR_TO_UTF8(*glAnywhereSecretKey);
        }

        FString glAnywhereSessionToken = "";
        if (FParse::Value(FCommandLine::Get(), TEXT("glAnywhereSessionToken="), glAnywhereSessionToken))
        {
            ServerParametersForAnywhere.m_sessionToken = TCHAR_TO_UTF8(*glAnywhereSessionToken);
        }

        UE_LOG(GameServerLog, SetColor, TEXT("%s"), COLOR_YELLOW);
        UE_LOG(GameServerLog, Log, TEXT(">>>> WebSocket URL: %s"), *ServerParametersForAnywhere.m_webSocketUrl);
        UE_LOG(GameServerLog, Log, TEXT(">>>> Fleet ID: %s"), *ServerParametersForAnywhere.m_fleetId);
        UE_LOG(GameServerLog, Log, TEXT(">>>> Process ID: %s"), *ServerParametersForAnywhere.m_processId);
        UE_LOG(GameServerLog, Log, TEXT(">>>> Host ID (Compute Name): %s"), *ServerParametersForAnywhere.m_hostId);
        UE_LOG(GameServerLog, Log, TEXT(">>>> Auth Token: %s"), *ServerParametersForAnywhere.m_authToken);
        UE_LOG(GameServerLog, Log, TEXT(">>>> Aws Region: %s"), *ServerParametersForAnywhere.m_awsRegion);
        UE_LOG(GameServerLog, Log, TEXT(">>>> Access Key: %s"), *ServerParametersForAnywhere.m_accessKey);
        UE_LOG(GameServerLog, Log, TEXT(">>>> Secret Key: %s"), *ServerParametersForAnywhere.m_secretKey);
        UE_LOG(GameServerLog, Log, TEXT(">>>> Session Token: %s"), *ServerParametersForAnywhere.m_sessionToken);
        UE_LOG(GameServerLog, SetColor, TEXT("%s"), COLOR_NONE);
    }

    UE_LOG(GameServerLog, Log, TEXT("Initializing the GameLift Server..."));

    //InitSDK will establish a local connection with GameLift's agent to enable further communication.
    FGameLiftGenericOutcome InitSdkOutcome = GameLiftSdkModule->InitSDK(ServerParametersForAnywhere);
    if (InitSdkOutcome.IsSuccess())
    {
        UE_LOG(GameServerLog, SetColor, TEXT("%s"), COLOR_GREEN);
        UE_LOG(GameServerLog, Log, TEXT("GameLift InitSDK succeeded!"));
        UE_LOG(GameServerLog, SetColor, TEXT("%s"), COLOR_NONE);
    }
    else
    {
        UE_LOG(GameServerLog, SetColor, TEXT("%s"), COLOR_RED);
        UE_LOG(GameServerLog, Log, TEXT("ERROR: InitSDK failed : ("));
        FGameLiftError GameLiftError = InitSdkOutcome.GetError();
        UE_LOG(GameServerLog, Log, TEXT("ERROR: %s"), *GameLiftError.m_errorMessage);
        UE_LOG(GameServerLog, SetColor, TEXT("%s"), COLOR_NONE);
        return;
    }

    ProcessParameters = MakeShared<FProcessParameters>();

    //When a game session is created, Amazon GameLift Servers sends an activation request to the game server and passes along the game session object containing game properties and other settings.
    //Here is where a game server should take action based on the game session object.
    //Once the game server is ready to receive incoming player connections, it should invoke GameLiftServerAPI.ActivateGameSession()
    ProcessParameters->OnStartGameSession.BindLambda([=](Aws::GameLift::Server::Model::GameSession InGameSession)
        {
            FString GameSessionId = FString(InGameSession.GetGameSessionId());
            UE_LOG(GameServerLog, Log, TEXT("GameSession Initializing: %s"), *GameSessionId);
            GameLiftSdkModule->ActivateGameSession();
        });

    //OnProcessTerminate callback. Amazon GameLift Servers will invoke this callback before shutting down an instance hosting this game server.
    //It gives this game server a chance to save its state, communicate with services, etc., before being shut down.
    //In this case, we simply tell Amazon GameLift Servers we are indeed going to shutdown.
    ProcessParameters->OnTerminate.BindLambda([=]()
        {
            UE_LOG(GameServerLog, Log, TEXT("Game Server Process is terminating"));
            // First call ProcessEnding()
            FGameLiftGenericOutcome processEndingOutcome = GameLiftSdkModule->ProcessEnding();
            // Then call Destroy() to free the SDK from memory
            FGameLiftGenericOutcome destroyOutcome = GameLiftSdkModule->Destroy();
            // Exit the process with success or failure
            if (processEndingOutcome.IsSuccess() && destroyOutcome.IsSuccess()) {
                UE_LOG(GameServerLog, Log, TEXT("Server process ending successfully"));
            }
            else {
                if (!processEndingOutcome.IsSuccess()) {
                    const FGameLiftError& error = processEndingOutcome.GetError();
                    UE_LOG(GameServerLog, Error, TEXT("ProcessEnding() failed. Error: %s"),
                    error.m_errorMessage.IsEmpty() ? TEXT("Unknown error") : *error.m_errorMessage);
                }
                if (!destroyOutcome.IsSuccess()) {
                    const FGameLiftError& error = destroyOutcome.GetError();
                    UE_LOG(GameServerLog, Error, TEXT("Destroy() failed. Error: %s"),
                    error.m_errorMessage.IsEmpty() ? TEXT("Unknown error") : *error.m_errorMessage);
                }
            }
        });

    //This is the HealthCheck callback.
    //Amazon GameLift Servers will invoke this callback every 60 seconds or so.
    //Here, a game server might want to check the health of dependencies and such.
    //Simply return true if healthy, false otherwise.
    //The game server has 60 seconds to respond with its health status. Amazon GameLift Servers will default to 'false' if the game server doesn't respond in time.
    //In this case, we're always healthy!
    ProcessParameters->OnHealthCheck.BindLambda([]()
        {
            UE_LOG(GameServerLog, Log, TEXT("Performing Health Check"));
            return true;
        });

    //GameServer.exe -port=7777 LOG=server.mylog
    ProcessParameters->port = FURL::UrlConfig.DefaultPort;
    TArray<FString> CommandLineTokens;
    TArray<FString> CommandLineSwitches;

    FCommandLine::Parse(FCommandLine::Get(), CommandLineTokens, CommandLineSwitches);

    for (FString SwitchStr : CommandLineSwitches)
    {
        FString Key;
        FString Value;

        if (SwitchStr.Split("=", &Key, &Value))
        {
            if (Key.Equals("port"))
            {
                ProcessParameters->port = FCString::Atoi(*Value);
            }
        }
    }

    //Here, the game server tells Amazon GameLift Servers where to find game session log files.
    //At the end of a game session, Amazon GameLift Servers uploads everything in the specified 
    //location and stores it in the cloud for access later.
    TArray<FString> Logfiles;
    Logfiles.Add(TEXT("GameLiftUnrealApp/Saved/Logs/server.log"));
    ProcessParameters->logParameters = Logfiles;

    //The game server calls ProcessReady() to tell Amazon GameLift Servers it's ready to host game sessions.
    UE_LOG(GameServerLog, Log, TEXT("Calling Process Ready..."));
    FGameLiftGenericOutcome ProcessReadyOutcome = GameLiftSdkModule->ProcessReady(*ProcessParameters);

    if (ProcessReadyOutcome.IsSuccess())
    {
        UE_LOG(GameServerLog, SetColor, TEXT("%s"), COLOR_GREEN);
        UE_LOG(GameServerLog, Log, TEXT("Process Ready!"));
        UE_LOG(GameServerLog, SetColor, TEXT("%s"), COLOR_NONE);
    }
    else
    {
        UE_LOG(GameServerLog, SetColor, TEXT("%s"), COLOR_RED);
        UE_LOG(GameServerLog, Log, TEXT("ERROR: Process Ready Failed!"));
        FGameLiftError ProcessReadyError = ProcessReadyOutcome.GetError();
        UE_LOG(GameServerLog, Log, TEXT("ERROR: %s"), *ProcessReadyError.m_errorMessage);
        UE_LOG(GameServerLog, SetColor, TEXT("%s"), COLOR_NONE);
    }

    UE_LOG(GameServerLog, Log, TEXT("InitGameLift completed!"));
#endif
}
```

## 整合客户端游戏地图
<a name="unreal-plugin-anywhere-integrate-simple-client"></a>

启动游戏地图包含蓝图逻辑和用户界面元素，这些元素已经包含请求游戏会话和使用连接信息连接到游戏会话的基本代码。您可以按原样使用地图，也可以根据需要对其进行修改。将启动游戏地图与其他游戏资产一起使用，例如 Unreal Engine 提供的第三人称模板项目。这些资产可在内容浏览器中找到。您可以使用它们来测试插件的部署工作流程，或者作为为游戏创建自定义后端服务的指南。

该启动地图具有以下特性：
+ 它包括 Anywhere 实例集和托管 EC2 实例集的逻辑。在运行客户端时，您可以选择连接到任一实例集。
+ 客户端功能包括查找游戏会话（`SearchGameSessions()`）、创建新的游戏会话（`CreateGameSession()`）以及直接加入游戏会话。
+ 它会从项目的 Amazon Cognito 用户群体中获得一个唯一的玩家 ID（这是部署的 Anywhere 解决方案的一部分）。

**要使用启动游戏地图**

1. 在 UE 编辑器中，打开**项目设置、地图和模式**页面，然后展开**默认地图**部分。

1. 对于**编辑器启动地图**，从下拉列表中选择 StartupMap “”。您可能需要搜索位于 `... > Unreal Projects/[project-name]/Plugins/Amazon GameLift Servers Plugin Content/Maps` 中的文件。

1. 对于**游戏默认地图**，请从下拉列表中选择相同的 StartupMap “”。

1. 对于**服务器默认地图**，对于虚幻引擎 5.6 或更高版本，选择 “Lv1\_ThirdPerson”，对于早期版本，选择 ThirdPersonMap “”。这是您的游戏项目中包含的默认地图。这张地图是为游戏中的两个玩家设计的。

1. 打开服务器默认地图的详细信息面板。将 “**GameMode 覆盖**” 设置为 “无”。

1. 展开**默认模式**部分，将**全局默认服务器游戏模式**设置为您为服务器集成而更新的游戏模式。

对项目进行这些更改后，就可以开始构建游戏组件了。

**注意**  
对于虚幻引擎 5.6 或更高版本，如果在连接到游戏服务器后无法移动角色，请更新 BP\_ ThirdPersonCharacter 蓝图以添加`IMC_Default`和的输入映射上下文，`IMC_MouseLook`如下所示：  

![](http://docs.aws.amazon.com/zh_cn/gameliftservers/latest/developerguide/images/unreal-enhanced-input-blueprint.png)


## 打包游戏组件
<a name="unreal-plugin-anywhere-integrate-build"></a>

**打包游戏服务器和游戏客户端生成包**

1. 在 Unreal Engine 编辑器的源代码构建版本中打开游戏项目。

1. 如果使用虚幻引擎 5.6 或更高版本，请前往**编辑、项目设置、打包**。在**项目内容目录中找到 Cook 所有内容**并将其启用。

1. 使用编辑器打包游戏客户端和服务器生成包。

   1. 选择一个目标。前往**平台、Windows**，然后选择以下选项之一：
      + 服务器：`[your-application-name]Server`
      + 客户：`[your-application-name]Client`

   1. 启动构建。转到**平台、Windows、程序包项目**。

每个打包过程都会生成一个可执行文件：`[your-application-name]Client.exe` 或 `[your-application-name]Server.exe`。

在插件中，在本地工作站上设置客户端和服务器构建可执行文件的路径。