亚马逊AWS官方博客

Amazon GameLift 高阶使用技巧(三)- 使用 Amazon GameLift Servers+ Amazon GameLift Streams 托管游戏服,并构建云游戏串流

前言

在当今快速发展的游戏产业中,开发者面临着前所未有的技术挑战——玩家对游戏体验的期望不断攀升,而全球化市场又要求无缝的跨区域部署与低延迟体验。作为游戏开发者,如何在控制成本的同时构建稳定、高性能的游戏基础设施,成为了我们必须解决的核心问题。

亚马逊云科技通过专门针对游戏行业打造的 GameLift 服务,为这些挑战提供了全面的解决方案。本博客将聚焦于 GameLift 的两大核心组件:成熟的 GameLift Servers 以及最新正式发布的 GameLift Streams。这两项服务共同构建了一个强大的生态系统,支持从游戏开发初期到全球部署,再到创新分发模式的全生命周期。

无论您是 AAA 大型工作室还是独立开发者,了解如何有效利用这些云服务,都将为您的游戏项目带来显著的技术优势和商业价值。

相关服务介绍

Amazon GameLift Servers

Amazon GameLift Servers 作为 Amazon GameLift 生态系统的基石,自 2016 年推出以来已发展成为业界公认的多人游戏托管解决方案。这项完全托管的服务为开发者提供了强大而灵活的工具,使其能够构建和扩展要求苛刻的多人在线游戏。

全球基础设施与性能优势

GameLift Servers 的一大亮点是其广泛的全球覆盖。服务跨越 5 大洲的 24 个区域和 9 个本地区域,提供 99.99% 的可用性,确保了玩家无论身处何地,都能享受低延迟的游戏体验,这对于竞技性游戏尤为关键。

智能资源管理与成本效益

该服务采用了先进的动态扩展技术,能够智能响应玩家需求的波动。在游戏发布、特殊活动或高峰期间,系统可迅速扩容以应对突增的流量;而在低峰期,又能自动缩减资源以优化成本。通过利用 EC2 Spot 实例和 Graviton 处理器等技术,GameLift 可将每玩家基础设施成本降至每月约1美元,大幅提高了运营效率。

广泛的兼容性与简化的集成

GameLift Servers 支持主流游戏引擎如 Unreal 和 Unity,并为 C#、C++ 和 Go 等语言提供 SDK。其灵活的平台支持范围涵盖 PC、主机、移动设备甚至 AR/VR 设备。此外,与 Pragma、Heroic Labs/Nakama 等流行游戏后端的无缝集成,进一步降低了实施门槛。

开发环境与生产环境的桥梁

2022 年 12 月推出的 GameLift Anywhere 显著提升了开发体验。它允许开发者将本地工作站转变为 GameLift 管理的服务器,实现在熟悉环境中快速迭代和调试。这种混合基础设施管理方式不仅加速了开发周期,还为从本地部署向云端过渡提供了平滑路径,使团队能够充分利用现有资源的同时享受云服务的优势。

Amazon GameLift Streams

Amazon GameLift Streams 作为亚马逊云科技新推出的革命性服务,彻底改变了游戏分发的传统模式。这项服务使开发者能够将游戏以 1080p 分辨率和 60 帧每秒的性能直接流式传输到任何支持 WebRTC 的浏览器,无需下载或安装,从而开创了即时游戏体验的新时代。

无障碍的游戏访问体验

GameLift Streams 消除了传统游戏体验中的主要摩擦点——冗长的下载和安装过程。玩家只需通过浏览器即可在几秒钟内开始体验游戏,这种即时访问能力彻底改变了玩家获取和留存的策略。借助亚马逊云科技全球网络基础设施的强大支持,即使是图形要求苛刻的游戏也能在普通硬件上流畅运行。

开发简便性与快速部署

对开发者而言,GameLift Streams 的一大优势是其与现有游戏构建的高度兼容性。支持 Windows、Linux 和 Proton 运行时环境的游戏无需代码修改即可适配云端流式传输,这大大降低了向云游戏转型的技术门槛和成本。开发者只需几步操作即可上传游戏、配置流媒体容量并开始测试,使云游戏技术变得前所未有地易于实施。

战略位置部署与灵活扩展

目前,GameLift Streams 已在六个亚马逊云科技海外区域部署:美国东部(弗吉尼亚北部和俄亥俄)、美国西部(俄勒冈)、亚太地区(东京)以及欧洲(法兰克福和爱尔兰)。这种分布使开发者能够将流媒体资源放置在靠近目标玩家的位置,最大限度地减少延迟。服务还提供了按需扩展的灵活选项,确保资源使用与实际需求相匹配,有效控制运营成本。此外 GameLift Streams 按照串流数和时长收费,不再收取串流的数据传输费用。

创新应用场景与商业机会

GameLift Streams 为游戏发行开辟了全新路径。开发者可以建立自己的分发渠道或订阅平台,与玩家建立直接联系;通过网站链接、广告或直播平台提供即时试玩体验,提高玩家转化率;甚至可以为老旧游戏目录注入新生命,无需针对现代系统进行兼容性更新。此外,安全的云端测试环境也简化了游戏测试流程,加速了开发周期并保护了知识产权。

如万代南梦宫的“高达元宇宙”项目所证明,GameLift Streams 不仅适用于传统游戏体验,还能支持将游戏品牌扩展到虚拟社交和商业空间的创新应用,展现了云游戏技术的广阔前景。

Amazon GameLift 与游戏项目集成

本次实验将使用 Lyra 示例游戏进行。Lyra 是 Epic Games 为虚幻引擎 5(Unreal Engine 5)开发的一个完整游戏示例项目,于 2022 年 4 月随 UE5 正式版一同发布。与以往的示例内容不同,Lyra 不仅是一个技术演示,更是一个功能齐全的游戏框架,展示了如何使用最新的引擎功能构建现代游戏。

Lyra 分为服务端和客户端两部分,本实验将服务端运行在基于 Graviton 的 GameLift Servers 上,客户端由 GameLift Streams 串流到玩家的浏览器。为实现以上目的,需要下载编译虚幻引擎和 Lyra 工程文件,并将 Lyra 进行源码上的微调以适配 GameLift,最后打包成 ARM64 格式的服务端以在 Graviton 上运行。

为保证串流体验,选择东京(ap-northeast-1)区域来进行实验。

方案架构

下载编译 Unreal Engine

下载 Unreal Engine 的源码首先需要将 GitHub 账号连接到 Epic Games,编译推荐使用 Visual Studio 2022,并注意 Unreal Engine 和 VS 的版本适配。建议参考这篇博客来下载编译 UE5.3。

下载编译 Lyra 示例游戏

Lyra 示例游戏需要在 Epic Games Store 下载。在 Epic Games Store 的 Unreal Engine 的 Sample 中找到 Lyra Starter Game,创建 Lyra 工程。注意在创建时,选择与上一节中所编译的虚幻引擎相一致的版本。

创建好后,需要构建 Lyra 工程。

在我们指定的下载目录下找到 Lyra 工程文件,在 LyraStarterGame 上右键 Generate Visual Studio project files,生成 LyraStarterGame.sln 项目文件。

双击 LyraStarterGame.uproject 文件,在 Unreal Editor 中打开 Lyra。参考此博客的“调整工程参数”部分,调整 Lyra 的场景。

在构建的过程中,可能会在 Visual Studio 2022 中 Build Solution 报错,信息如下:

CS8604 Possible null reference argument for parameter 'other' in 'void HashSet<string>.UnionWith(IEnumerable<string> other)'.BuildGraph.Automation
CA2200 Re-throwing caught exception changes stack information

可以参考 https://github.com/EpicGames/UnrealEngine/pull/11110/files 这个 PR 来进行修改后,重新 Build Solution。

构建 GameLift Plugin

Amazon GameLift plugin 支持在 Windows 以及 Linux 服务器进行构建,但是我们需要将 GameLift Server SDK 进行重新编译并将其放到 UE project 的 plugin 里。

C++ Server SDK 下载地址

编译 GameLift Managed Servers C++ SDK For Windows

前提条件:需要安装 Git.

打开 Visual Studio 2022 Developer Command Prompt

修改目录至 C++ SDK 文件目录

使用以下命令编译 Windows 版本插件

mkdir out 
cd out 
cmake -G "Visual Studio 17 2022" -DBUILD_FOR_UNREAL=1 .. 
msbuild ALL_BUILD.vcxproj /p:Configuration=Release
PowerShell

上面命令会产生两个文件至 out\gamelift-server-sdk\Release 目录

aws-cpp-sdk-gamelift-server.lib、aws-cpp-sdk-gamelift-server.dll

编译 GameLift Managed Servers C++ SDK For Linux2023

创建测试使用 EC2,注意要选择 ARM 架构的实例

通过 SSH 登陆到 EC2 上,安装 git 并 clone amazon-gamelift-toolkit

sudo yum install git docker
sudo systemctl enable docker
sudo systemctl start docker
git clone https://github.com/aws/amazon-gamelift-toolkit.git
cd amazon-gamelift-toolkit/building-gamelift-server-sdk-for-unreal-engine-and-amazon-linux
Bash

在这里我们需要确认当前的 UE 支持的 OpenSSL 的版本,在 VS2022 中搜索 OpenSSL,OpenSSL.Build.cs 文件中指示了版本,如 1.1.1t。

接下来,需要在下载的 amazon-gamelift-toolkit 中做一些修改来编译 GameLift Servers SDK。

首先需要修改 Dockerfile,如下所示:

FROM public.ecr.aws/amazonlinux/amazonlinux:latest as build-server

# Install dependencies
RUN yum install -y gcc-c++ gdb cmake3 git wget openssl openssl-devel tar perl sudo zip unzip

# Install correct OpenSSL version for Unreal Engine 5
RUN wget https://github.com/openssl/openssl/archive/refs/tags/OpenSSL_1_1_1t.tar.gz && \
    tar -xzvf OpenSSL_1_1_1t.tar.gz && \
    cd openssl-OpenSSL_1_1_1t/ && \
    ./config && \
    make && \
    make install && \
    mkdir -p lib && \
    cp libssl.so.1.1 /lib && \
    cp libcrypto.so.1.1 /lib

# Set OpenSSL environment and verify version
RUN export LD_LIBRARY_PATH=/usr/local/lib:/usr/local/lib64 && \
    openssl version

# Create binaries directory and copy OpenSSL libraries
RUN mkdir -p /binaries && \
    cp /openssl-OpenSSL_1_1_1t/libssl.so.1.1 /binaries/ && \
    cp /openssl-OpenSSL_1_1_1t/libcrypto.so.1.1 /binaries/

# Download and build the GameLift Server SDK
RUN echo "Download and unzip GameLift Server SDK 5.1.2" && \
    mkdir -p /SDK && cd /SDK && \
    wget https://gamelift-server-sdk-release.s3.us-west-2.amazonaws.com/cpp/GameLift-Cpp-ServerSDK-5.1.2.zip && \
    unzip GameLift-Cpp-ServerSDK-5.1.2.zip && \
    echo "Build the GameLift server SDK" && \
    mkdir -p cmake-build && \
    export OPENSSL_ROOT_DIR=/openssl-OpenSSL_1_1_1t/ && \
    export OPENSSL_LIBRARIES=/openssl-OpenSSL_1_1_1t/lib/ && \
    cmake -G "Unix Makefiles" -DBUILD_FOR_UNREAL=1 -DCMAKE_BUILD_TYPE=Release -S . -B ./cmake-build && \
    cmake --build cmake-build --target all && \
    cd ./cmake-build/prefix/ && \
    echo "Copying files over to binaries folder" && \
    cp -r ./lib/* /binaries/ && \
    echo "Copying over headers" && \
    mkdir -p /binaries/include && \
    cp -r ./include/* /binaries/include/

# Export only the binaries
FROM scratch AS server
COPY --from=build-server /binaries/ /

接下来修改 buildbinaries.sh ,主要是修改 docker build 中的 platform 值为 arm64。

#!/bin/bash
echo "Building the Unreal GameLift Server SDK binaries for Amazon Linux 2023 (ARM64)..."

# 确保启用BuildKit
export DOCKER_BUILDKIT=1

# 创建输出目录
mkdir -p ./output

# 构建ARM64架构的Docker镜像并提取二进制文件
docker buildx build --platform=linux/arm64 --output=type=local,dest=./output --target=server .

echo "Build completed. Creating ZIP archive..."

# 打包所有输出文件
cd output
zip -r ../AL2023GameliftUE5sdk_arm64.zip ./*
cd ..

echo "Done! Your ARM64 binaries are available in AL2023GameliftUE5sdk_arm64.zip"
Bash

执行命令,获取编译包:

sudo ./buildbinaries.sh
Bash

执行完毕后,下载 AL2023GameliftUE5sdk_arm64.zip 至 Windows 机器上供后续编译使用。

配置 Amazon GameLift Unreal Plugin

在亚马逊云科技官网下对应 UE 版本的插件,解压文件,并拷贝插件至 Lyra 项目中,目录结构如下:

在 UE Project 中添加 GameLiftServerSDK Plugin,在 LyraStarterGame.uproject 文件中添加如下记录:

{"Name": "GameLiftServerSDK","Enabled": true}
C++

/Source/LyraGame/LyraGame.Build.cs 文件中添加 GameLiftServerSDK 到公共依赖列表中:

PublicDependencyModuleNames.AddRange(
    new string[] {
        "Core",
        ...
        "GameLiftServerSDK" # 添加这一行至此
    }
);
C++

解压 AL2023GameliftUE5sdk_arm64.zip 并将其中的 libaws-cpp-sdk-gamelift-server.so 文件拷贝至 LyraStarterGame\Plugins\GameLiftServerSDK\ThirdParty\GameLiftServerSDK\Linux\x86_64-unknown-linux-gnu 目录下。

拷贝上面 Windows 下编译的两个文件 aws-cpp-sdk-gamelift-server.lib、aws-cpp-sdk-gamelift-server.dll 到目录 LyraStarterGame\Plugins\GameLiftServerSDK\ThirdParty\GameLiftServerSDK\Win64。

打开 LyraStarterGame.uprojet,会提示如下信息,点击 Yes 重新 rebuild,期间可能没有任何提示,请等待几分钟,rebuild 成功后会自动打开 Unreal Engine。

构建 Lyra 服务端

本文我们会集成 GameLift Anywhere Fleet 和 GameLift Managed Fleet 两种方式,当我们在 Anywhere 模式调用 InitSDK() 来初始化 Amazon GameLift 服务器 SDK 时,您需要提供一个 FServerParameters 对象,其中包含了 SDK 应该如何初始化的值。而对于托管的 Amazon EC2 实例,这些服务器参数值可以留空。

我们需要在代码中实现:

  • 通过 InitSDK 操作初始化 Amazon GameLift 服务器 SDK,如果提供了命令行参数,则使用这些参数。
  • 通过 ProcessReady 操作通知 Amazon GameLift 服务,我们的进程已准备好托管玩家,实现回调函数来处理来自 Amazon GameLift 服务的请求,以检查进程健康状况、激活新的游戏会话和终止运行中的游戏会话。

打开 Source\LyraGame\GameModes\LyraGameMode.h 添加代码如下:
private:
void InitGameLift();

protected:
virtual void BeginPlay() override;

打开 Source\LyraGame\LyraGameMode.cpp,并在文件顶部包含 Amazon Gamelift Server SDK 标头文件:

#include “GameLiftServerSDK.h”
C++

在文件 LyraGameMode.cpp 末尾处添加 GameLift 集成相关代码:

void ALyraGameMode::BeginPlay()
{
    // Code enclosed with the WITH_GAMELIFT=1 flag is only processed if the following are true:
    // The plugin found the Amazon GameLift server SDK binary files.
    // The build is a game server : Target.Type == TargetRules.TargetType.Server

#if WITH_GAMELIFT
    InitGameLift();
#else
    UE_LOG(LogTemp, Warning, TEXT("WITH_GAMELIFT is false. InitGameLift() not called."));
#endif
}

void ALyraGameMode::InitGameLift()
{
    FString AuthToken;
    FString HostId;
    FString FleetId;
    FString WSSUrl;

    FServerParameters serverParameters;
    FGameLiftGenericOutcome initSdkOutcome;

    UE_LOG(LogLyra, Log, TEXT("Running on port %d"), GetWorld()->URL.Port);

    bool bIsAnywhereActive = false;
    if (FParse::Param(FCommandLine::Get(), TEXT("glanywhere")))
    {
        bIsAnywhereActive = true;
    }
    else
    {
        UE_LOG(LogLyra, Log, TEXT("The -glanywhere parameter is not active."));
    }

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

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

        FParse::Value(FCommandLine::Get(), TEXT("-authtoken="), serverParameters.m_authToken);

        if (FParse::Value(FCommandLine::Get(), TEXT("-hostid="), serverParameters.m_hostId))
        {
            UE_LOG(LogLyra, Log, TEXT("HOST_ID: %s"), *serverParameters.m_hostId)
        }

        if (FParse::Value(FCommandLine::Get(), TEXT("-fleetid="), serverParameters.m_fleetId))
        {
            UE_LOG(LogLyra, Log, TEXT("FLEET_ID: %s"), *serverParameters.m_fleetId)
        }

        if (FParse::Value(FCommandLine::Get(), TEXT("-websocketurl="), serverParameters.m_webSocketUrl))
        {
            UE_LOG(LogLyra, Log, TEXT("WEBSOCKET_URL: %s"), *serverParameters.m_webSocketUrl)
        }

#ifndef __linux__
        serverParameters.m_processId = FString::Printf(TEXT("%d"), GetCurrentProcessId());
#else
        serverParameters.m_processId = FString::Printf(TEXT("%d"), getpid());
#endif

        UE_LOG(LogLyra, Log, TEXT("PID: %s"), *serverParameters.m_processId);

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

        //InitSDK will establish a local connection with GameLift's agent to enable further communication.
        initSdkOutcome = gameLiftSdkModule->InitSDK(serverParameters);
    }
    else 
    {
        UE_LOG(LogLyra, Log, TEXT("Configuring server parameters for Managed Fleet"));
        initSdkOutcome = gameLiftSdkModule->InitSDK();
    }

    if (initSdkOutcome.IsSuccess())
    {
        UE_LOG(LogLyra, Log, TEXT("GameLift InitSDK succeeded"));
    }
    else
    {
        UE_LOG(LogLyra, Log, TEXT("ERROR: InitSDK failed"));
        FGameLiftError gameLiftError = initSdkOutcome.GetError();
        UE_LOG(LogLyra, Log, TEXT("ERROR: %s"), *gameLiftError.m_errorMessage);
        return;
    }

    //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()
    auto onGameSession = [=](Aws::GameLift::Server::Model::GameSession gameSession)
        {
            FString gameSessionId = FString(gameSession.GetGameSessionId());
            UE_LOG(LogLyra, Log, TEXT("GameSession Initializing: %s"), *gameSessionId);
            gameLiftSdkModule->ActivateGameSession();
        };
    FProcessParameters* params = new FProcessParameters();
    params->OnStartGameSession.BindLambda(onGameSession);

    //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.
    params->OnTerminate.BindLambda([=]() {
        UE_LOG(LogLyra, 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!
    params->OnHealthCheck.BindLambda([]() {
        UE_LOG(LogLyra, Log, TEXT("Performing Health Check"));
        return true;
        });

    //The game server takes the port from the Unreal world.
    params->port = GetWorld()->URL.Port;

    //Here, the game server tells GameLift what set of files to upload when the game session ends.
    //GameLift will upload everything specified here for the developers to fetch later.

    TArray<FString> logfiles;
    logfiles.Add(TEXT("LyraStarterGame/Saved/Logs/LyraStarterGame"));
    params->logParameters = logfiles;
    //Calling ProcessReady tells GameLift this game server is ready to receive incoming game sessions!
    UE_LOG(LogLyra, Log, TEXT("Calling Process Ready"));
    FGameLiftGenericOutcome processReadyOutcome = gameLiftSdkModule->ProcessReady(*params);
    if (processReadyOutcome.IsSuccess())
    {
        UE_LOG(LogLyra, Log, TEXT("Process Ready Succeded"));
    }
    else
    {
        UE_LOG(LogLyra, Log, TEXT("ERROR: Process Ready Failed"));
        FGameLiftError processReadyError = processReadyOutcome.GetError();
        UE_LOG(LogLyra, Log, TEXT("ERROR: %s"), *processReadyError.m_errorMessage);
    }
    UE_LOG(LogLyra, Log, TEXT("Init GameLift complete"));
}
C++

接下来是修改 Lyra 服务端能够识别 ARM64 架构的服务器。打开 GameLift Server SDK 中代码文件 GameLiftServerSDK.Build.cs,修改其中代码,大概在 45 行:

# 在下面的判断条件中添加: || Target.Platform == UnrealTargetPlatform.LinuxArm64,示例代码如下:
if (Target.Platform == UnrealTargetPlatform.Linux || Target.Platform == UnrealTargetPlatform.LinuxArm64)
{
    SDKDirectory = System.IO.Path.Combine(SDKDirectory, "x86_64-unknown-linux-gnu");
    string SDKLib = System.IO.Path.Combine(SDKDirectory, "libaws-cpp-sdk-gamelift-server.so");
                
    PublicAdditionalLibraries.Add(SDKLib);
    RuntimeDependencies.Add(SDKLib);
}
C++

在 Plugin 下创建 LinuxArm64 目录:

复制在 EC2 上编译的 AL2023GameliftUE5sdk_arm64 中的文件到 LinuxArm64 目录下:

打开 Visual Studio 2022,选择 Development Server 平台,选择 LinuxArm64,重新 Build Solution:

完成后,将选择更改为 Development Client,平台选择 Win64,再次选择菜单中的“Build”->“Build Solution”来重复生成步骤。

通过交叉编译来打包 Lyra Server,我们选择 LinuxArm64->Development->LyraServer 选项,首先 Cook Content,然后再 Package Project:

等待几分钟后,LinuxArm64 下的游戏包打包完毕,此时我们需要将 libssl.so.1.1 和 libcrypto.so.1.1 放到打包后游戏服务端的目录内:

至此,Lyra 服务端打包完毕。稍后会用到此服务端在基于 Graviton 的 EC2 上 host 游戏服务器,并注册到 GameLift Servers Anywhere。

构建 Lyra 客户端

Lyra 客户端的构建和服务端构建类似,在 UE 中选择 Windows 下的 Development 和 LyraClient 选项,先进行 Cook Content 烘焙,再 Package Project 打包即可,无需其他修改。

生成的 WindowsClient 文件夹中包含了客户端的可执行文件,稍后我们将其上传到 GameLift Streams 进行串流使用。

部署游戏环境

配置 GameLift Servers Anywhere 托管游戏服务器

首先将打包好的 Lyra 服务端上传到编译 amazon-gamelift-toolkit 的 EC2 上。之后执行以下命令,创建一个 GameLift 的 location:

aws gamelift create-location --location-name custom-arm-test
Bash

使用新创建的位置创建一个 Fleet,并将计算类型指定为 ANYWHERE:

aws gamelift create-fleet --name arm-test-fleet --compute-type ANYWHERE --locations "Location=custom-arm-test"
Bash

注册 EC2 至 Fleet,注意 EC2 需要有公网 IP 地址,ip-address 参数处填写公网 IP:

aws gamelift register-compute --fleet-id <fleet-id> --compute-name linux-server1 --ip-address <ipaddrees> --location custom-arm-test
Bash

系统会返回一个 JSON 对象,请记录下 GameLiftServiceSdkEndpoint,后续步骤中需要将其传递给我们的游戏服务器,返回的 JSON 格式如下:

{
    "Compute": {
        "FleetId": "fleet-daa90c56xxxxxx",
        "FleetArn": "arn:aws:gamelift:ap-northeast-1:1965xx:fleet/fleet-daa90c56xxx",
        "ComputeName": "linux-server1",
        "ComputeArn": "arn:aws:gamelift:ap-northeast-1:1965xx:compute/linux-server1",
        "IpAddress": "52.xxxxx",
        "ComputeStatus": "Active",
        "Location": "custom-arm-test",
        "CreationTime": "2025-03-07T15:56:xxx",
        "GameLiftServiceSdkEndpoint": "wss://ap-northeast-1.api.amazongamelift.com",
        "GameLiftAgentEndpoint": "wss://ap-northeast-1.process-manager-api.amazongamelift.com"
    }
}
JSON

获取计算授权令牌:

aws gamelift get-compute-auth-token --fleet-id <FleetId> --compute-name linux-server1
Bash

进入 Lyra 服务端目录,使用如下命令启动服务,并看到如下日志则表示启动成功:

./LyraServer-Arm64.sh -log -glanywhere -fleetid=<fleet-id> -hostid=linux-server1 -websocketurl=wss://ap-northeast-1.api.amazongamelift.com -authtoken=<上面获取到的token>
Bash

创建 Game Session 进行测试:

aws gamelift create-game-session —fleet-id <fleet-id> —maximum-player-session-count 2 —location custom-arm-test
Bash

返回信息类似如下:

{
    "GameSession": {
        "GameSessionId": "arn:aws:gamelift:ap-northeast-1::gamesession/fleet-daa90c56xxx/custom-arm-test/gsess-89447c93-cbcb-40xxx",
        "FleetId": "fleet-daa90c56xxx",
        "FleetArn": "arn:aws:gamelift:ap-northeast-1:19650xxx:fleet/fleet-daa90c56xxx",
        "CreationTime": "2025-03-07T16:02:25.763000+00:00",
        "CurrentPlayerSessionCount": 0,
        "MaximumPlayerSessionCount": 2,
        "Status": "ACTIVATING",
        "GameProperties": [],
        "IpAddress": "52.xxx",
        "Port": 7777,
        "PlayerSessionCreationPolicy": "ACCEPT_ALL",
        "Location": "custom-arm-test"
    }
}
JSON

注意 Lyra 客户端需要通过 7777 端口和服务器通信,请开放 EC2 的 UDP 7777 端口。

配置 Amazon GameLift Streams 串流游戏客户端

首先,我们将之前打包好的 Lyra 客户端上传到 S3 桶。注意客户端不能压缩,需要以文件夹的形式上传到 S3 桶中。上传后的目录结构参考下图:

在亚马逊云科技控制台中,搜索 GameLift Streams 服务并打开。注意在右上角确认是东京区域,点击 Create a stream。

进入 Create application 界面。选择 Proton 8.0-5 作为 Runtime。Proton 是将 Windows 文件运行在 Linux 系统上的兼容层,Lyra 游戏能够兼容 Proton,成本比使用 Windows 的 Runtime 更低。在 General setting 中,填写串流应用名称,并指定上传到 S3 的 Lyra 客户端根目录和可执行文件。一切完成后,滑动到页面底部点击 Create application 创建应用。

接下来,打开 Stream groups,点击 Create stream group,为刚刚创建的应用新建一个串流组:

选择串流的机型。串流机型根据不同的 Runtime 和 GPU 分成诸多 Stream class。机型的硬件参数可以在底部查看,在这里选择 gen5n_high。

把串流组链接到刚刚创建的 Lyra 应用上:

选择串流组的容量。串流组由两种容量组成,Always-on 会一直运行,当玩家登录到云串流时,立刻就能够拉起串流的 session,费用会持续产生。On-demand 会根据玩家数动态拉起串流实例,可能会有几分钟的延迟才能够开始云游戏。这里我们选择 Always-on capacity 为 2,On-demand capacity 为 0。

检查无误后,创建串流组。串流组配置完成。

联机游戏

连接到串流

上一步中,我们创建了串流应用和串流组。接下来,我们连接到串流应用来联机游戏。注意,联机游戏需要保证 GameLift Servers 正在运行 Lyra 的服务端并创建了 game session。连接到串流有很多种方式,GameLift Streams 提供了 SDK 允许用户将串流集成到自己的应用中,在亚马逊云科技控制台中,GameLift Streams 也提供了便捷的串流测试界面。

在 GameLift Streams 控制台中测试串流

点击 Test stream,选择刚刚创建的串流组。

在 Test stream 界面的 Program configurations 中,找到 Command-line arguments 栏,输入 EC2 的 IP:7777 作为参数,连接到 GameLift Servers Anywhere 服务器。点击 Test stream。

进入到串流页面,点击屏幕灰色部分连接到串流。稍等片刻,即可进入 Lyra 的游戏界面。至此,通过 GameLift Streams 串流 Lyra 连接到 GameLift Servers 的全部流程已经跑通。

搭建前端页面串流

GameLift Streams 提供了串流前端页面的示例代码。下载并解压后,使用以下命令启动前端服务器。

npm install
node server.js
Bash

需要注意在运行前端服务器的设备上,应配置好 IAM 权限和区域,使其能够调用 GameLift Streams 服务。在浏览器中输入<IP>:8000 打开串流页面。

在 GameLift Streams 的 Stream group 中获取 Stream Group ID,Application ID,Placement Locations 信息,填入到串流页面中,在 Command-Line Arguments 中填写 GameLift Servers 的 IP 和端口。注意在填写 Placement Locations 和 Command-Line Arguments 时,需要使用 JSON list of string 的格式,可参考图上填写。

点击 Click To Start,进入到串流页面。

在示例给出的串流前端代码中,server.js 文件通过 GameLiftStreams 类,实现串流资源的构建。此外在根目录的 gameliftstreams-1.0.0.js 文件中,还可以看到以下几个处理输入事件的方法:

processKeyboardEvent(e)  // 处理键盘事件
processMouseEvent(e)     // 处理鼠标事件
processGamepads()        // 轮询游戏手柄状态
JavaScript

输入的信号会通过专门的数据通道传输到服务端。这个数据通道被配置为无序传输且不重传,这对于实时输入处理来说是最佳选择。

因为客户端库中已经配置了鼠标、键盘、手柄等输入,在串流开始时,只需要调用 attachInput() 方法即可开始接收设备的输入信号。在示例串流代码中,点击 Attach Input 时,会触发这一操作。

// 在客户端库中
createDataChannel("inputChannel", {
  ordered: false,
  maxRetransmits: 0
})
JavaScript

更多 WebSDK 的开发参考,可参阅开发文档

多人对战

接下来,我们通过 Windows 的 Lyra 客户端,同样连接到 GameLift Server,与 GameLift Streams 上的串流玩家联机对战。

在 Windows 上找到打包的 WindowsClient,使用命令启动游戏:

.\LyraClient.exe <IP>:7777
PowerShell

进入到游戏界面,使用 Tab 键可以查看到 GameLift Streams 上的串流玩家。

同样的,在串流端,能识别到来自于 Windows 的玩家。

总结

本篇博客基于 Amazon GameLift Servers 和 Amazon GameLift Stream 服务,使用 Unreal Engine 的 Lyra 游戏,构建了一套基于云端的游戏前后端托管和串流案例。通过本案例,可以实现更加轻松的游戏后端云上托管,以及更安全和更便捷的游戏游玩体验。基于此架构,能够轻松将游戏拓展到全球规模,同时能够避免因玩家硬件原因导致的游戏体验不佳或测试版游戏本体泄露等问题,带来全新的游戏体验。

系列博客

参考

本篇作者

于泽沛

亚马逊云科技游戏行业解决方案架构师,负责云计算解决方案的咨询和设计,在 AI/ML 、DevOps 等领域有丰富的经验。

郭俊龙

亚马逊云科技解决方案架构师,主要负责游戏行业客户解决方案设计,比较擅长云原生微服务以及大数据方案设计和实践。