游戏实例:ULyraGameInstance是怎么管理在线会话、加载屏幕等的行为?
对于在线会话:它是一个协调者和响应者。它通过绑定到的事件,在会话生命周期的关键时刻(如客户端旅行前)注入游戏特定的逻辑和参数。对于加载屏幕:它是一个架构定义者。它通过设置一个基于“初始化状态”的框架,为整个游戏(包括加载屏幕的显示与隐藏、资源的加载与初始化)提供了一个清晰的生命周期。具体的加载屏幕UI则由其他组件在这个状态框架内驱动。
LyraGameInstance.h
// 版权所有 Epic Games, Inc. 保留所有权利。
#pragma once
#include "CommonGameInstance.h"
#include "LyraGameInstance.generated.h"
#define UE_API LYRAGAME_API
class ALyraPlayerController;
class UObject;
UCLASS(MinimalAPI, Config = Game)
class ULyraGameInstance : public UCommonGameInstance
{
GENERATED_BODY()
public:
/**
* 构造函数
* @param ObjectInitializer 对象初始化器
*/
UE_API ULyraGameInstance(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
/**
* 获取主玩家控制器
* @return 返回主玩家控制器指针
*/
UE_API ALyraPlayerController* GetPrimaryPlayerController() const;
/**
* 检查是否可以加入请求的会话
* @return 如果可以加入返回true,否则返回false
*/
UE_API virtual bool CanJoinRequestedSession() const override;
/**
* 处理用户初始化完成
* @param UserInfo 用户信息
* @param bSuccess 是否成功
* @param Error 错误信息
* @param RequestedPrivilege 请求的权限
* @param OnlineContext 在线上下文
*/
UE_API virtual void HandlerUserInitialized(const UCommonUserInfo* UserInfo, bool bSuccess, FText Error, ECommonUserPrivilege RequestedPrivilege, ECommonUserOnlineContext OnlineContext) override;
/**
* 接收网络加密令牌
* @param EncryptionToken 加密令牌
* @param Delegate 加密密钥响应委托
*/
UE_API virtual void ReceivedNetworkEncryptionToken(const FString& EncryptionToken, const FOnEncryptionKeyResponse& Delegate) override;
/**
* 接收网络加密确认
* @param Delegate 加密密钥响应委托
*/
UE_API virtual void ReceivedNetworkEncryptionAck(const FOnEncryptionKeyResponse& Delegate) override;
protected:
/**
* 初始化游戏实例
*/
UE_API virtual void Init() override;
/**
* 关闭游戏实例
*/
UE_API virtual void Shutdown() override;
/**
* 在客户端旅行到会话前的处理
* @param URL 旅行URL
*/
UE_API void OnPreClientTravelToSession(FString& URL);
/** 用于测试加密代码的硬编码加密密钥。这不安全,不要在生产环境中使用此技术! */
TArray<uint8> DebugTestEncryptionKey; // 调试测试加密密钥
};
#undef UE_API
LyraGameInstance.cpp
// 版权所有 Epic Games, Inc. 保留所有权利。
#include "LyraGameInstance.h"
#include "CommonSessionSubsystem.h"
#include "CommonUserSubsystem.h"
#include "Components/GameFrameworkComponentManager.h"
#include "HAL/IConsoleManager.h"
#include "LyraGameplayTags.h"
#include "Misc/Paths.h"
#include "Player/LyraPlayerController.h"
#include "Player/LyraLocalPlayer.h"
#include "GameFramework/PlayerState.h"
#if UE_WITH_DTLS
#include "DTLSCertStore.h"
#include "DTLSHandlerComponent.h"
#include "Misc/FileHelper.h"
#endif // UE_WITH_DTLS
#include UE_INLINE_GENERATED_CPP_BY_NAME(LyraGameInstance)
namespace Lyra
{
static bool bTestEncryption = false; // 测试加密标志
static FAutoConsoleVariableRef CVarLyraTestEncryption( // 控制台变量引用
TEXT("Lyra.TestEncryption"),
bTestEncryption,
TEXT("如果为true,客户端将在加入服务器请求时发送加密令牌,并尝试使用调试密钥加密连接。这不安全,仅用于演示目的。"),
ECVF_Default);
#if UE_WITH_DTLS
static bool bUseDTLSEncryption = false; // 使用DTLS加密标志
static FAutoConsoleVariableRef CVarLyraUseDTLSEncryption( // DTLS加密控制台变量
TEXT("Lyra.UseDTLSEncryption"),
bUseDTLSEncryption,
TEXT("如果使用Lyra.TestEncryption和DTLS数据包处理程序,则设置为true。"),
ECVF_Default);
/* 用于在同一设备上测试多个游戏实例(桌面构建) */
static bool bTestDTLSFingerprint = false; // 测试DTLS指纹标志
static FAutoConsoleVariableRef CVarLyraTestDTLSFingerprint( // DTLS指纹测试控制台变量
TEXT("Lyra.TestDTLSFingerprint"),
bTestDTLSFingerprint,
TEXT("如果为true且使用DTLS加密,将为每个连接生成唯一证书,指纹将写入文件以模拟通过在线服务传递。"),
ECVF_Default);
#if !UE_BUILD_SHIPPING
static FAutoConsoleCommandWithWorldAndArgs CmdGenerateDTLSCertificate( // 生成DTLS证书命令
TEXT("GenerateDTLSCertificate"),
TEXT("生成用于测试的DTLS自签名证书并导出为PEM格式。"),
FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray<FString>& InArgs, UWorld* InWorld)
{
if (InArgs.Num() == 1) // 检查参数数量
{
const FString& CertName = InArgs[0]; // 获取证书名称
FTimespan CertExpire = FTimespan::FromDays(365); // 设置证书过期时间
TSharedPtr<FDTLSCertificate> Cert = FDTLSCertStore::Get().CreateCert(CertExpire, CertName); // 创建证书
if (Cert.IsValid()) // 检查证书是否有效
{
const FString CertPath = FPaths::ProjectContentDir() / TEXT("DTLS") / FPaths::MakeValidFileName(FString::Printf(TEXT("%s.pem"), *CertName)); // 生成证书路径
if (!Cert->ExportCertificate(CertPath)) // 导出证书
{
UE_LOG(LogTemp, Error, TEXT("GenerateDTLSCertificate: 导出证书失败。")); // 记录错误日志
}
}
else
{
UE_LOG(LogTemp, Error, TEXT("GenerateDTLSCertificate: 生成证书失败。")); // 记录错误日志
}
}
else
{
UE_LOG(LogTemp, Error, TEXT("GenerateDTLSCertificate: 无效参数。")); // 记录错误日志
}
}));
#endif // UE_BUILD_SHIPPING
#endif // UE_WITH_DTLS
};
/**
* 构造函数
* @param ObjectInitializer 对象初始化器
*/
ULyraGameInstance::ULyraGameInstance(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer) // 调用父类构造函数
{
}
/**
* 初始化游戏实例
*/
void ULyraGameInstance::Init()
{
Super::Init(); // 调用父类初始化函数
// 注册自定义初始化状态
UGameFrameworkComponentManager* ComponentManager = GetSubsystem<UGameFrameworkComponentManager>(this); // 获取游戏框架组件管理器子系统
if (ensure(ComponentManager)) // 确保组件管理器指针有效,如果无效则断言
{
// 注册各个初始化状态及其依赖关系
ComponentManager->RegisterInitState(LyraGameplayTags::InitState_Spawned, false, FGameplayTag()); // 注册生成状态,无前置依赖
ComponentManager->RegisterInitState(LyraGameplayTags::InitState_DataAvailable, false, LyraGameplayTags::InitState_Spawned); // 注册数据可用状态,依赖生成状态
ComponentManager->RegisterInitState(LyraGameplayTags::InitState_DataInitialized, false, LyraGameplayTags::InitState_DataAvailable); // 注册数据初始化状态,依赖数据可用状态
ComponentManager->RegisterInitState(LyraGameplayTags::InitState_GameplayReady, false, LyraGameplayTags::InitState_DataInitialized); // 注册游戏准备就绪状态,依赖数据初始化状态
}
// 使用设定值初始化调试加密密钥(AES256算法)。注意:这不安全,仅用于示例目的。
DebugTestEncryptionKey.SetNum(32); // 设置调试测试加密密钥数组大小为32字节(AES256需要256位密钥)
for (int32 i = 0; i < DebugTestEncryptionKey.Num(); ++i) // 遍历调试测试加密密钥数组的每个元素
{
DebugTestEncryptionKey[i] = uint8(i); // 将密钥的每个字节设置为对应的索引值(0,1,2,...,31)
}
if (UCommonSessionSubsystem* SessionSubsystem = GetSubsystem<UCommonSessionSubsystem>()) // 获取通用会话子系统并检查是否有效
{
SessionSubsystem->OnPreClientTravelEvent.AddUObject(this, &ULyraGameInstance::OnPreClientTravelToSession); // 将客户端旅行前事件绑定到当前对象的OnPreClientTravelToSession函数
}
}
/**
* 关闭游戏实例
*/
void ULyraGameInstance::Shutdown()
{
if (UCommonSessionSubsystem* SessionSubsystem = GetSubsystem<UCommonSessionSubsystem>()) // 获取会话子系统
{
SessionSubsystem->OnPreClientTravelEvent.RemoveAll(this); // 移除所有客户端旅行前事件绑定
}
Super::Shutdown(); // 调用父类关闭
}
/**
* 获取主玩家控制器
* @return 返回主玩家控制器指针
*/
ALyraPlayerController* ULyraGameInstance::GetPrimaryPlayerController() const
{
return Cast<ALyraPlayerController>(Super::GetPrimaryPlayerController(false)); // 转换并返回主玩家控制器
}
/**
* 检查是否可以加入请求的会话
* @return 如果可以加入返回true,否则返回false
*/
bool ULyraGameInstance::CanJoinRequestedSession() const
{
// 临时第一遍:总是返回true
// 这将进一步完善以检查玩家状态
if (!Super::CanJoinRequestedSession()) // 调用父类检查
{
return false; // 如果父类不允许则返回false
}
return true; // 返回true
}
/**
* 处理用户初始化完成
* @param UserInfo 用户信息
* @param bSuccess 是否成功
* @param Error 错误信息
* @param RequestedPrivilege 请求的权限
* @param OnlineContext 在线上下文
*/
void ULyraGameInstance::HandlerUserInitialized(const UCommonUserInfo* UserInfo, bool bSuccess, FText Error, ECommonUserPrivilege RequestedPrivilege, ECommonUserOnlineContext OnlineContext)
{
Super::HandlerUserInitialized(UserInfo, bSuccess, Error, RequestedPrivilege, OnlineContext); // 调用父类处理
// 如果登录成功,告诉本地玩家加载他们的设置
if (bSuccess && ensure(UserInfo)) // 检查是否成功且用户信息有效
{
ULyraLocalPlayer* LocalPlayer = Cast<ULyraLocalPlayer>(GetLocalPlayerByIndex(UserInfo->LocalPlayerIndex)); // 获取本地玩家
// 专用服务器用户不会附加本地玩家
if (LocalPlayer) // 检查本地玩家是否存在
{
LocalPlayer->LoadSharedSettingsFromDisk(); // 从磁盘加载共享设置
}
}
}
/**
* 接收网络加密令牌
* @param EncryptionToken 加密令牌
* @param Delegate 加密密钥响应委托
*/
void ULyraGameInstance::ReceivedNetworkEncryptionToken(const FString& EncryptionToken, const FOnEncryptionKeyResponse& Delegate)
{
// 这是一个简单的实现,演示使用硬编码密钥加密游戏流量。
// 对于完整的实现,您可能希望从安全源(如通过HTTPS的Web服务)检索加密密钥。
// 这可以在此函数中完成,甚至是异步的 - 只需在知道密钥后调用传入的响应委托。
// EncryptionToken的内容由用户决定,但通常包含用于生成唯一加密密钥的信息,例如用户和/或会话ID。
FEncryptionKeyResponse Response(EEncryptionResponse::Failure, TEXT("未知加密失败")); // 初始化响应
if (EncryptionToken.IsEmpty()) // 检查加密令牌是否为空
{
Response.Response = EEncryptionResponse::InvalidToken; // 设置响应为无效令牌
Response.ErrorMsg = TEXT("加密令牌为空。"); // 设置错误消息
}
else
{
#if UE_WITH_DTLS
if (Lyra::bUseDTLSEncryption) // 检查是否使用DTLS加密
{
TSharedPtr<FDTLSCertificate> Cert; // 证书指针
if (Lyra::bTestDTLSFingerprint) // 检查是否测试DTLS指纹
{
// 为此标识符生成服务器证书,发布指纹
FTimespan CertExpire = FTimespan::FromHours(4); // 设置证书过期时间
Cert = FDTLSCertStore::Get().CreateCert(CertExpire, EncryptionToken); // 创建证书
}
else
{
// 从磁盘加载证书用于测试目的(永远不要在生产环境中使用)
const FString CertPath = FPaths::ProjectContentDir() / TEXT("DTLS") / TEXT("LyraTest.pem"); // 证书路径
Cert = FDTLSCertStore::Get().GetCert(EncryptionToken); // 获取证书
if (!Cert.IsValid()) // 检查证书是否无效
{
Cert = FDTLSCertStore::Get().ImportCert(CertPath, EncryptionToken); // 导入证书
}
}
if (Cert.IsValid()) // 检查证书是否有效
{
if (Lyra::bTestDTLSFingerprint) // 检查是否测试DTLS指纹
{
// 指纹应发布到安全的Web服务以供发现
// 为本地测试写入磁盘
TArrayView<const uint8> Fingerprint = Cert->GetFingerprint(); // 获取指纹
FString DebugFile = FPaths::Combine(*FPaths::ProjectSavedDir(), TEXT("DTLS")) / FPaths::MakeValidFileName(EncryptionToken) + TEXT("_server.txt"); // 调试文件路径
FString FingerprintStr = BytesToHex(Fingerprint.GetData(), Fingerprint.Num()); // 转换为十六进制字符串
FFileHelper::SaveStringToFile(FingerprintStr, *DebugFile); // 保存到文件
}
// 服务器当前只需要标识符
Response.EncryptionData.Identifier = EncryptionToken; // 设置标识符
Response.EncryptionData.Key = DebugTestEncryptionKey; // 设置密钥
Response.Response = EEncryptionResponse::Success; // 设置响应为成功
}
else
{
Response.Response = EEncryptionResponse::Failure; // 设置响应为失败
Response.ErrorMsg = TEXT("无法获取证书。"); // 设置错误消息
}
}
else
#endif // UE_WITH_DTLS
{
Response.Response = EEncryptionResponse::Success; // 设置响应为成功
Response.EncryptionData.Key = DebugTestEncryptionKey; // 设置密钥
}
}
Delegate.ExecuteIfBound(Response); // 执行委托
}
/**
* 接收网络加密确认
* @param Delegate 加密密钥响应委托
*/
void ULyraGameInstance::ReceivedNetworkEncryptionAck(const FOnEncryptionKeyResponse& Delegate)
{
// 这是一个简单的实现,演示使用硬编码密钥加密游戏流量。
// 对于完整的实现,您可能希望从安全源(如通过HTTPS的Web服务)检索加密密钥。
// 这可以在此函数中完成,甚至是异步的 - 只需在知道密钥后调用传入的响应委托。
FEncryptionKeyResponse Response; // 加密密钥响应
#if UE_WITH_DTLS
if (Lyra::bUseDTLSEncryption) // 检查是否使用DTLS加密
{
Response.Response = EEncryptionResponse::Failure; // 设置响应为失败
APlayerController* const PlayerController = GetFirstLocalPlayerController(); // 获取第一个本地玩家控制器
if (PlayerController && PlayerController->PlayerState && PlayerController->PlayerState->GetUniqueId().IsValid()) // 检查玩家控制器和玩家状态
{
const FUniqueNetIdRepl& PlayerUniqueId = PlayerController->PlayerState->GetUniqueId(); // 获取玩家唯一ID
// 理想情况下,加密令牌应直接传入,而不是尝试重新构建它
const FString EncryptionToken = PlayerUniqueId.ToString(); // 转换为字符串
Response.EncryptionData.Identifier = EncryptionToken; // 设置标识符
// 服务器的指纹应从安全服务中拉取
if (Lyra::bTestDTLSFingerprint) // 检查是否测试DTLS指纹
{
// 但为了测试目的...
FString DebugFile = FPaths::Combine(*FPaths::ProjectSavedDir(), TEXT("DTLS")) / FPaths::MakeValidFileName(EncryptionToken) + TEXT("_server.txt"); // 调试文件路径
FString FingerprintStr; // 指纹字符串
FFileHelper::LoadFileToString(FingerprintStr, *DebugFile); // 从文件加载指纹
Response.EncryptionData.Fingerprint.AddUninitialized(FingerprintStr.Len() / 2); // 分配指纹数组大小
HexToBytes(FingerprintStr, Response.EncryptionData.Fingerprint.GetData()); // 十六进制字符串转换为字节
}
else
{
// 从磁盘拉取预期指纹用于测试,这应来自安全服务
const FString CertPath = FPaths::ProjectContentDir() / TEXT("DTLS") / TEXT("LyraTest.pem"); // 证书路径
TSharedPtr<FDTLSCertificate> Cert = FDTLSCertStore::Get().GetCert(EncryptionToken); // 获取证书
if (!Cert.IsValid()) // 检查证书是否无效
{
Cert = FDTLSCertStore::Get().ImportCert(CertPath, EncryptionToken); // 导入证书
}
if (Cert.IsValid()) // 检查证书是否有效
{
TArrayView<const uint8> Fingerprint = Cert->GetFingerprint(); // 获取指纹
Response.EncryptionData.Fingerprint = Fingerprint; // 设置指纹
}
else
{
Response.Response = EEncryptionResponse::Failure; // 设置响应为失败
Response.ErrorMsg = TEXT("无法获取证书。"); // 设置错误消息
}
}
Response.EncryptionData.Key = DebugTestEncryptionKey; // 设置密钥
Response.Response = EEncryptionResponse::Success; // 设置响应为成功
}
}
else
#endif // UE_WITH_DTLS
{
Response.Response = EEncryptionResponse::Success; // 设置响应为成功
Response.EncryptionData.Key = DebugTestEncryptionKey; // 设置密钥
}
Delegate.ExecuteIfBound(Response); // 执行委托
}
/**
* 在客户端旅行到会话前的处理
* @param URL 旅行URL
*/
void ULyraGameInstance::OnPreClientTravelToSession(FString& URL)
{
// 如果需要,添加调试加密令牌。
if (Lyra::bTestEncryption) // 检查是否测试加密
{
#if UE_WITH_DTLS
if (Lyra::bUseDTLSEncryption) // 检查是否使用DTLS加密
{
APlayerController* const PlayerController = GetFirstLocalPlayerController(); // 获取第一个本地玩家控制器
if (PlayerController && PlayerController->PlayerState && PlayerController->PlayerState->GetUniqueId().IsValid()) // 检查玩家控制器和玩家状态
{
const FUniqueNetIdRepl& PlayerUniqueId = PlayerController->PlayerState->GetUniqueId(); // 获取玩家唯一ID
const FString EncryptionToken = PlayerUniqueId.ToString(); // 转换为字符串
URL += TEXT("?EncryptionToken=") + EncryptionToken; // 添加加密令牌到URL
}
}
else
#endif // UE_WITH_DTLS
{
// 这只是一个用于测试/调试的值,服务器将使用相同的密钥,无论令牌值如何。
// 但令牌可以是用户ID和/或会话ID,用于为每个用户和/或会话生成唯一密钥(如果需要)。
URL += TEXT("?EncryptionToken=1"); // 添加默认加密令牌到URL
}
}
}
ULyraGameInstance 作为游戏的“单例”和总协调员,它并不直接实现所有功能,而是通过子系统 和委托绑定 来管理和协调这些全局系统的行为。
下面我们详细分析它是如何管理在线会话和加载屏幕的。
1. 管理在线会话
在线会话的管理主要是通过 UCommonSessionSubsystem 来实现的。ULyraGameInstance 的角色是初始化和响应这个子系统的关键事件。
a. 初始化与事件绑定
在 Init() 函数中,我们看到了关键的一步:
if (UCommonSessionSubsystem* SessionSubsystem = GetSubsystem<UCommonSessionSubsystem>()) // 获取通用会话子系统
{
// 绑定客户端旅行前事件
SessionSubsystem->OnPreClientTravelEvent.AddUObject(this, &ULyraGameInstance::OnPreClientTravelToSession);
}
- 获取子系统:
GetSubsystem<UCommonSessionSubsystem>()获取了引擎或Lyra框架提供的在线会话管理实例。 - 绑定委托:它将子系统的
OnPreClientTravelEvent委托与自身的OnPreClientTravelToSession方法绑定。这意味着,每当客户端即将旅行(连接)到一个新的服务器会话时,会话子系统会发出信号,而游戏实例则负责响应。
b. 在旅行前处理会话参数OnPreClientTravelToSession(FString& URL) 函数是游戏实例介入会话管理的关键环节:
void ULyraGameInstance::OnPreClientTravelToSession(FString& URL)
{
// 如果需要,添加调试加密令牌。
if (Lyra::bTestEncryption)
{
// ... (DTLS 加密逻辑) ...
{
// ... 获取玩家唯一ID ...
URL += TEXT("?EncryptionToken=") + EncryptionToken; // 将会话所需的参数附加到连接URL中
}
// ... (非DTLS 加密逻辑) ...
{
URL += TEXT("?EncryptionToken=1"); // 附加测试用的加密令牌
}
}
}
- 作用:这个函数在客户端即将向服务器发起连接时被调用。
- 行为:它修改了连接的
URL,为其添加了自定义参数(如EncryptionToken)。这是游戏实例影响会话连接行为的主要方式。它可以将必要的认证信息、版本号、自定义选项等注入到会话请求中。 - 总结:游戏实例通过响应会话子系统的事件,在关键时刻(如连接前)为会话设置必要的上下文和参数。
c. 会话可用性检查CanJoinRequestedSession() 函数提供了一个检查点:
bool ULyraGameInstance::CanJoinRequestedSession() const
{
// 临时第一遍:总是返回true
// 这将进一步完善以检查玩家状态
if (!Super::CanJoinRequestedSession())
{
return false;
}
return true;
}
- 虽然当前实现很简单,但这里预留了逻辑位置,用于在未来根据玩家的状态(如是否已完成初始化、是否在某个关键任务中等)来决定是否允许加入一个新会话。
2. 管理加载屏幕
在提供的代码中,没有直接出现“LoadingScreen”的字样,但游戏实例通过另一种更架构化的方式为加载屏幕的管理奠定了基础——游戏框架组件管理器 和初始化状态。
在 Init() 函数中,有另一段关键代码:
UGameFrameworkComponentManager* ComponentManager = GetSubsystem<UGameFrameworkComponentManager>(this);
if (ensure(ComponentManager))
{
// 注册自定义初始化状态
ComponentManager->RegisterInitState(LyraGameplayTags::InitState_Spawned, false, FGameplayTag());
ComponentManager->RegisterInitState(LyraGameplayTags::InitState_DataAvailable, false, LyraGameplayTags::InitState_Spawned);
ComponentManager->RegisterInitState(LyraGameplayTags::InitState_DataInitialized, false, LyraGameplayTags::InitState_DataAvailable);
ComponentManager->RegisterInitState(LyraGameplayTags::InitState_GameplayReady, false, LyraGameplayTags::InitState_DataInitialized);
}
这是如何与加载屏幕关联的?
- 状态驱动:游戏实例定义了游戏中的一个核心状态流:
Spawned->DataAvailable->DataInitialized->GameplayReady。 - 组件协调:
UGameFrameworkComponentManager是一个强大的工具,它可以管理游戏中 Actor(尤其是玩家状态、玩家控制器、游戏模式等)上的“组件”(Mixin),并让这些组件在特定的初始化状态下执行逻辑。 - 加载屏幕的体现:
- 当游戏开始或进行地图旅行时,系统会进入一个加载状态。
- 一个专门的 “加载屏幕组件” 可能会被添加到玩家控制器上。
- 这个组件可以监听这些初始化状态。例如:
- 在
InitState_Spawned时,显示加载屏幕。 - 在
InitState_DataAvailable时,开始异步加载游戏资产。 - 在
InitState_GameplayReady时,隐藏加载屏幕,将控制权交给玩家。
- 在
- 游戏实例通过定义这些状态,为整个加载流程提供了一个标准化的、可追踪的框架。各个系统(包括渲染加载屏幕的UI)只需要关注自己感兴趣的状态变化即可。
总结
ULyraGameInstance 管理在线会话和加载屏幕的行为模式可以概括为:
- 对于在线会话:它是一个协调者和响应者。它通过绑定到
UCommonSessionSubsystem的事件,在会话生命周期的关键时刻(如客户端旅行前)注入游戏特定的逻辑和参数。 - 对于加载屏幕:它是一个架构定义者。它通过设置一个基于 “初始化状态”的框架,为整个游戏(包括加载屏幕的显示与隐藏、资源的加载与初始化)提供了一个清晰的生命周期。具体的加载屏幕UI则由其他组件在这个状态框架内驱动。
这种设计遵循了“单一职责原则”,游戏实例本身不处理所有细节,而是将具体工作委托给专门的子系统(Session Subsystem, Component Manager),自己则负责最高层次的初始化和流程控制。
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)