本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。
Unreal 的外掛程式:使用 Amazon 設定本機測試 GameLift Anywhere
在此工作流程中,您可以為 Amazon GameLift 功能新增用戶端和伺服器遊戲程式碼,並使用外掛程式將本機工作站指定為測試遊戲伺服器主機。完成整合任務後,請使用 外掛程式建置遊戲用戶端和伺服器元件。
若要啟動 Amazon GameLift Anywhere 工作流程:
在 Unreal 編輯器主工具列中,選擇 Amazon GameLift 選單,然後選擇 Host with Anywhere 。此動作會開啟外掛程式頁面 Deploy Anywhere ,此頁面提供六步驟程序來整合、建置和啟動遊戲元件。
步驟 1:設定您的設定檔。
選擇您希望在遵循此工作流程時使用的設定檔。您選擇的設定檔會影響工作流程中的所有步驟。您建立的所有資源都與設定檔 AWS 的帳戶相關聯,並放置在設定檔的預設 AWS 區域中。設定檔使用者的許可決定您對 AWS 資源和動作的存取。
設定使用者設定檔
-
從可用設定檔的下拉式清單中選取設定檔。如果您還沒有設定檔,或想要建立新的設定檔,請前往 Amazon GameLift 選單,然後選擇設定 AWS 使用者設定檔。
-
如果引導狀態不是「作用中」,請選擇引導設定檔,然後等待狀態變更為「作用中」。
步驟 2:設定遊戲程式碼
在此步驟中,您會對用戶端和伺服器程式碼進行一系列更新,以新增託管功能。如果您尚未設定 Unreal 編輯器的來源建置版本,外掛程式會提供指示和原始程式碼的連結。
透過 外掛程式, 可以在整合遊戲程式碼時利用一些便利性。您可以進行最少的整合來設定基本託管功能。您也可以進行更廣泛的自訂整合。本節中的資訊說明最低整合選項。使用外掛程式隨附的測試地圖,將用戶端 Amazon GameLift 功能新增至您的遊戲專案。對於伺服器整合,請使用提供的程式碼範例來更新專案的遊戲模式。
整合您的伺服器遊戲模式
將伺服器程式碼新增至您的遊戲,以啟用遊戲伺服器與 Amazon GameLift 服務之間的通訊。您的遊戲伺服器必須能夠回應來自 Amazon 的請求 GameLift,例如開始新的遊戲工作階段,以及報告遊戲伺服器運作狀態和玩家連線的狀態。
新增 Amazon 的伺服器程式碼 GameLift
在程式碼編輯器中,開啟遊戲專案的解決方案 (
.sln
) 檔案,通常位於專案根資料夾中。例如:GameLiftUnrealApp.sln
。開啟解決方案時,找到專案遊戲模式標頭檔案:
[project-name]GameMode.h
檔案。例如:GameLiftUnrealAppGameMode.h
。變更標頭檔案以符合下列範例程式碼。請務必使用您自己的專案名稱取代 "GameLiftServer"。這些更新是遊戲伺服器特有的;我們建議您備份原始遊戲模式檔案,以便與用戶端搭配使用。
// 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 "GameLiftServerGameMode.generated.h" struct FProcessParameters; DECLARE_LOG_CATEGORY_EXTERN(GameServerLog, Log, All); UCLASS(minimalapi) class AGameLiftServerGameMode : public AGameModeBase { GENERATED_BODY() public: AGameLiftServerGameMode(); protected: virtual void BeginPlay() override; private: void InitGameLift(); private: TSharedPtr<FProcessParameters> ProcessParameters; };
開啟相關的來源
[project-name]GameMode.cpp
檔案 (例如GameLiftUnrealAppGameMode.cpp
)。變更程式碼以符合下列範例程式碼。請務必使用您自己的專案名稱取代 "GameLiftUnrealApp"。這些更新是遊戲伺服器特有的;我們建議您製作原始檔案的備份副本,以便與用戶端搭配使用。下列範例程式碼說明如何新增與 Amazon 整合的伺服器所需的最低元素 GameLift:
初始化 Amazon GameLift API 用戶端。Amazon GameLift Anywhere 機群需要具有伺服器參數的
InitSDK()
呼叫。當您連線到 Anywhere 機群時,外掛程式會將伺服器參數儲存為主控台引數。範例程式碼可在執行時間存取這些值。實作必要的回呼函數,以回應 Amazon GameLift 服務的請求,包括
OnStartGameSession
、OnProcessTerminate
和onHealthCheck
。ProcessReady()
使用指定的連接埠呼叫 ,以便在準備好託管遊戲工作階段時通知 Amazon GameLift 服務。
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #include "GameLiftServerGameMode.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); AGameLiftServerGameMode::AGameLiftServerGameMode() : 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 AGameLiftServerGameMode...")); } void AGameLiftServerGameMode::BeginPlay() { Super::BeginPlay(); #if WITH_GAMELIFT InitGameLift(); #endif } void AGameLiftServerGameMode::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. ServerParametersForAnywhere.m_processId = TCHAR_TO_UTF8( *FText::Format( FText::FromString("ProcessId_{0}"), FText::AsNumber(std::time(nullptr)) ).ToString() ); } 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); } 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, 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, GameLift 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. GameLift 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 GameLift we are indeed going to shutdown. ProcessParameters->OnTerminate.BindLambda([=]() { UE_LOG(GameServerLog, Log, TEXT("Game Server Process is terminating")); GameLiftSdkModule->ProcessEnding(); }); //This is the HealthCheck callback. //GameLift 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. GameLift 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 GameLift where to find game session log files. //At the end of a game session, GameLift uploads everything in the specified //location and stores it in the cloud for access later. TArray<FString> Logfiles; Logfiles.Add(TEXT("GameServerLog/Saved/Logs/GameServerLog.log")); ProcessParameters->logParameters = Logfiles; //The game server calls ProcessReady() to tell GameLift 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 }
整合用戶端遊戲地圖
啟動遊戲映射包含藍圖邏輯和 UI 元素,已包含基本程式碼來請求遊戲工作階段,並使用連線資訊來連線至遊戲工作階段。您可以像原樣使用地圖,或視需要修改這些地圖。將啟動遊戲映射與其他遊戲資產搭配使用,例如 Unreal Engine 提供的第三方範本專案。這些資產可在 Content Browser 中使用。您可以使用它們來測試外掛程式的部署工作流程,或做為為遊戲建立自訂後端服務的指南。
啟動映射具有下列特性:
它包含 Anywhere 機群和受管機EC2群的邏輯。當您執行用戶端時,您可以選擇連線到任一機群。
用戶端功能包括尋找遊戲工作階段 ()
SearchGameSessions()
、建立新的遊戲工作階段 (CreateGameSession()
),以及直接加入遊戲工作階段。它從專案的 Amazon Cognito 使用者集區取得唯一的玩家 ID (這是部署的 Anywhere 解決方案的一部分)。
使用啟動遊戲映射
在 UE 編輯器中,開啟專案設定、地圖和模式頁面,然後展開預設地圖區段。
對於編輯器啟動映射 ,從下拉式清單中選取「StartupMap」。您可能需要搜尋位於 的檔案
... > Unreal Projects/[project-name]/Plugins/Amazon GameLift Plugin Content/Maps
。對於遊戲預設地圖 ,請從下拉式清單中選取相同的「StartupMap」。
對於伺服器預設映射 ,選取「ThirdPersonMap」。這是遊戲專案中包含的預設地圖。此地圖專為遊戲中的兩個玩家而設計。
開啟伺服器預設映射的詳細資訊面板。將GameMode 覆寫設定為「無」。
展開預設模式區段,並將全域預設伺服器遊戲模式設定為您為伺服器整合更新的遊戲模式。
對專案進行這些變更之後,您就可以建置遊戲元件。
封裝遊戲元件
封裝您的遊戲伺服器和遊戲用戶端建置
建立新的伺服器和用戶端目標檔案
在遊戲專案資料夾中,前往來源資料夾並尋找
Target.cs
檔案。將檔案複製到
[project-name]Editor.Target.cs
兩個名為[project-name]Client.Target.cs
和 的新檔案[project-name]Server.Target.cs
。編輯每個新檔案以更新類別名稱和目標類型值,如下所示:
UnrealProjects > MyGame > Source > MyGameClient.Target.cs // Copyright Epic Games, Inc. All Rights Reserved. using UnrealBuildTool; using System.Collections.Generic; public class MyGameClientTarget : TargetRules { public MyGameClientTarget(TargetInfo Target) : base(Target) { Type = TargetType.Client; DefaultBuildSettings = BuildSettingsVersion.V2; IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_1; ExtraModuleNames.Add("MyGame"); } }
UnrealProjects > MyGame > Source > MyGameServer.Target.cs // Copyright Epic Games, Inc. All Rights Reserved. using UnrealBuildTool; using System.Collections.Generic; public class MyGameServerTarget : TargetRules { public MyGameServerTarget(TargetInfo Target) : base(Target) { Type = TargetType.Server; DefaultBuildSettings = BuildSettingsVersion.V2; IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_1; ExtraModuleNames.Add("MyGame"); } }
更新
.Build.cs
檔案。開啟專案
.Build.cs
的檔案。此檔案位於UnrealProjects/[project name]/Source/[project name]/[project name].Build.cs
:更新
ModuleRules
類別,如下列程式碼範例所示。public class MyGame : ModuleRules { public GameLiftUnrealApp(TargetInfo Target) { PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" }); bEnableExceptions = true; if (Target.Type == TargetRules.TargetType.Server) { PublicDependencyModuleNames.AddRange(new string[] { "GameLiftServerSDK" }); PublicDefinitions.Add("WITH_GAMELIFT=1"); } else { PublicDefinitions.Add("WITH_GAMELIFT=0"); } } }
重建您的遊戲專案解決方案。
在來源建置的 Unreal Engine 編輯器版本中開啟遊戲專案。
為您的用戶端和伺服器執行下列動作:
選擇目標。前往平台、Windows 並選擇下列其中一項:
伺服器:
[your-application-name]Server
用戶端:
[your-application-name]Client
啟動建置。前往平台、Windows、套件專案 。
每個封裝程序都會產生可執行檔: [your-application-name]Client.exe
或 [your-application-name]Server.exe
。
在 外掛程式中,設定用戶端和伺服器在本機工作站上建置可執行檔的路徑。
步驟 3:連線至 Anywhere 機群
在此步驟中,您會指定要使用的 Anywhere 機群。Anywhere 機群定義了運算資源的集合,可以位於任何地方,用於遊戲伺服器託管。
如果您目前使用的 AWS 帳戶具有現有的 Anywhere 機群,請開啟機群名稱下拉式清單欄位,然後選擇機群。此下拉式清單僅顯示目前作用中使用者設定檔 AWS 區域中的 Anywhere 機群。
如果沒有現有機群,或您想要建立新的機群,請選擇建立新的 Anywhere 機群並提供機群名稱。
在您為專案選擇 Anywhere 機群之後,Amazon 會 GameLift 驗證機群狀態為作用中的廣告會顯示機群 ID。您可以在 Unreal 編輯器的輸出日誌中追蹤此請求的進度。
步驟 4:註冊您的工作站
在此步驟中,您會在新的 Anywhere 機群中將本機工作站註冊為運算資源。
將工作站註冊為 Anywhere 運算
輸入本機機器的運算名稱。如果您在機群中新增多個運算,名稱必須是唯一的。
為您的本機機器提供 IP 地址。此欄位預設為機器的公有 IP 地址。只要您在相同電腦上執行遊戲用戶端和伺服器,也可以使用 localhost (127.0.0.1)。
選擇註冊運算。您可以在 Unreal 編輯器的輸出日誌中追蹤此請求的進度。
為了回應此動作,Amazon 會 GameLift 驗證是否可以連線至運算,並傳回新註冊的運算相關資訊。它也會建立遊戲可執行檔初始化與 Amazon GameLift 服務通訊時所需的主控台引數。
步驟 5:產生驗證權杖
在 Anywhere 運算上執行的遊戲伺服器程序需要身分驗證權杖,才能對 GameLift 服務進行呼叫。每當您從外掛程式啟動遊戲伺服器時,外掛程式會自動產生並儲存 Anywhere 機群的身分驗證權杖。驗證權杖值會儲存為命令列引數,您的伺服器程式碼可在執行階段擷取該引數。
在此步驟中,您不需要採取任何動作。
步驟 6:啟動遊戲
此時,您已完成使用 Amazon 啟動和在本機工作站上播放多人遊戲所需的所有任務 GameLift。
播放託管遊戲
啟動您的遊戲伺服器。遊戲伺服器將在準備好託管遊戲工作階段 GameLift 時通知 Amazon。
啟動您的遊戲用戶端並使用新功能來啟動新的遊戲工作階段。此請求 GameLift 會透過新的後端服務傳送至 Amazon。為了回應,Amazon 會 GameLift呼叫遊戲伺服器,在本機機器上執行,以啟動新的遊戲工作階段。當遊戲工作階段準備好接受玩家時,Amazon GameLift 會提供連線資訊,讓遊戲用戶端加入遊戲工作階段。