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——它代表你这座岛的运行空间。最常用的三样东西:
GetPlayers():[]player— 当前所有真人玩家的数组,开局点名用它;PlayerAddedEvent— 有玩家中途加入时触发,可 Subscribe 也可 Await;PlayerRemovedEvent— 有玩家离开时触发,清理状态用它。
特别之处在于:订阅这两个事件不需要在关卡里摆任何设备——playspace 是代码直接拿到的,这和第 21 课「插槽 + 拖线」的路子形成互补。
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[]): |
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 给离开的注销户口:
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 官方文档与教程: