

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 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>

啟動遊戲映射包含藍圖邏輯和 UI 元素，這些元素已包含基本程式碼來請求遊戲工作階段，並使用連線資訊來連線至遊戲工作階段。您可以照原樣使用映射，或視需要修改這些映射。將啟動遊戲地圖與其他遊戲資產搭配使用，例如 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. 針對**伺服器預設映射**，針對 Unreal Engine 5.6 或更新版本選取「Lv1\$1ThirdPerson」，或針對舊版選取「ThirdPersonMap」。這是遊戲專案中包含的預設地圖。此地圖專為遊戲中的兩個玩家而設計。

1. 開啟伺服器預設映射的詳細資訊面板。將 **GameMode 覆寫**設定為 "None"。

1. 展開**預設模式**區段，並將**全域預設伺服器遊戲模式**設定為您為伺服器整合更新的遊戲模式。

對專案進行這些變更之後，您就可以開始建置遊戲元件。

**注意**  
針對 Unreal Engine 5.6 或更新版本，如果您在連線到遊戲伺服器後無法移動角色，請更新 BP\$1ThirdPersonCharacter 藍圖，以新增 `IMC_Default`和 的輸入映射內容`IMC_MouseLook`，如下所示：  

![\[alt text not found\]](http://docs.aws.amazon.com/zh_tw/gameliftservers/latest/developerguide/images/unreal-enhanced-input-blueprint.png)


## 封裝您的遊戲元件
<a name="unreal-plugin-anywhere-integrate-build"></a>

**封裝遊戲伺服器和遊戲用戶端組建**

1. 在來源建置版本的 Unreal Engine 編輯器中開啟您的遊戲專案。

1. 如果使用 Unreal Engine 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`。

在 外掛程式中，設定本機工作站上用戶端和伺服器建置可執行檔的路徑。