梓榆
本帖最后由 SPGoding 于 2019-10-7 23:25 编辑

[1.14.4] 追根溯「源」——实体选择器

MIT

GitHub 原帖

MCBBS 原帖

追根溯「源」

追根溯「源」,是从源代码角度对部分命令机制的漫谈系列。也许在骂着 Mojang SB 的间隙,偶尔看看他们是怎么犯下的蠢,能帮你更好地驾驭命令?谁知道呢。

本系列漫谈基于对 Minecraft Java Edition 1.14.4 的反编译与反混淆,十分感谢 FabricMC 提供的散发着自由气息的 yarn 项目,梓榆谨代表自己向所有该项目的贡献者致以最崇高的敬意。三鞠躬。由于散步商业闭源软件的源代码属于违法行为,本帖中只给出极少数代码片段,仅供学习交流使用。当然,因为名字都是人起的,为了方便,本人对部分映射名进行了修改。

阅读本系列可能需要具备一定的英语水平,或是具备查阅字典的能力,并不需要有多么高深的编程水平。毕竟,笔者压根就不会编程。

实体选择器

实体选择器(entity selector),也叫目标选择器(target selector),是一种用于选择实体的命令参数。你可以在 Wiki(或中文 Wiki)中阅读相关信息。

实体选择器的相关代码放置在 net.minecraft.command.EntitySelector 类中。我们可以看到其定义了如下的字段:



可以发现,它们和实体选择器的参数并不是一一对应的关系。那么是怎样的关系呢?这需要先从 net.minecraft.command.EntitySelectorReader 类讲起。

解析

EntitySelectorReader 可以解析玩家输入的字符串形式的实体选择器。我们简单来看一下它的流程:

判断输入字符串是 UUID、玩家名,还是一个实体选择器:

  • 若第一个字符不是 @,则为一个 UUID 或玩家名;
  • 若第一个字符是 @,则为一个实体选择器。

对于 UUID 或玩家名的处理流程

尝试按照 UUID 解析。如果解析成功,则设定:

字段备注
includingNonPlayertrue允许包含非玩家
uuid-UUID。
limit1相当于我们在选择器参数里写的 limit=1

解析失败,说明它不是 UUID,而是一个玩家名。设定:

字段备注
includingNonPlayerfalse不允许包含非玩家
playerName-玩家名。
limit1相当于我们在选择器参数里写的 limit=1

对于实体选择器的处理流程

解析实体选择器变量

在这一步中,将根据不同的实体选择器变量,来设定实体选择器的参数。

@p

字段备注
includingNonPlayerfalse不允许包含非玩家
limit1相当于我们在选择器参数里写的 limit=1
sorterNEAREST相当于我们在选择器参数里写的 sort=nearest
entityTypeEntityType.PLAYER相当于我们在选择器参数里写的 type=player

@a

字段备注
includingNonPlayerfalse不允许包含非玩家
limit2147483647相当于我们在选择器参数里写的 limit=2147483647。从这里可以看出,选择器选择的数量默认有上限的,一般达不到就是了。
sorterARBITRARY相当于我们在选择器参数里写的 sort=arbitrary
entityTypeEntityType.PLAYER相当于我们在选择器参数里写的 type=player

@r

字段备注
includingNonPlayerfalse不允许包含非玩家
limit1相当于我们在选择器参数里写的 limit=1
sorterRANDOM相当于我们在选择器参数里写的 sort=random
entityTypeEntityType.PLAYER相当于我们在选择器参数里写的 type=player

@s

字段备注
includingNonPlayertrue允许包含非玩家
senderOnlytrue只选择执行者
limit1相当于我们在选择器参数里写的 limit=1

@e

字段备注
includingNonPlayertrue允许包含非玩家
predicateEntity::isAlive筛选出活着的实体
limit2147483647相当于我们在选择器参数里写的 limit=2147483647
sorterARBITRARY相当于我们在选择器参数里写的 sort=arbitrary

读取选择器参数

在这一步中,将根据不同的选择器参数,来设定实体选择器的字段。本部分中使用 %值% 来简单表示玩家指定的值,实际上整个过程要复杂不少。

name=%值%

字段备注
predicateentity.getName().asString().equals(%值%) != isNegation判断实体名是否满足指定条件。

注: predicate 中调用的 asString() 很有意思。我们可以把它返回的内容大体理解为显示出来的文本内容(其实完全不是),因此你使用 /summon xxx ~ ~ ~ {CustomName:'{"text":"haha","color":"red"}'} 命令生成的生物可以被 @e[name=haha] 选中。可能以后本系列会对 JSON 原始文本进行详细的讲解。

distance=%值%

字段备注
localWorldOnlytrue只获取当前世界中的实体
distance%值%-

level=%值%

字段备注
includingNonPlayerfalse不允许包含非玩家
levelRange%值%-

x=%值%

字段备注
localWorldOnlytrue只获取当前世界中的实体
offsetX%值%-

y=%值%

字段备注
localWorldOnlytrue只获取当前世界中的实体
offsetY%值%-

z=%值%

字段备注
localWorldOnlytrue只获取当前世界中的实体
offsetZ%值%-

dx=%值%

字段备注
localWorldOnlytrue只获取当前世界中的实体
boxX%值%-

dy=%值%

字段备注
localWorldOnlytrue只获取当前世界中的实体
boxY%值%-

dz=%值%

字段备注
localWorldOnlytrue只获取当前世界中的实体
boxZ%值%-

x_rotation=%值%

字段备注
pitchRange%值%-

y_rotation=%值%

字段备注
yawRange%值%-

limit=%值%

字段备注
limit%值%-

sort=%值%

字段备注
sorter%值%-

gamemode=%值%

字段备注
includingNonPlayerfalse不允许包含非玩家
predicateisNegation ? mode != %值% : mode == %值%判断玩家的游戏模式是否满足指定条件。

team=%值%

字段备注
predicateteamName.equals(%值%) != isNegation判断实体所处的队伍是否满足指定条件。

type=%值%

字段备注
includingNonPlayer某些情况下设置为 false%值%minecraft:player 并且没有感叹号 ! 时设置为不允许包含非玩家
type某些情况下设置为 %值%只有在 %值% 不包含感叹号 ! 时才设置。
predicate判断实体的类型是否满足指定条件。

tag=%值%

字段备注
predicate判断实体的标签是否满足指定条件。

注:设置 predicate 的函数中有判断 %值% 是否为空字符串的部分,如果为空,则会设置 predicateentity.getScoreboardTags().isEmpty() != isNegation因此我们可以使用 @e[tag=] 来选择没有标签的实体,用 @e[tag=!] 来选择有任意标签的实体我也真佩服 Mojang 绕得出这个逻辑

nbt=%值%

字段备注
predicateTagHelper.areTagsEqual(%值%, tag, true) != isNegation判断实体的 NBT 标签是否满足指定条件。

scores=%值%

字段备注
predicate判断实体的分数是否满足指定条件。

advancements=%值%

字段备注
includingNonPlayerfalse不允许包含非玩家
predicate判断实体的进度是否满足指定条件。

小结

停一停,我都忘了这部分标题叫什么了!

这一部分的标题是「解析」,讲述的是游戏如何将玩家输入的字符串解析为实体选择器。游戏会根据不同的实体选择器变量、实体选择器参数,来不断调整各种字段的值。

从上面的字段表中我们可以发现,除了我们熟悉的 limitsortertype 等字段对应着实体选择器的各种参数外,还有 includingNonPlayerpredicatesenderOnlylocalWorldOnlyplayerNameuuid 这六个陌生的东西。它们是什么?有什么用?作为 CBer 的我们该如何利用?且看下一部分。





  • 其实「下一个字符」比「第一个字符」更加准确,但这么描述就涉及到了整个命令解析的技术细节,比较繁琐,所以简化了描述;
  • 本帖描述省略了各种异常处理。

获取

本部分将讲述实体选择器获取实体的工作流程。

获取实体的主要行为定义在函数 getEntities() 中。该函数有一个参数,代表命令的执行者(执行者不一定是实体,也可以是方块、命令函数、控制台等)。当不满足 includeNonPlayers,即只允许选取玩家时,该函数会把后续操作交给 getPlayers() 函数进行。

getPlayers() 的流程

  • 当指定了 playerName 时,遍历服务器玩家列表,找到玩家名和 playerName 一致的玩家(见源码 net.minecraft.server.PlayerManager#getPlayer),并返回
  • 当指定了 uuid 时,直接从服务器玩家列表中获取 UUID 为 uuid 的玩家(该操作基于 HashMap,速度非常快)(见源码 net.minecraft.server.PlayerManager#getPlayer),并返回
  • 基于命令执行坐标等,建立谓词(后文有说明);
  • senderOnlytrue,即只选择命令执行者时,将检查命令执行者是否为玩家、是否满足谓词,均满足则返回命令执行者,否则返回空列表。(由于是直接对命令执行者进行判断,而上文又提到过,命令的执行者被作为参数传入函数,因此该判断没有进行任何遍历,非常迅速);
  • localWorldOnlytrue,即只选择命令执行者所在的世界的玩家时,将遍历该世界的玩家列表,筛选出满足谓词的玩家(见源码 net.minecraft.server.world.ServerWorld#getPlayers);
  • 否则,遍历全服的玩家列表(见源码 net.minecraft.server.MinecraftServer#getPlayerManagernet.minecraft.server.PlayerManager#getPlayerList),筛选出满足谓词的玩家。
  • 将上述步骤(5 或 6)中筛选出的玩家按照 sorter 排序,再按照 limit 限制的数量从列表中移除多余的玩家(见源码 net.minecraft.command.EntitySelector#getEntities),并返回

刚刚我们提到了「谓词」,这就进行解释。

谓词

谓词,英文 predicate,可简单理解为一系列的条件。当我们把一个实体传入谓词中后,谓词会进行一系列判断,返回这个实体是否满足各种条件。

上述过程中的「建立谓词」,指的是在基础谓词的基础上,加入坐标谓词合成出新谓词的过程。

其中,基础谓词,是在实体选择器的解析过程中不断建立出来的:在解析过程中,设置 predicate 字段的时候,其实是在将新谓词合并到原有谓词中(见源码 net.minecraft.command.EntitySelectorReader#setPredicate)。在全部解析完成后,又会执行一个函数,添加 x_rotationy_rotationlevel 这三个选择器参数对应的谓词到基础谓词当中(见源码 net.minecraft.command.EntitySelectorReader#buildPredicate),至此基础谓词彻底建立完毕。例如,根据上面的字段表,在解析实体选择器 @e[type=zombie] 时,当解析完变量 @e 后,会将 Entity::isAlive 加入谓词;当解析完参数 type=zombie 后,会将「判断实体类型是否为僵尸」合并入谓词;全部解析完成后,得到了基础谓词,它要求实体既需要是活的,也需要是一只僵尸。

坐标谓词,是基于 distancexyzdxdydz 这七个选择器参数建立的对实体坐标的谓词。我们省去 offsetbox 等只在源代码中体现的技术性细节不谈,只说近似结论:

  • xyz 中缺省的项目会使用命令执行坐标补全;
  • 当存在 distance 时,会判定实体所在坐标到 (x, y, z) 的距离是否在 distance 限定范围之内;
  • 当存在 dxdydz 的任意一个时,会将缺省项目用 0.0 补齐,然后使用以下函数建立 Box,判定实体的碰撞箱是否与这个 Box 相交:

  1. // net.minecraft.command.EntitySelectorReader#createBox
  2. private Box createBox(double dx, double dy, double dz) {
  3.     boolean boolean_1 = dx < 0.0D;
  4.     boolean boolean_2 = dy < 0.0D;
  5.     boolean boolean_3 = dz < 0.0D;
  6.     double double_1 = boolean_1 ? dx : 0.0D;
  7.     double double_2 = boolean_2 ? dy : 0.0D;
  8.     double double_3 = boolean_3 ? dz : 0.0D;
  9.     double double_4 = (boolean_1 ? 0.0D : dx) + 1.0D;
  10.     double double_5 = (boolean_2 ? 0.0D : dy) + 1.0D;
  11.     double double_6 = (boolean_3 ? 0.0D : dz) + 1.0D;
  12.     return new Box(double_1, double_2, double_3, double_4, double_5, double_6);
  13. }
复制代码

这是一个非常莫名其妙的函数。说成人话,即:

如果 dx 小于 0.0,那么实体的碰撞箱在 x 轴方向上需要接触的范围是 (x + dx, x + 1.0)  
如果 dx 大于等于 0.0,那么该范围是 (x, x + dx + 1.0)  
不论 dx 取何值,该范围与我们印象中的 (x, x + dx) 都不相同。
其中 xdx 均为实体选择器参数。
(替换为 ydyzdz 同理。)

还有一件诡异的事情,当你完全没有指定 dxdydz 这三者,但是指定了 distance 的最大值(例如 distance=..5distance=3 等)的时候,游戏会自动建立出一个判定区域 Box,判定实体的碰撞箱是否与这个 Box 相交:

  1. float distanceMax = (Float)this.distance.getMax();
  2. box = new Box(
  3.     (double)(-distanceMax), (double)(-distanceMax), (double)(-distanceMax),
  4.     (double)(distanceMax + 1.0F), (double)(distanceMax + 1.0F), (double)(distanceMax + 1.0F)
  5. );
复制代码

这一段操作看似多出了不少没必要的操作,实则是对实体选择器极大的优化。为什么呢?且看下一部分,注意对比它与 getPlayers() 流程的差异。

getEntities() 的流程

  • 当不满足 includeNonPlayers,即只允许选取玩家时,调用上面提到的 getPlayers() 函数,并返回
  • 当指定了 playerName 时,遍历服务器玩家列表,找到玩家名和 playerName 一致的玩家(这一步和 getPlayers() 里相应步骤一致),并返回
  • 当指定了 uuid 时,遍历所有加载的世界,获取该世界中 UUID 为 uuid 的实体(后半部分操作同样基于 HashMap,速度非常快)(见源码 net.minecraft.server.world.ServerWorld#getEntity),并返回
  • 基于命令执行坐标等,建立谓词;
  • senderOnlytrue,即只选择命令执行者时,将检查命令执行者是否为实体、是否满足谓词,均满足则返回命令执行者,否则返回空列表。(由于是直接对命令执行者进行判断,而上文又提到过,命令的执行者被作为参数传入函数,因此该判断没有进行任何遍历,非常迅速);
  • localWorldOnlytrue,即只选择命令执行者所在的世界的实体时,将调用该世界的 getEntities() 函数,筛选出满足谓词的实体(见源码 net.minecraft.server.world.ServerWorld#getEntities);
  • 否则,遍历所有世界,调用每个世界的 getEntities() 函数,筛选出满足谓词的实体。
  • 将上述步骤(6 或 7)中筛选出的实体按照 sorter 排序,再按照 limit 限制的数量从列表中移除多余的实体(见源码 net.minecraft.command.EntitySelector#getEntities),并返回

其中,世界的 getEntities() 函数代码如下:

  1. // net.minecraft.world.World#getEntities
  2. public List<Entity> getEntities(@Nullable EntityType<?> type, Box box, Predicate<? super Entity> predicate) {
  3.     /* 所谓 box,是根据 x y z dx dy dz 这六个选择器参数计算出来的方块区域,
  4.      * 而我们上文曾说过,如果指定 distance,游戏会自动计算出一个 box,这便是为了让这一步骤中能够不遍历不必要的区块。
  5.      * 因此,只要参数中指定了 dx dy dz distance 中的任几个,并且 includingNonPlayer 为 true,都可以享受到由本函数带来的优化。
  6.      */
  7.     // 计算出 box 涉及到的区块坐标们。
  8.     int chunkMinX = MathHelper.floor((box.minX - 2.0D) / 16.0D);
  9.     int chunkMaxX = MathHelper.ceil((box.maxX + 2.0D) / 16.0D);
  10.     int chunkMinZ = MathHelper.floor((box.minZ - 2.0D) / 16.0D);
  11.     int chunkMaxZ = MathHelper.ceil((box.maxZ + 2.0D) / 16.0D);
  12.     List<Entity> result = Lists.newArrayList();

  13.     // 遍历这些区块坐标。
  14.     for(int chunkX = chunkMinX; chunkX < chunkMaxX; ++chunkX) {
  15.         for(int chunkZ = chunkMinZ; chunkZ < chunkMaxZ; ++chunkZ) {
  16.         WorldChunk chunk = this.getChunkManager().getWorldChunk(chunkX, chunkZ, false);
  17.         if (chunk != null) {
  18.             /* 调用该区块的 appendEntities 函数,把该区块中满足谓词的实体加入返回的实体列表当中。
  19.              * 而 net.minecraft.world.chunk.Chunk#appendEntities 函数中调用的是 net.minecraft.util.TypeFilterableList#getAllOfType,
  20.              * 在类 net.minecraft.util.TypeFilterableList 中,元素以类型索引,
  21.              * 说了这么多废话,就是想说,如果选择器参数中指定了 type,就只会遍历该类型实体的列表了。
  22.              */
  23.             chunk.appendEntities((EntityType)type, box, result, predicate);
  24.         }
  25.         }
  26.     }

  27.     return result;
  28. }
复制代码

可以看出,这一部分的流程大体与 getPlayers() 一致,但是在具体代码实现上,是从实体列表中遍历,还引入了针对实体类型 type、针对实体坐标所在区块的特殊优化,使得每次检索实体时不一定遍历整个实体列表,而是可以只获取某几个区块的指定类型的生物的实体列表。

小结

实体选择器在获取实体时的步骤,整合以后可以归类如下:

  • 对 UUID 的特殊处理,直接从 HashMap 索引,速度非常快;
  • @ssenderOnly)的特殊处理,直接读取参数,速度非常快;
  • 一般情况下,将从列表中获取实体。字段 includingNonPlayers 决定使用实体列表还是玩家列表,字段 localWorldOnly 决定范围是当前世界还是全服,是否有字段 box 决定范围是某几个区块还是全地图,是否有字段 type 决定范围是指定类型的实体列表还是全部实体列表;
  • 把获取到的实体传入谓词,筛选出符合条件的实体;
  • 根据 sorter 排序,再根据 limit 移除多余实体。

如果你追求性能的话:

  • 在函数全篇大量使用某个相同实体(如 @e[tag=marker])时,不如套一层 execute as @e[tag=marker] run function xxx,用极其高效的 @s 替换掉多次遍历全服实体列表;
  • 在选择确定类型的实体的时候,在选择器内显式指定不带感叹号且不是实体标签的 type
  • 在选择确定位置的实体的时候,尽量指定 distancedxdydz 中的一个或几个,缩小遍历范围;
  • 如果你有些病态,可以用手动指定 UUID 来替代用 tag 标记 marker,但我个人不太推荐这么做。

另外,由常识:

  • 玩家列表通常比实体列表要小,至少不会比它大;
  • 当前世界的列表通常比全服列表小,至少不会比它大。

因此,如果你是一个十分病态、极致追求性能(其实一般情况下没有必要,真的涉及实体数量非常多的话,MC 自己就会卡得不行了,你命令再怎么高性能也挽救不回来)的玩家的话,可以通过限定选择器变量、选择器参数,在允许的情况下尽量使 includingNonPlayers 变为 false,使 localWorldOnly 变为 true

后语

在最初观看 Wiki、看到实体选择器的相关介绍时,我曾有过各种各样的困惑。而如今,我也能够把握十足地回答它们了。请注意,以下内容适用于 Minecraft 1.14.4,可能颠覆您的三观:

@e[type=minecraft:player]@a 是否等价?性能呢?

并不等价。根据「解析」部分的字段表,@e 向谓词中自动加入了 Entity::isAlive,导致前者不能选中死亡的玩家;后者则没有这种限制。

性能区别不大。前者在解析完 type 后会设定 includingNonPlayersfalse,后者 @a 自动设定 includingNonPlayersfalse,两者都是从玩家列表中选择玩家。

@e[nbt={UUIDMost:1L,UUIDLeast:1L}]00000000-0000-0001-0000-000000000001 是否等价? 性能呢?

效果不等价。如上所述,前者不能选中死亡的实体。不过鉴于实体死亡后很快就会从实体列表中移除,这个差别不是很大。

性能上前者慢于后者。因为前者将遍历全部世界和世界中的全部实体,而后者将在遍历全部世界时直接从 HashMap 中获取指定 UUID 对应的实体。

@p[name=SPGoding]SPGoding 是否等价? 性能呢?

效果等价。都是选择名为 SPGoding 的玩家。

性能区别不大。两者都遍历了一遍全服玩家列表。

@e[tag=marker]@e[tag=marker,type=minecraft:armor_stand] 是否等价? 性能呢?(假设只有盔甲架有 marker 标签。)

效果等价。

性能上前者慢于后者,因为前者将遍历各地图、各区块的全部实体,而后者将只遍历各地图、各区块的盔甲架。

@a[sort=nearest,limit=1]@p 是否等价? 性能呢?

效果等价,都是选择最近的玩家。

性能几乎一致。不过,写那么一大长串的人似乎不太聪明…?

结语

不知道本帖是否具有很高的实用性,但我个人认为,从源代码的角度理解实体选择器的运作原理,能让人不那么「被 Mojang 牵着鼻子走」,至少在写下每一个选择器的时候,心里能有点底,知道它到底意味着什么。

由于笔者完全不会编程,只是对命令略有涉猎,文章中可能有不少错误,望各位 dalaoes 不吝指出,感谢。

你知道吗

本部分是本人在分析反编译后的源代码中发现的一些有趣的事情。

  • 在判断实体到指定坐标的距离是否满足选择器参数 distance 中规定的范围时,Mojang 进行比较的是距离的平方,省去了很多次开根运算。其实我们在写一些数据包的时候也可以这样的,不一定非要算出距离是多少,距离的平方也许就够用了
  • 在判断实体 NBT 标签的谓词中,有一句向玩家的标签中添加 SelectedItem 的代码。也就是说,我们平时检测得很爽的 SelectedItem 标签其实原本是不存在的,它是 Mojang 为了让我们方便检测,硬生生加出来的。感动!
  • 对字符串进行的 switch 编译以后会变成对 hashCode()switch。这是因为 switch 其实只能对数字进行,对字符串的支持是在 Java7 加入的补救措施。

来自群组: Command Block Logic

ruhuasiyu
SelectedItem 应该是1.8加入的,以前就只能恼人地用SelectedItemSlot+背包判断,所以还是mojang良心发现加入的……

另外,不是说一般加上distance或者其它范围限制的话会比直接从所有实体选择要快吗?这一点能看出来吗?

梓榆
本帖最后由 梓榆 于 2019-7-26 11:49 编辑
ruhuasiyu 发表于 2019-7-26 00:39
SelectedItem 应该是1.8加入的,以前就只能恼人地用SelectedItemSlot+背包判断,所以还是mojang良心发现加 ...

不能,大概是谣传 ;(

确实如此。distance、dx、dy、dz 中任意定义一个或几个,都能有效减少遍历范围。

chyx
效果等价,都是选择 UUID 为  00000000-0000-0001-0000-00000000000 的实体。

性能上前者慢于后者。因为前者将遍历全部世界和世界中的全部实体,而后者将在遍历全部世界时直接从 HashMap 中获取指定 UUID 对应的实体。

你不是上面刚说完前者选不到死了的吗。。。。。。

⊙v⊙
本帖最后由 ⊙v⊙ 于 2019-7-25 10:57 编辑

说得好,但还是有一些疑问

dxdydz创建的box做不到(范围从全世界缩减到box范围)减少目标的效果?
@e[tag=marker]、@e[tag=marker,type=minecraft:armor_stand] 是否等价? 性能呢?(假设只有盔甲架有 marker 标签。)
选择器参数是否有处理顺序,如果有的话,例中使用[type,tag]是否会有不同的效果?
为什么@e的值是EntityType.PLAYER?
这是哪个大佬的小号?头像为什么不是往中间摔?


也请大佬讲讲这些差别...
  1. execute if score @s board matches 1..
  2. execute if entity @s[scores={board=1..}]
复制代码
  1. execute if data @s A.B
  2. execute if entity @s[nbt={A:{B:1b}}]
复制代码

梓榆
chyx 发表于 2019-7-26 03:37
你不是上面刚说完前者选不到死了的吗。。。。。。

因为普通实体死了之后没多久就删了,所以没考虑这么多…
补上了!

梓榆
本帖最后由 梓榆 于 2019-7-26 14:41 编辑
⊙v⊙ 发表于 2019-7-26 04:47
说得好,但还是有一些疑问
dxdydz创建的box做不到(范围从全世界缩减到box范围)减少目标的效果?

没错,dx dy dz distance 都可以,帖子已更新

为什么@e的值是EntityType.PLAYER?

对不起!!!

这是哪个大佬的小号?头像为什么不是往中间摔?

为什么你用户名的嘴是尖的,而不是圆的?

execute if score @s board matches 1..
execute if entity @s[scores={board=1..}]

前者的代码是这样的


后者 scores 所引入的断言代码是这样的


前者直接解析出对应的计分项,然后比较值的大小;而后者会遍历一次实体的全部分数。所以前者性能更好。

execute if data @s A.B
execute if entity @s[nbt={A:{B:1b}}]

这两个效果首先就不等效。第一个只要有 B 就行,而第二个需要 B 为 1b。当然,如果 B 是自定义标签并且只在 1b 的时候才设置的话,效果上就没有区别了。

前者的代码


后者的代码


两者都是先用实体的 `toTag()` 构造出 NBT。前者是不断层层递进,看 NBT 标签符不符合传入的路径;后者是遍历 valueTag 复合标签中的键,看该键在 valueTag 中的值与在 tag 中的值是否相等。俩都挺复杂的…可能后者因为涉及到两个标签间的比较会更慢一些。

chyx
0-0-1-0-1

00000000-0000-0001-0000-000000000001
等价吗?

卡狗


梓榆
chyx 发表于 2019-7-30 01:18
0-0-1-0-1

00000000-0000-0001-0000-000000000001

这一部分解析是由 Java 的 java.util.UUID.fromString 所实现的,我 哪 知 道【跑

也许查看 Wikipedia 对 UUID 的介绍能明白些什么,但因为太硬核了我不想看


1581277682
666666666666666

xin_erQWQ
好复杂看不懂...

RF_Tar_Railt
那性能上execute as @e[tag=marker,type=minecraft:armor_stand] run fuction foo:c与
a.mcfunction:
execute as @e[type=minecraft:armor_stand] run function foo:b
b.mcfunction:
execute as @e[tag=marker] run function foo:c
是否相同?
还有@e[tag=marker,nbt={A:{B:1b}}] 与 @e[tag=marker] if entity @s [nbt={A:{B:1b}}]?

Doraemon_
感谢大佬搬运 ,虽然我这个菜鸡不怎么看得懂(手动滑稽)

uuu2011
在函数全篇大量使用某个相同实体(如 @e[tag=marker])时,不如套一层 execute as @e[tag=marker] run function xxx,用极其高效的 @s 替换掉多次遍历全服实体列表;

这个太有用了,我一直在困惑是否需要用在大量 @e 外面套一层 execute ,今天找到答案了

mon-yu
?(.???.)?来份????

DOPING_DEFINED
顶顶顶顶顶顶顶顶顶顶顶顶顶顶!!

3220392
又是个大佬,萌新表示看不懂

海豹暴晒
哇刚入门java的我还是完全看不懂

Potat
对小白的我来说,真是头大

13851214659qwe
6666666666666666

奋飞的小鸟
你们知道吗?对于一个问心无愧的命令方块渣渣,这个帖子扎了TA多深的心吗?都是因为TA看不懂!!!

烟火依旧
MCBBS有你更精彩

歪歪c
牛逼啊,铁子,可以

趴趴小猪
6666666666

745056399
很好,楼主很用心,期待楼主能在mcbbs中有更多的作品

寒冰520
厉害厉害厉害厉害

junkoofpurity
大佬,少见如此细致的

柘木铃
这样,如果减少遍历的实体数,即使把选择器加得更长,性能也会优化这样?
之后一定可以派上用场,感谢梓榆
梓榆的教程风格还是一如既往地让人安心(某叔写这种代码层面的教程的话铃子可能见标题就跑(?
顺便以后有机会看到更多同系列的作品吗?铃子相当期待

下一页 最后一页