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

遍历 map:Key->Value 语法与手动计数器

同样一个箭头 ->,遍历数组时左边是数字下标,遍历 map 时左边却是键。想在 map 遍历里同时拿到「这是第几个」?官方没给捷径——这一页讲清这处不对称,以及社区公认的补法。

同一种写法,遍历数组时多出来的那位是数字下标,遍历 map 时多出来的却是「键」。想在遍历 map 时同时拿到「这是第几个」?官方没给捷径——这一页讲清这处不对称(蓝图里 For Each Loop (Array) 给你 Index、For Each Loop (Map) 给你 Key,正是同一处区别),以及社区公认的补法。

一、同一个箭头,两种收获

第 10 课学过数组的带下标遍历:for (Index -> Item : Loot):,箭头左边是从 0 开始的 int。map 也支持箭头形式:for (Key -> Value : Map):——但注意,这里左边拿到的是,不是序号。形式一模一样,语义完全两回事:数组的「地址」天生就是数字,map 的「地址」是你自己定的键(字符串、int 都行),所以箭头左边送上的自然也是键。

第 10 课学过数组的带下标遍历(蓝图 For Each Loop (Array) 的 Array Index 引脚),从 0 开始数。map 也有同样「多给一位」的写法:for (Key -> Value : Map):——但注意,这里多给的是,不是序号,正好对应蓝图 For Each Loop (Map) 的 Key 引脚。写法长得一模一样,含义完全两回事:数组的「地址」天生是数字下标,map 的「地址」是你自己定的键(字符串、整数都行),所以这里送上来的自然是键。

shop_device.verse
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }

shop_device := class(creative_device):

    # 商店价目表:键是商品名,值是价格
    Prices:[string]int = map{"药水" => 50, "木剑" => 120, "金币袋" => 300}

    OnBegin<override>()<suspends>:void =
        for (Name -> Price : Prices):
            Print("{Name} 售价 {Price} 金币")

读图翻译:Prices 是一张价目表(蓝图里的 Map,键是商品名、值是价格)。这段就是 For Each Loop (Map):每轮从 Key 引脚拿到商品名 Name、从 Value 引脚拿到价格 Price,用 Print String 打成「__ 售价 __ 金币」。

这段代码里 Name 依次是各个商品名,Price 是对应价格。省事归省事,但假如需求是「打印第 1 件、第 2 件、第 3 件」呢?数组遍历顺手就有的序号,map 这边——没有。

二、要序号?官方也没捷径,自己数

这不是你没找到 API。Epic 开发者论坛上有人专门发帖问「用 for 遍历 map 时能不能同时拿到一个序号」,这个帖子至今零回复——官方与社区都没有给出内建方案,因为它确实不存在。可行的做法就是发帖人自己记录的那个朴素 workaround:循环外开一个计数 var,循环体内手动加一。

这不是你没找到节点。Epic 开发者论坛上有人专门发帖问「用 for 遍历 map 时能不能顺便拿到一个序号」,这个帖子至今零回复——官方和社区都没有内建办法,因为它确实不存在。能用的做法就是发帖人自己记下的那个土办法:循环外面先建一个计数变量,循环体里每轮手动加一。

counted_shop_device.verse
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }

counted_shop_device := class(creative_device):

    Prices:[string]int = map{"药水" => 50, "木剑" => 120, "金币袋" => 300}

    OnBegin<override>()<suspends>:void =
        # map 遍历没有内建序号:循环外开计数器,体内手动 +1
        var Counter:int = 0
        for (Name -> Price : Prices):
            set Counter += 1
            Print("第{Counter}件:{Name} 售价 {Price} 金币")

读图翻译:循环外先建一个整数变量 Counter = 0(蓝图里的 Integer 变量),For Each Loop (Map) 每轮先用一个 Set 节点把 Counter 加一(set Counter += 1),再用 Print String 打「第 __ 件:__ 售价 __」。这正是蓝图里给 ForEach 手动配一个计数变量的老套路。

两个细节值得留意。其一,set Counter += 1 写在循环体里,而不是 for 的括号子句里——子句是失败上下文,必须可回滚,把带副作用的 set 塞进去要么报效果错误、要么在过滤失败时被回滚得无影无踪,徒增混乱。其二,这个计数器数的是「实际执行的轮次」:如果 for 带了过滤子句,被跳过的轮次不会加一,序号依然连续——这通常正是你想要的。

两个细节值得留意。其一,set Counter += 1(那个 Set 节点)要放在循环体里,别塞进 for 的括号筛选里——筛选必须能试完收回,把「做了就算数」的 Set 放进去,要么领效果错误(Compile 报红),要么在某轮没通过时被一起收回、白加,徒增混乱。其二,这个计数器数的是「真正跑过的轮次」:如果 for 带了筛选,被跳过的那几轮不会加一,序号照样连续——这通常正合你意。

三、顺序能信吗?先实测再托付

最后一个自然的问题:map 遍历吐出键值对的顺序是什么?常见的观察是与写入顺序一致,但请谨慎——官方文档并没有把遍历顺序写成一条硬性承诺,依赖它的逻辑属于「在没有合同的地方签字」。如果你的玩法逻辑(排行榜、商店排序)依赖确定的顺序,稳妥做法有二:要么先在你的目标版本里实测确认(# TODO: 以 Verse API Reference 为准),要么干脆别赌——把键收进数组、按需排序后再逐个用 Map[Key] 取值,顺序从此掌握在自己手里。

最后一个自然的问题:map 遍历吐出这些键值对的顺序是什么?常见的观察是跟写入顺序一致,但请当心——官方文档并没有把遍历顺序写成一条硬承诺,依赖它就像「在没签合同的地方签字」。如果你的玩法(排行榜、商店排序)依赖确定顺序,稳妥做法有二:要么先在你的目标版本里实测一遍确认(# TODO: 以 Verse API Reference 为准),要么干脆别赌——先把所有键收进一个数组、按需排好序,再逐个用键去 map 里取值(蓝图里 Map 的 Find 节点),顺序从此攥在自己手里。

另外别忘了第 10 课的值语义:for 遍历的是循环开跑那一刻的 map 快照,循环体内往同名 var map 里 set 新键值,本轮遍历不会跟着变。

另外别忘了第 10 课的「快照」:for 认的是循环开跑那一刻的 map 样子,循环体里就算你用 Set 节点往同一个 map 变量里塞新键值,本轮遍历也不会跟着变。

四、随堂小测

for (K -> V : MyMap) 遍历一个 [string]int 的 map 时,K 是什么?用 For Each Loop (Map) 遍历一个「键是文字、值是整数」的 map 时,多给你的那一位 K 是什么?

来源

本文整理自 Epic 开发者论坛与官方文档:Iterate map variable in a for loop and index counter ↗Array in Verse ↗