map 没有删除键的函数:重建法与 weak_map 重置法
想从 map 里删掉一行?搜遍文档也找不到 Remove 或 Delete。这一页教你社区总结的两套删除模式:普通 map 用「过滤重建」,不可遍历的 weak_map 用「整条重置」。想从 map 里删掉一行?蓝图的 Map 有个 Remove 节点,可 Verse 的 map 偏偏没有——搜遍文档也找不到。这一页教你社区总结的两套删除套路:普通 map 用「过滤重建」,不能逐行点名(不可遍历)的 weak_map 用「整条重置」。
一、查无此函数:Remove 去哪了一、没有 Remove 这回事:它去哪了
从 C#、Python 或 JavaScript 过来的开发者,遇到「删除一个键」的需求会条件反射地敲出 MyMap.Remove(Key) 或 delete MyMap[Key]——然后收获一条「查无此函数」的编译错误。这不是 API 还没写完,而是设计使然:Verse 的 map 和 array 一样是不可变值,没有任何原地修改的方法。你对 var 的每一次「修改」,本质上都是造出一个新容器再整体替换。
从蓝图转 Verse 的人,遇到「删除一个键」会条件反射地去找 Remove 节点——蓝图的 Map 明明就有——结果 Verse 里翻遍了也没有,硬写还会被 Compile 打回。这不是功能没做完,而是设计使然:Verse 的 map 和 array 一样是不能原地改的整体,没有任何「就地抠掉一格」的办法。你对一个变量的每一次「修改」,本质上都是造出一张新表再整体换上去。
顺着这个世界观想,「删除」的正确姿势自然就浮出水面:造一张没有那个键的新表。社区把它称为「重建法」,YouTube 上有教程专门演示这个封装函数。
顺着这个世界观想,「删除」的正确姿势自然浮出水面:造一张没有那个键的新表。社区把它称为「重建法」,YouTube 上有教程专门演示怎么把它包成一个蓝图函数(我的蓝图面板里那种 f 图标的函数),随用随调。
二、重建法:过滤 + 重灌
套路三步走:新建一张空表;遍历旧表,用 for 的过滤子句跳过要删的键;把剩下的键值对逐条写进新表,最后整表替换。封装成函数长这样:
套路三步走:新建一张空表;用 ForEach Loop 逐行点名旧表,在循环头挂一个「过滤条件」跳过要删的键;把剩下的每一对键值逐条写进新表,最后整表替换。包成一个蓝图函数长这样:
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }
cleanup_device := class(creative_device):
var Scores:[string]int = map{"泡泡" => 10, "阿奇" => 5, "捣蛋鬼" => 99}
# 返回一张「没有 KeyToRemove 那一行」的新表
RemoveKey(Source:[string]int, KeyToRemove:string):[string]int =
var Result:[string]int = map{}
for (K -> V : Source, K <> KeyToRemove):
if (set Result[K] = V) {}
Result
OnBegin<override>()<suspends>:void =
set Scores = RemoveKey(Scores, "捣蛋鬼")
Print("剩余 {Scores.Length} 人")
三处值得咀嚼的细节:第一,for (K -> V : Source, K <> KeyToRemove) 里逗号后面是过滤子句,<> 是「不等于」——等于把「跳过要删的键」写进了循环头;第二,往新表写入用的是本课的老朋友 if (set Result[K] = V) {},索引赋值必须包失败上下文;第三,set Scores = RemoveKey(...) 是对 var 的整体赋值,不是索引赋值,所以不需要 if。函数最后一行孤零零的 Result 是 Verse 的隐式返回。跑完 OnBegin,日志打出「剩余 2 人」——捣蛋鬼被请出了登记表。
三处值得咀嚼的细节:第一,for (K -> V : Source, K <> KeyToRemove) 就是一个 ForEach Loop,逗号后面挂的是过滤条件,<> 读作「不等于」——等于把「跳过要删的键」直接写进了循环头;第二,往新表写入用的是本课老朋友 if (set Result[K] = V) {},往 map 写值必须接进 Branch;第三,set Scores = RemoveKey(...) 是把整张新表一次性赋给变量(整体替换),不是往某个键写值,所以不用 Branch。函数最后孤零零的一行 Result,就是把它当作这个蓝图函数的返回值往外递——最后一个节点的输出直接接到 Return,不用专门喊 return。游戏一开场(Event BeginPlay)跑完,日志打出「剩余 2 人」,捣蛋鬼被请出了登记表。
三、weak_map 的困境:不能重建,只能重置
重建法有个前提:表得能遍历。可 map 的近亲 weak_map(跨局持久化数据的载体,详见另一篇拓展)恰恰不可遍历——你没法「过滤旧表」,重建法当场失灵。那想清掉某个玩家的存档数据怎么办?
重建法有个前提:表得能逐行点名(遍历)。可 map 的近亲 weak_map(跨局存档数据的载体,详见另一篇拓展)恰恰不能点名——你没法「过滤旧表」,重建法当场失灵。那想清掉某个玩家的存档数据怎么办?
Epic 论坛上开发者 Kasity 给出的方案是「整条重置」:与其删掉那一行,不如把它覆盖成一个全新的默认实例。比如在玩家离开游戏时,把该玩家在 weak_map 里的条目重置为初始状态。他同时强调一个关键防线:操作前必须先确认玩家仍然有效,否则会触发 ErrRuntime_WeakMapInvalidKey 运行时错误。
Epic 论坛上开发者 Kasity 给出的方案是「整条重置」:与其删掉那一行,不如把它覆盖成一个全新的、恢复出厂设置的记录。比如玩家离开游戏时,把他在 weak_map 里那一条重置回初始状态。他同时强调一个关键防线:动手前必须先确认这名玩家还在场,否则会触发 ErrRuntime_WeakMapInvalidKey 运行时报错。
using { /Verse.org/Simulation }
# 玩家统计表(持久化写法详见拓展页三)
player_stats_table := class<final><persistable>:
Score:int = 0
var StatsTable:weak_map(player, player_stats_table) = map{}
# 在玩家离开的回调里调用:把这名玩家的记录覆盖成默认实例
ResetPlayer(Target:player):void =
if (Target.IsActive[]): # TODO: 以 Verse API Reference 为准
if (set StatsTable[Target] = player_stats_table{}) {}
读图翻译:先声明一个「玩家统计」的数据结构(带 <persistable>,意思是能存档),里面就一个字段 Score。再声明一张按玩家存的 weak_map。ResetPlayer 是一个蓝图函数:先用一个 Branch 确认这名玩家还在场(Target.IsActive[]),确认了才在里层 Branch 里把这名玩家那条覆盖成一个「全新出厂」的记录。
player_stats_table{} 是「按默认值造一个新实例」的写法——所有字段回到出厂设置,效果上等同于「删除后重开一行」。两套模式各就各位:能遍历的普通 map,用重建法精确删行;不能遍历的 weak_map,用重置法整条清零。
player_stats_table{} 就是「照默认值全新造一份」的写法——所有字段回到出厂设置,效果上等同于「删掉那行再重开一行」。两套套路各就各位:能逐行点名的普通 map,用重建法精确删行;不能点名的 weak_map,用重置法整条清零。
想从一张普通 map 里删掉一个键,正确做法是?
来源
本文整理自社区教程与 Epic 开发者论坛:
▸ How to Remove a Key from a Map in Verse(YouTube 教程)↗
▸ How do we remove player stats from weak map?(Epic 开发者论坛)↗