🔥 先赞后看,养成习惯! 这篇文章带你从0到1彻底搞懂 Lyra 项目中 GameMode 是怎么跑起来的、干了哪些活、哪些接口最关键。建议收藏,开发的时候随时翻阅~


📌 一句话概括

ALyraGameMode 和咱们以前写的传统 UE GameMode 完全不是一个思路。它不再硬编码玩法逻辑,而是变成了一个 “玩法调度中心”——活都交给 Experience 数据资产去干,GameMode 自己就负责协调调度。

说白了:GameMode 不干活,它只负责让对的资产干对的活。


🏗️ 一、先搞清楚继承链

AActor
 └── AInfo
      └── AGameModeBase
           └── AModularGameModeBase   ← ModularGameplayActors 插件
                └── ALyraGameMode     ← Lyra 项目

这里面 AModularGameModeBase 挺省事的,就是把默认的 GameState、PlayerController 之类的类全换成 Modular 版本,本身没加什么逻辑。所以核心都在 ALyraGameMode 里。


🎯 二、GameMode 到底负责干啥?

很多小伙伴刚接触 Lyra 的时候会懵:GameMode 怎么这么"空"?其实它负责的事情一点都不少,只不过干活方式变了

职责 一句话说明
🧩 Experience 选择与启动 地图加载后,按优先级解析该加载哪个 Experience
📡 Experience 加载监听 注册回调,加载完了我得知道
🧍 玩家 Pawn 生成 Pawn 类型和数据都由 Experience 的 PawnData 决定
📍 玩家出生点选择 不自己选,甩锅给 PlayerSpawningManagerComponent
🔄 玩家重启/重生 支持延迟重启,失败了还会自动重试
🖥️ 专用服务器登录 Dedicated Server 的在线登录和会话托管
📢 玩家初始化广播 通过委托告诉其他系统"这个玩家准备好了"

💡 划重点:你会发现 GameMode 自己几乎不实现业务逻辑,全是通过组件和委托来完成的。这就是 Lyra 的设计哲学——数据驱动 + 组件化


🔄 三、完整运行流程——从地图加载到玩法运行

3.1 全局生命周期一览

先看一张大图,心里有个数:

🗺️ 地图加载

🏗️ 创建 ALyraGameMode

⚙️ 构造函数: 绑定默认类

📦 InitGame: 下一帧调度 Experience 解析

📡 InitGameState: 注册 OnExperienceLoaded 回调

🔍 HandleMatchAssignmentIfNotExpectingOne: 解析 ExperienceId

✅ OnMatchAssignmentGiven: 设置 Experience

⏳ ExperienceManagerComponent 加载 Experience

🎮 OnExperienceLoaded: 生成已有玩家

👋 HandleStartingNewPlayer: 处理后续玩家加入

🎉 玩家生成 / Bot 创建 / 玩法正式运行

3.2 构造函数——先把队伍拉起来

ALyraGameMode::ALyraGameMode(const FObjectInitializer& ObjectInitializer)
    : Super(ObjectInitializer)
{
    GameStateClass = ALyraGameState::StaticClass();
    GameSessionClass = ALyraGameSession::StaticClass();
    PlayerControllerClass = ALyraPlayerController::StaticClass();
    ReplaySpectatorPlayerControllerClass = ALyraReplayPlayerController::StaticClass();
    PlayerStateClass = ALyraPlayerState::StaticClass();
    DefaultPawnClass = ALyraCharacter::StaticClass();
    HUDClass = ALyraHUD::StaticClass();
}

构造函数干的事很简单——把 Lyra 自己的那套班子绑上去。GameState、GameSession、PlayerController、PlayerState、Pawn、HUD 全部换成 Lyra 版本。

⚠️ 注意:这里绑定的只是默认类,后面 Experience 加载的时候,PawnData 还会进一步覆盖和补充配置,所以别以为改了这里就完事了。

3.3 InitGame——关键的"下一帧"设计

void ALyraGameMode::InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage)
{
    Super::InitGame(MapName, Options, ErrorMessage);
    // 重点来了:不是立刻解析,而是下一帧!
    GetWorld()->GetTimerManager().SetTimerForNextTick(this, &ThisClass::HandleMatchAssignmentIfNotExpectingOne);
}

🤔 为什么要下一帧? 这是个好问题!

因为 InitGame 被调用的时候,很多配置还没初始化好呢——PIE 设置、URL 参数、命令行参数啥的可能还没来得及生效。延迟一帧就给这些东西留出了初始化的时间窗口。这个设计挺巧妙的,记住了,后面经常用到这个"延迟一帧"的套路

3.4 Experience 选择——7层优先级,一个都不能少

HandleMatchAssignmentIfNotExpectingOne() 这个函数名字虽然长,但逻辑很清晰,就是按优先级一层层往下找 ExperienceId:

没设置

没设置

没设置

没设置

没设置

非专用服务器

1️⃣ Matchmaking 分配

2️⃣ URL 参数 ?Experience=xxx

3️⃣ 开发者设置 (PIE only)

4️⃣ 命令行 -Experience=xxx

5️⃣ WorldSettings 默认 Experience

6️⃣ 专用服务器登录流程

7️⃣ 默认回退: B_LyraDefaultExperience

优先级 来源 怎么用 啥场景
1 Matchmaking 分配 预留接口,目前还没实现 在线匹配
2 URL 选项 地图名后面加 ?Experience=xxx 快速测试、Seamless Travel 传参
3 开发者设置 ULyraDeveloperSettings::ExperienceOverride 仅 PIE 模式生效
4 命令行 启动参数加 -Experience=xxx 命令行启动专用
5 WorldSettings 在地图的 WorldSettings 里配 给地图配默认 Experience
6 Dedicated Server TryDedicatedServerLogin() 专用服务器自动登录
7 默认回退 B_LyraDefaultExperience 以上全都没有,就它了

💡 实战小技巧:开发调试的时候,最方便的就是方法2和方法3——在PIE里直接override,或者URL里加参数,不用改代码就能切换玩法模式。

3.5 OnMatchAssignmentGiven——找到了,把它交给专业的人

void ALyraGameMode::OnMatchAssignmentGiven(FPrimaryAssetId ExperienceId, const FString& ExperienceIdSource)
{
    ULyraExperienceManagerComponent* ExperienceComponent = GameState->FindComponentByClass<ULyraExperienceManagerComponent>();
    check(ExperienceComponent);
    ExperienceComponent->SetCurrentExperience(ExperienceId);
}

拿到 ExperienceId 之后,GameMode 什么都不干,直接甩给 ULyraExperienceManagerComponent。这个组件才是真正负责加载的——异步加载资产、激活 GameFeature 插件、执行 Action,全是它的事。

🎮 打个比方:GameMode 是项目经理,ExperienceManagerComponent 是程序员。项目经理说"做这个需求",程序员就去干活了。

3.6 Experience 加载——一个完整的状态机

ULyraExperienceManagerComponent 内部是个状态机,状态流转如下:

SetCurrentExperience()

资产加载完成, 有 GameFeature 要加载

资产加载完成, 没有 GameFeature

GameFeature 插件全部加载完

配了混沌测试延迟

Actions 执行完毕

延迟结束

EndPlay

所有 Action 反激活完成

Unloaded

Loading

LoadingGameFeatures

ExecutingActions

LoadingChaosTestingDelay

Loaded

Deactivating

整个加载过程拆解一下就是:

  1. SetCurrentExperience → 让 AssetManager 去加载 Experience 数据资产
  2. StartExperienceLoad → 异步加载 Experience 及 ActionSet 的 Bundle 资产(会区分 Client/Server)
  3. OnExperienceLoadComplete → 把需要加载的 GameFeature 插件 URL 收集起来
  4. LoadAndActivateGameFeaturePlugin → 一个一个加载并激活 GameFeature 插件
  5. OnExperienceFullLoadCompleted → 执行所有 Experience Actions 和 ActionSet 里的 Actions
  6. 广播 OnExperienceLoaded → 按高/中/低三个优先级通知所有监听者

⚠️ 踩坑提醒:GameFeature 插件加载是异步的,而且可能不止一个。如果你在 Experience 加载完成前就去访问某些插件里的东西,大概率会崩。一定要在 OnExperienceLoaded 回调之后再去操作!

3.7 OnExperienceLoaded——加载完了,赶紧把等着的玩家安排上

void ALyraGameMode::OnExperienceLoaded(const ULyraExperienceDefinition* CurrentExperience)
{
    for (FConstPlayerControllerIterator Iterator = GetWorld()->GetPlayerControllerIterator(); Iterator; ++Iterator)
    {
        APlayerController* PC = Cast<APlayerController>(*Iterator);
        if ((PC != nullptr) && (PC->GetPawn() == nullptr))
        {
            if (PlayerCanRestart(PC))
            {
                RestartPlayer(PC);
            }
        }
    }
}

这段逻辑很直白:遍历所有已经连上但还没 Pawn 的玩家,赶紧给他们生成 Pawn

为什么要这么做?因为玩家可能在 Experience 还没加载完的时候就连进来了,那时候 Pawn 类都还没确定呢,只能让他们先等着。现在 Experience 加载完了,该安排的都安排上。

3.8 InitGameState——先把回调注册好

void ALyraGameMode::InitGameState()
{
    Super::InitGameState();
    ULyraExperienceManagerComponent* ExperienceComponent = GameState->FindComponentByClass<ULyraExperienceManagerComponent>();
    check(ExperienceComponent);
    ExperienceComponent->CallOrRegister_OnExperienceLoaded(
        FOnLyraExperienceLoaded::FDelegate::CreateUObject(this, &ThisClass::OnExperienceLoaded));
}

CallOrRegister_OnExperienceLoaded 这个函数名就说明了一切——如果 Experience 已经加载完了,直接调用;如果还没完,先注册回调等着。这个设计很实用,不管谁先谁后都能处理。


🔧 四、核心接口详解——一个一个掰扯清楚

4.1 重写的 AGameModeBase 接口

📦 InitGame
virtual void InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage) override;
  • 啥时候调:地图加载后、任何 Actor 初始化之前
  • Lyra 干了啥:调完 Super 后,延迟一帧调度 Experience 解析
  • 为啥这么干:前面说了,给配置初始化留时间
📡 InitGameState
virtual void InitGameState() override;
  • 啥时候调:GameState 创建并初始化之后
  • Lyra 干了啥:找到 ExperienceManagerComponent,注册 OnExperienceLoaded 回调
  • 为啥这么干:不注册回调的话,GameMode 怎么知道 Experience 加载完了?到时候玩家就只能干等着了
🧍 GetDefaultPawnClassForController_Implementation
virtual UClass* GetDefaultPawnClassForController_Implementation(AController* InController) override;
  • 啥时候调:要给 Controller 确定用哪个 Pawn 类的时候
  • Lyra 干了啥:走 GetPawnDataForController 拿 PawnData,从里面取 PawnClass
  • 为啥这么干:Pawn 类不再写死在 GameMode 里了,而是由 Experience 的 PawnData 动态决定。想换个角色?改 PawnData 就行,代码都不用动
🎭 SpawnDefaultPawnAtTransform_Implementation
virtual APawn* SpawnDefaultPawnAtTransform_Implementation(AController* NewPlayer, const FTransform& SpawnTransform) override;
  • 啥时候调:要在指定位置生成玩家 Pawn 的时候
  • Lyra 干了啥
    1. 拿到 Pawn 类
    2. bDeferConstruction = true 先不完成构造就 Spawn 出来
    3. 找到 ULyraPawnExtensionComponent,调用 SetPawnData 注入 PawnData
    4. FinishSpawning 完成整个 Pawn 创建
  • 为啥这么干:💡 这是 Lyra 模块化 Pawn 的灵魂! PawnData 里包含输入配置、能力集(AbilitySet)、相机模式等。如果不先注入 PawnData 就完成构造,Pawn 里面那些依赖数据的初始化就会出问题

⚠️ 踩坑:如果你自己写代码生成 Pawn,一定要记得先调 SetPawnDataFinishSpawning,不然 Pawn 初始化会丢数据!

👋 HandleStartingNewPlayer_Implementation
virtual void HandleStartingNewPlayer_Implementation(APlayerController* NewPlayer) override;
  • 啥时候调:新玩家加入游戏
  • Lyra 干了啥只有 Experience 加载完了才继续处理,否则啥也不干
  • 为啥这么干:Experience 没加载完的话,连 Pawn 类是啥都不知道,怎么给玩家生成 Pawn?不如先放着,等 OnExperienceLoaded 的时候统一处理
📍 ChoosePlayerStart_Implementation
virtual AActor* ChoosePlayerStart_Implementation(AController* Player) override;
  • Lyra 干了啥:直接交给 ULyraPlayerSpawningManagerComponent::ChoosePlayerStart
  • 为啥这么干:出生点选择逻辑解耦了!不同玩法可以换不同的 SpawningManager,GameMode 完全不用改
🔄 FinishRestartPlayer
virtual void FinishRestartPlayer(AController* NewPlayer, const FRotator& StartRotation) override;
  • Lyra 干了啥:先通知 PlayerSpawningManagerComponent,再调 Super
  • 为啥这么干:让 SpawningManager 在重启后也能做点自己的事情
✅ PlayerCanRestart_Implementation / ControllerCanRestart
virtual bool PlayerCanRestart_Implementation(APlayerController* Player) override;
virtual bool ControllerCanRestart(AController* Controller);
  • Lyra 干了啥:统一走 ControllerCanRestart,里面分情况:
    • PlayerController → 先走引擎基础检查
    • Bot Controller → 做简单有效性检查
    • 最后都去问 PlayerSpawningManagerComponent::ControllerCanRestart
  • 为啥这么干:玩家和 Bot 的重启逻辑统一管理,不用写两套
🚫 ShouldSpawnAtStartSpot
virtual bool ShouldSpawnAtStartSpot(AController* Player) override;
  • Lyra 干了啥:永远返回 false
  • 为啥这么干:Lyra 完全不用引擎默认的 StartSpot 生成逻辑,出生点全由 PlayerSpawningManagerComponent 管。这个返回 false 就是在告诉引擎:“别管了,我自己来”
📢 GenericPlayerInitialization
virtual void GenericPlayerInitialization(AController* NewPlayer) override;
  • Lyra 干了啥:调完 Super 之后,广播 OnGameModePlayerInitialized 委托
  • 为啥这么干:提供统一的"玩家已就绪"事件,玩家和 Bot 都会触发,其他系统一听就知道有人准备好了
🔄 UpdatePlayerStartSpot
virtual bool UpdatePlayerStartSpot(AController* Player, const FString& Portal, FString& OutErrorMessage) override;
  • Lyra 干了啥:直接返回 true,啥也不干
  • 为啥这么干:这时候团队分配啥的还没完成呢,更新出生点没意义。等真正生成的时候再决定就行
⚡ FailedToRestartPlayer
virtual void FailedToRestartPlayer(AController* NewPlayer) override;
  • Lyra 干了啥:如果 Pawn 类存在,就通过 RequestPlayerRestartNextFrame 下一帧再试
  • 为啥这么干:🛡️ 自动重试机制! 不然玩家可能因为某些临时条件不满足就永远无法重生了。这设计很贴心,避免了"卡死"的情况

4.2 Lyra 自己加的接口

🔍 GetPawnDataForController
UFUNCTION(BlueprintCallable, Category = "Lyra|Pawn")
const ULyraPawnData* GetPawnDataForController(const AController* InController) const;

这个函数的查找链值得仔细看:

  1. 先从 ALyraPlayerState::GetPawnData() 找 → 可能被其他系统设置过了
  2. 找不到?从当前 Experience 的 DefaultPawnData
  3. Experience 还没加载?返回 nullptr
  4. Experience 加载了但没配 DefaultPawnData?返回全局默认的 ULyraAssetManager::GetDefaultPawnData()

💡 这个查找链的设计很灵活:支持按玩家定制(第1步)、按 Experience 定制(第2步)、还有兜底方案(第4步)。

⏳ RequestPlayerRestartNextFrame
UFUNCTION(BlueprintCallable)
void RequestPlayerRestartNextFrame(AController* Controller, bool bForceReset = false);

行为拆解:

  • bForceReset = true → 立刻 Controller->Reset()(把当前 Pawn 扔了)
  • PlayerController → 下一帧调 ServerRestartPlayer_Implementation
  • BotController → 下一帧调 ServerRestartController

💡 又是"下一帧"的套路!为什么要这样?因为在当前帧的调用栈里直接重启,很容易出递归或状态不一致的问题。延迟一帧就安全了。

📢 OnGameModePlayerInitialized
FOnLyraGameModePlayerInitialized OnGameModePlayerInitialized;
// 声明:DECLARE_MULTICAST_DELEGATE_TwoParams(FOnLyraGameModePlayerInitialized, AGameModeBase*, AController*)

玩家和 Bot 初始化完成后都会触发。外部系统想监听"有人准备好了",绑这个就对了。

4.3 Protected 接口(内部用的)

接口 一句话说明
OnExperienceLoaded Experience 加载完了,赶紧给等着的人生成 Pawn
IsExperienceLoaded 检查 Experience 加载没
HandleMatchAssignmentIfNotExpectingOne 那个按7层优先级找 Experience 的函数
OnMatchAssignmentGiven 找到 Experience 了,设置到组件里
TryDedicatedServerLogin 专用服务器登录,用 CommonUserSubsystem
HostDedicatedServerMatch 解析命令行创建会话,触发地图旅行
OnUserInitializedForDedicatedServer 专用服务器用户登录回调,不管成功失败都去创建会话

🤝 五、GameMode 和它的朋友们——协作关系全景图

5.1 先看架构图

设置 Experience

注册回调

加载完成通知

选择出生点

判断能否重启

完成重启通知

获取 PawnData

DefaultPawnData

生成 Pawn 时注入数据

监听 Experience 加载

初始化 Bot

持有

持有

持有

默认 ExperienceId

广播初始化完成

🎮 ALyraGameMode

🧩 ExperienceManagerComponent

📍 PlayerSpawningManagerComponent

📋 ExperienceDefinition

🎭 PawnExtensionComponent

🤖 BotCreationComponent

🏛️ GameState

🌍 WorldSettings

📢 OnGameModePlayerInitialized

5.2 ULyraExperienceManagerComponent——GameMode 最铁的兄弟

  • 住在哪ALyraGameState 上面的组件
  • 干啥的:管理 Experience 的整个生命周期——加载、GameFeature 插件激活/反激活、Action 执行
  • 和 GameMode 怎么交互
    • GameMode 调 SetCurrentExperience → 让它去加载
    • GameMode 调 CallOrRegister_OnExperienceLoaded → 注册回调等通知
    • GameMode 调 IsExperienceLoaded() → 查状态
    • GameMode 调 GetCurrentExperienceChecked() → 拿加载完的 Experience(用来读 PawnData)

5.3 ULyraPlayerSpawningManagerComponent——出生点管家

  • 住在哪:也是 ALyraGameState 上的组件
  • 干啥的:管玩家在哪出生、能不能重启
  • GameMode 代理调用关系
    • ChoosePlayerStart → 甩给它选出生点
    • ControllerCanRestart → 甩给它判断能不能重启
    • FinishRestartPlayer → 甩给它做重启后处理

💡 设计亮点:出生点策略从 GameMode 解耦了!不同玩法想要不同的生成策略?换一个 SpawningManager 就行,GameMode 完全不用改。

5.4 ULyraBotCreationComponent——Bot 制造机

  • 住在哪:还是 ALyraGameState 上的组件
  • 干啥的:Experience 加载完后自动创建 Bot
  • 和 GameMode 怎么交互
    • 监听 CallOrRegister_OnExperienceLoaded_LowPriority(注意是低优先级!确保真人玩家先生成)
    • GameMode->GenericPlayerInitializationGameMode->RestartPlayer 完成 Bot 初始化

⚠️ 注意:Bot 用的是低优先级回调,这样保证真人玩家在 Bot 之前生成。如果你的自定义逻辑也依赖 Experience 加载完成,注意选对优先级!

5.5 ALyraWorldSettings——地图的默认配置

  • 干啥的:给地图配默认 Experience
  • 和 GameMode 怎么交互:GameMode 在找 ExperienceId 的时候,如果前面6种方式都没找到,就来这里通过 GetDefaultGameplayExperience()

5.6 ALyraGameSession——简单的代理

  • 干啥的:覆盖了 ProcessAutoLogin 直接返回 true
  • 为啥这么干:因为登录逻辑已经由 GameMode 的 TryDedicatedServerLogin 处理了,GameSession 不用再管

🧬 六、玩家生成全流程——从连接到拿到 Pawn

这个流程太重要了,我单独拿出来讲。很多小伙伴搞不清楚"为什么玩家进来了但没有Pawn",看完这个就明白了:

🎭 PawnExtension 📍 SpawningManager 🧩 ExperienceManager 🎮 GameMode 👤 玩家连接 🎭 PawnExtension 📍 SpawningManager 🧩 ExperienceManager 🎮 GameMode 👤 玩家连接 啥也不干,先等着 alt [✅ Experience 已加载] [❌ Experience 还没加载完] Login → PostLogin HandleStartingNewPlayer Super::HandleStartingNewPlayer GetDefaultPawnClassForController (从 PawnData 拿) ChoosePlayerStart (选出生点) SpawnDefaultPawnAtTransform SetPawnData (注入 PawnData!) GenericPlayerInitialization OnGameModePlayerInitialized 广播 📢 OnExperienceLoaded 🎉 遍历所有没 Pawn 的玩家 RestartPlayer(PC) ChoosePlayerStart SpawnDefaultPawnAtTransform SetPawnData

💡 这就是为什么有时候玩家进来了但没有 Pawn——Experience 还没加载完呢!遇到这种情况不要慌,检查一下 Experience 加载是否正常。


🖥️ 七、专用服务器启动流程

如果你的项目要上专用服务器,这段流程必须搞明白:

📡 CommonSessionSubsystem 👤 CommonUserSubsystem 🎮 GameMode 🖥️ Dedicated Server 📡 CommonSessionSubsystem 👤 CommonUserSubsystem 🎮 GameMode 🖥️ Dedicated Server 不管登录成功失败,都继续 InitGame HandleMatchAssignmentIfNotExpectingOne TryDedicatedServerLogin TryToLoginForOnlinePlay(0号用户) OnUserInitializeComplete OnUserInitializedForDedicatedServer HostDedicatedServerMatch(Online) 解析命令行 UserExperience/Playlist HostSession(HostRequest) 地图旅行 🚀 (触发新 GameMode 和 Experience 加载)

⚠️ 注意OnUserInitializedForDedicatedServer不管登录成功失败都会调 HostDedicatedServerMatch。这是因为即使在线登录失败,也可以用离线模式继续运行。


💎 八、关键设计模式——Epic 的工程师是怎么想的?

8.1 🧩 数据驱动取代继承

传统做法:想加个新玩法?继承 GameMode 写个新的。

Lyra 做法:配一个新的 Experience 数据资产就行

传统:AGameMode_DM → AGameMode_TDM → AGameMode_CTF (继承链越来越长)
Lyra:B_ShooterGame_Experience / B_TopDown_Experience (配置不同,代码共享)

这才是正确的打开方式!🎉

8.2 ⏳ 延迟初始化

GameMode 到处都是"下一帧再干":

  • InitGame → 下一帧才解析 Experience
  • HandleStartingNewPlayer → 等 Experience 加载完才处理
  • RequestPlayerRestartNextFrame → 下一帧才重启

这不是偷懒,这是智慧——避免在调用栈中产生递归和状态不一致的问题。

8.3 🔌 组件化委托

GameMode 把核心逻辑都甩给了 GameState 上的组件:

组件 负责啥
ExperienceManagerComponent Experience 生命周期管理
PlayerSpawningManagerComponent 生成逻辑管理
BotCreationComponent Bot 创建管理

好处很明显:想换策略就换组件,GameMode 不用动。这就是开闭原则的实践!

8.4 📢 事件广播

  • OnGameModePlayerInitialized → 玩家/Bot 初始化完了
  • OnExperienceLoaded → Experience 加载完了(还有三个优先级)

系统之间通过事件通信,谁也不用认识谁,解耦得干干净净。


📋 九、接口速查表——开发时随时翻

AGameModeBase 重写接口

接口 干啥的 Lyra 的特殊处理
InitGame 游戏初始化 下一帧才解析 Experience
InitGameState GameState 初始化 注册 OnExperienceLoaded 回调
GetDefaultPawnClassForController 获取 Pawn 类 从 PawnData 动态拿
SpawnDefaultPawnAtTransform 生成 Pawn 先注入 PawnData 再完成构造
HandleStartingNewPlayer 处理新玩家 等 Experience 加载完才处理
ChoosePlayerStart 选出生点 甩给 PlayerSpawningManagerComponent
FinishRestartPlayer 完成重启 通知 PlayerSpawningManagerComponent
PlayerCanRestart 能否重启 统一走 ControllerCanRestart
ShouldSpawnAtStartSpot 在 StartSpot 生成? 永远返回 false
GenericPlayerInitialization 通用初始化 广播 OnGameModePlayerInitialized
UpdatePlayerStartSpot 更新出生点 直接返回 true
FailedToRestartPlayer 重启失败 自动下一帧重试

Lyra 自定义接口

接口 访问级别 干啥的
GetPawnDataForController Public + BP 获取 Controller 对应的 PawnData
RequestPlayerRestartNextFrame Public + BP 下一帧重启玩家/Bot
ControllerCanRestart Public, Virtual 统一的重启判断
OnGameModePlayerInitialized Public Delegate 玩家/Bot 初始化完成事件
OnExperienceLoaded Protected Experience 加载完成回调
IsExperienceLoaded Protected 检查 Experience 状态
HandleMatchAssignmentIfNotExpectingOne Protected 那个7层优先级解析函数
OnMatchAssignmentGiven Protected 设置 Experience 到组件
TryDedicatedServerLogin Protected 专用服务器登录
HostDedicatedServerMatch Protected 专用服务器创建会话
OnUserInitializedForDedicatedServer Protected 专用服务器登录回调

📂 十、源文件索引

文件 说明
LyraGameMode.h GameMode 头文件
LyraGameMode.cpp GameMode 实现
LyraGameState.h GameState 头文件
LyraExperienceManagerComponent.h Experience 管理组件头文件
LyraExperienceManagerComponent.cpp Experience 管理组件实现
LyraExperienceDefinition.h Experience 数据定义
LyraWorldSettings.h WorldSettings(默认 Experience)
LyraPlayerSpawningManagerComponent.h 玩家生成管理组件
LyraBotCreationComponent.h Bot 创建组件
LyraUserFacingExperienceDefinition.h UI 侧 Experience 定义
LyraGameSession.h GameSession
ModularGameMode.h Modular GameMode 基类

🎯 总结一句话:Lyra 的 GameMode 不是传统意义上的"游戏规则制定者",而是一个"玩法调度中心"。它通过 Experience 数据资产驱动玩法加载,通过组件委托处理具体逻辑,通过事件广播通知外部系统。理解了这个定位,你就理解了 Lyra 的设计精髓!

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐