适用于 Unreal 的插件:使用 Amazon GameLift Anywhere 设置本地测试
在此工作流程中,您可以为 Amazon GameLift 功能添加客户端和服务器游戏代码,然后使用该插件将您的本地工作站指定为测试游戏服务器主机。完成集成任务后,使用该插件来构建您的游戏客户端和服务器组件。
要启动 Amazon GameLift Anywhere 工作流程,请执行以下操作:
在 Unreal 编辑器主工具栏中,选择 Amazon GameLift 菜单,然后选择通过 Anywhere 托管。此操作将打开插件页面部署 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 }
整合客户端游戏地图
启动游戏地图包含蓝图逻辑和用户界面元素,这些元素已经包含请求游戏会话和使用连接信息连接到游戏会话的基本代码。您可以按原样使用地图,也可以根据需要对其进行修改。将启动游戏地图与其他游戏资产一起使用,例如 Unreal Engine 提供的第三人称模板项目。这些资产可在内容浏览器中找到。您可以使用它们来测试插件的部署工作流程,或者作为为游戏创建自定义后端服务的指南。
该启动地图具有以下特性:
它包括 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 在本地工作站上启动和玩多人游戏所需的所有任务。
玩托管游戏
启动游戏服务器。游戏服务器将在准备好托管游戏会话时通知 Amazon GameLift。
启动您的游戏客户端,然后使用新功能启动新的游戏会话。此请求通过新的后端服务发送到 Amazon GameLift。作为回应,Amazon GameLift 会调用在本地计算机上运行的游戏服务器以启动新的游戏会话。当游戏会话准备好接受玩家时,Amazon GameLift 会提供连接信息,供游戏客户端加入游戏会话。