指令最佳化(optimize)是一门大学问,许多最佳化的方式有利用算法、利用Minecraft特性、利用官方建议等等。
小弟在此分享一部国外大神Cloud Wolf拍的影片「如何最佳化你的数据报」:
整理几个重点:
1. 不要使用红石
2. 使用函数树
3. 尽量在@e内加上type=
4. 减少nbt=
5. 使用药水云取代盔甲架
1. 不要使用红石
红石会让你变慢,因为每一个tick,游戏都在检查红石是否被触发。反之,单单只是放一个函数文件在数据报内,并不会影响性能。
如果你想要计时或延时功能,用计分板和/schedule指令,不要使用红石。
2. 使用函数树
把需要大量检测的的指令分组,创建函数树(function tree),这样才不用每次需要检查时都从头检查一遍。
举例,原先的指令长这样:
execute if score foo bar matches 1 run …
execute if score foo bar matches 2 run …
execute if score foo bar matches 3 run …
…
execute if score foo bar matches 9 run …
execute if score foo bar matches 10 run …
这些指令可以被缩减为
execute if score foo bar matches 1..5 run function test:1_to_5
execute if score foo bar matches 6..10 run function test:6_to_10
并把原本的测试分成两半,写入两个函数内。
假设foo在bar上的分数是8,原先的写法不论如何都要检查10次,但新的写法只要检查一次1..5,然后function test:1_to_5就会被跳过,接着测试6..10通过,进入function test:6_to_10,检查6、7、8、9、10,总共7次即可。
3. 尽量在@e内加上type=
不要直接打@e[tag=xxx]或@e[scores={xxx=x..}]去指定实体,而是尽可能地加上type,这样游戏才不用走访(traverse)所有实体去检查是否条件通过,而是只需要走访指定的实体即可。
4. 减少nbt=
NBT标签的检测非常耗性能,能避免则避免,或是使用进度(advancement)、述词(predicate)等方式代替。
例如像检测玩家的主手是否拿着钻石剑,传统方法@a[nbt={SelectedItem:{id:”minecraft:diamond_sword”}}],就是NBT检测。
若是改用述词就能够降低消耗,不过也要注意,要使用到述词提供的功能,而不是用述词套NBT检测,那样不会省到。
以下是检测主手拿着钻石剑的述词:
有关进度和述词的wiki:
进度 述词
Mojang工程师Bartosz Bok,在述词发布的那天对其表示:
5. 使用药水云取代盔甲架
由于影片拍摄的时间是2020年3月,所以没有提到。不过从2021年4月的1.17快照21w15a开始,我们有更好的「标记(marker)」可以使用。
根据Minecraft wiki,标记拥有以下特性:
不过药水云依然是很有用的实体,例如显示悬浮文本时,而且1.16和之前的数据报也需要使用到药水云。
早在1.9时代就有人比较过药水云和盔甲架(Stands vs Clouds),药水云没有材质,省去游戏算图(render)的时间。同时它天生隐形,碰撞箱极小,因此取代盔甲架不成问题。
现今需要使用到盔甲架的场合,只剩下装备栏,或是在标记出现之前保存虚拟NBT。
以上就是Cloud Wolf在影片中提到的如何最佳化数据报。而我个人也有部分心得:
1. 使用UUID取代@e指定
2. 指定时加上limit=1
3. 使用假玩家保存分数
4. 使用进度取代计分板
碍于篇幅因素,下次有机会再发文详解。
谢谢大家。