Verse Wiki — 写给 UE6 时代的 Verse 互动手册
拓展 · EXTRA

playspace 与玩家生命周期:处理中途进出的玩家

教程里的玩家总是开局就整整齐齐站好,真实岛屿里的玩家却会中途加入、随时退出。这一页讲 playspace——整座岛的「花名册」,以及 agent / player / fort_character 三层模型,帮你的设备优雅接待每一位来客。本文整理自 Epic 官方文档与教程。

一、别假设玩家开局都在场

很多设备代码默认「OnBegin(即 Event BeginPlay) 跑的时候,所有玩家都已就位」。私下测试确实如此——只有你自己;可一旦发布,真实情况是:有人比赛打到一半才进来,有人拿了金币就掉线。如果初始化逻辑只在 OnBegin 里跑一次,中途加入的玩家就是「黑户」:没有初始状态、不在计分册上,各种功能对他失灵。官方 Team Elimination 系列教程专门用一整篇《Handling a Player Joining a Game in Progress》处理这个问题,足见它多常被忽略。

正确的心智模型:玩家名单是一条会流动的河,而不是一张开局拍死的合影。你需要两件东西:一份「当前在场」的名单,和「有人来了 / 有人走了」的通知。playspace 恰好都提供。

二、playspace:整座岛的花名册

在任何 creative_device 里调用 GetPlayspace(),就能拿到一个 fort_playspace——它代表你这座岛的运行空间。最常用的三样东西:

特别之处在于:订阅这两个事件不需要在关卡里摆任何设备——playspace 是代码直接拿到的,这和第 21 课「插槽 + 拖线」的路子形成互补。

headcount_snippet.verse
using { /UnrealEngine.com/Temporary/Diagnostics }

OnBegin<override>()<suspends>:void =
    Playspace := GetPlayspace()
    AllPlayers := Playspace.GetPlayers()
    Print("当前在场 {AllPlayers.Length} 名玩家")

读图翻译:游戏一开始,先用 GetPlayspace() 拿到这座岛的「运行空间」,再用它的 GetPlayers() 抓一份当前在场玩家的数组,最后用 Print String 把人数(数组的 Length)打到屏幕上。

三、agent / player / fort_character:三层模型

设备事件递给你的是 agent,playspace 给你的是 player,想拿位置血量又得找 fort_character——三者的关系值得一张表:

是什么 怎么拿到
agent 「干这件事的家伙」,可能是玩家或 AI 设备事件的回调参数那张图的输入引脚
player agent 的子类型,真人玩家agent 的一种,特指真人玩家 if (P := player[Agent]): 失败上下文转型用 if 试着转型,转不通就跳过
fort_character 场上的角色实体,有位置、血量 if (FC := Agent.GetFortCharacter[]):
agent_casting_snippet.verse
using { /Fortnite.com/Characters }
using { /UnrealEngine.com/Temporary/Diagnostics }

OnPressed(Agent:agent):void =
    # agent → player:失败上下文里转型,AI 会转型失败
    if (P := player[Agent]):
        Print("确认是真人玩家")
    # agent → fort_character:拿到角色实体
    if (FC := Agent.GetFortCharacter[]):
        # 可进一步查位置、血量或传送
        # TODO: 以 Verse API Reference 为准(fort_character 的具体成员)
        Print("拿到了场上的角色实体")

读图翻译:在按钮那张图(OnPressed)里,先用一个 if 试着把 Agent 转成 player——转通了说明是真人;再用一个 if 试着拿到它的 fort_character(场上真身),拿到了才能进一步查位置、血量。两个 if 都可能走不通,走不通就安静跳过那一段。

两处方括号都是失败上下文的老规矩:转型可能失败(按按钮的可能是 AI,角色可能已被淘汰),所以必须包在 if 里。下一课的持久化 weak_map 只认 player 做键,这个转型动作会越来越常用。

两处方括号都是「能不能走通」的老规矩:转型可能失败(按按钮的可能是 AI,角色可能已被淘汰),所以必须包在 if 里,走不通就跳过。下一课讲的持久化 weak_map(每个玩家一份的 SaveGame)只认 player 当键,这个转型动作会越来越常用。

四、标准做法:开局点名 + 订阅进出

把前面的零件组装成官方教程推荐的模式:OnBegin 先为在场玩家逐个建立状态,再订阅 PlayerAddedEvent 给新来的补办手续、订阅 PlayerRemovedEvent 给离开的注销户口:

把前面的零件组装成官方教程推荐的模式:游戏一开始(OnBegin / Event BeginPlay)先为在场玩家逐个建立状态,再 Bind(Subscribe)PlayerAddedEvent 给新来的补办手续、Bind PlayerRemovedEvent 给离开的注销户口:

roster_device.verse
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }

roster_device := class(creative_device):

    OnBegin<override>()<suspends>:void =
        Playspace := GetPlayspace()
        # 开局点名:先处理已经在场的玩家
        for (P : Playspace.GetPlayers()):
            InitPlayer(P)
        # 订阅中途进出(无需任何关卡设备)
        # TODO: 以 Verse API Reference 为准(PlayerAddedEvent 的确切签名)
        Playspace.PlayerAddedEvent().Subscribe(OnPlayerAdded)
        Playspace.PlayerRemovedEvent().Subscribe(OnPlayerRemoved)

    InitPlayer(P:player):void =
        Print("为一名玩家建立初始状态")

    OnPlayerAdded(NewPlayer:player):void =
        # 中途加入的玩家在这里补办手续
        InitPlayer(NewPlayer)

    OnPlayerRemoved(Gone:player):void =
        Print("清理离场玩家的会话状态")

这个骨架的关键是 InitPlayer 被两个入口共用:开局在场的走 for 循环,中途加入的走事件回调,两条路汇进同一段初始化逻辑,谁都不会漏。离场清理同样重要——玩家走了还攥着他的 player 引用去读写,是运行时错误的常见来源。到了下一课你会看到这个模式的最佳拍档:玩家中途加入的那一刻,正是从持久化 weak_map 里读出他历史存档的时机。

这个骨架的关键是 InitPlayer 被两个入口共用:开局在场的走 for(ForEach)循环,中途加入的走事件绑的那张图,两条路汇进同一段初始化逻辑,谁都不会漏。离场清理同样重要——玩家走了你还攥着他的 player 引用去读写,是运行时报错的常见来源。到了下一课你会看到这个模式的最佳拍档:玩家中途加入的那一刻,正是从持久化 weak_map(每人一份的 SaveGame)里读出他历史存档的时机。

想保证中途加入的玩家也拿到初始化状态,标准做法是?

来源

本文整理自 Epic 官方文档与教程: