Minecraft(我的世界)中文论坛

标题: [1.14.4] 追根溯「源」——实体选择器 [打印本页]

作者:  SPGoding    时间: 2019-7-26 00:01
标题: [1.14.4] 追根溯「源」——实体选择器
本帖最后由 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 解析。如果解析成功,则设定:

字段备注
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() 的流程


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

谓词

谓词,英文 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 等只在源代码中体现的技术性细节不谈,只说近似结论:


  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() 的流程


其中,世界的 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、针对实体坐标所在区块的特殊优化,使得每次检索实体时不一定遍历整个实体列表,而是可以只获取某几个区块的指定类型的生物的实体列表。

小结

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


如果你追求性能的话:


另外,由常识:


因此,如果你是一个十分病态、极致追求性能(其实一般情况下没有必要,真的涉及实体数量非常多的话,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 不吝指出,感谢。

你知道吗

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


[groupid=546]Command Block Logic[/groupid]
作者: ruhuasiyu    时间: 2019-7-26 00:39
SelectedItem 应该是1.8加入的,以前就只能恼人地用SelectedItemSlot+背包判断,所以还是mojang良心发现加入的……

另外,不是说一般加上distance或者其它范围限制的话会比直接从所有实体选择要快吗?这一点能看出来吗?
作者:  SPGoding    时间: 2019-7-26 00:45
本帖最后由 梓榆 于 2019-7-26 11:49 编辑
ruhuasiyu 发表于 2019-7-26 00:39
SelectedItem 应该是1.8加入的,以前就只能恼人地用SelectedItemSlot+背包判断,所以还是mojang良心发现加 ...

不能,大概是谣传 ;(

确实如此。distance、dx、dy、dz 中任意定义一个或几个,都能有效减少遍历范围。
作者: chyx    时间: 2019-7-26 03:37
效果等价,都是选择 UUID 为  00000000-0000-0001-0000-00000000000 的实体。

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

你不是上面刚说完前者选不到死了的吗。。。。。。
作者: ⊙v⊙    时间: 2019-7-26 04:47
本帖最后由 ⊙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}}]
复制代码

作者:  SPGoding    时间: 2019-7-26 10:16
chyx 发表于 2019-7-26 03:37
你不是上面刚说完前者选不到死了的吗。。。。。。

因为普通实体死了之后没多久就删了,所以没考虑这么多…
补上了!
作者:  SPGoding    时间: 2019-7-26 10:41
本帖最后由 梓榆 于 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    时间: 2019-7-30 01:18
0-0-1-0-1

00000000-0000-0001-0000-000000000001
等价吗?
作者: :spgbigfan:    时间: 2019-7-30 22:22


作者:  SPGoding    时间: 2019-7-30 23:13
chyx 发表于 2019-7-30 01:18
0-0-1-0-1

00000000-0000-0001-0000-000000000001

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

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


作者: 1581277682    时间: 2019-7-31 17:18
666666666666666
作者: xin_erQWQ    时间: 2019-8-1 06:05
好复杂看不懂...
作者: RF_Tar_Railt    时间: 2019-8-2 00:59
那性能上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}}]?
作者: (=°ω°)丿    时间: 2019-8-2 08:48
本帖最后由 Teenager_Yang 于 2019-8-2 16:18 编辑

我也要来问问题!
dalao 分析一下:
effect give @a[tag=233] minecraft:speed 10 0 true
execute as @a[tag=233] run effect give @s minecraft:speed 10 0 true
execute as @a run effect give @s[tag=233] minecraft:speed 10 0 true
execute as @a if entity @s[tag=233] run effect give @s minecraft:speed 10 0 true
这几个在性能上的差异。


作者: Doraemon_    时间: 2019-8-2 10:43
感谢大佬搬运 ,虽然我这个菜鸡不怎么看得懂(手动滑稽)
作者: 底层咸鱼    时间: 2019-8-2 16:09
本帖最后由 897412176 于 2019-8-4 10:03 编辑
RF_Tar_Railt 发表于 2019-8-2 00:59
那性能上execute as @e[tag=marker,type=minecraft:armor_stand] run fuction foo:c与
a.mcfunction:
execu ...

就第 1 个问题的话,我觉得应该差不多。
execute as @e[tag=marker,type=minecraft:armor_stand] run fuction foo:c

a: execute as @e[type=minecraft:armor_stand] run function foo:b
b: execute as @e[tag=marker] run function foo:c
a: 我个人没有去翻过代码,不知道当 tag 和 type 同时出现的话,不知道是先检测 tag 还是 type……
不过我记得应该和你写的顺序无关,检测顺序应该是代码写死的)
当然,如果是先检测 type,再检测 tag 的话,两个应该是等价的(不考虑在函数内调用其他函数消耗的性能)
当然,我对代码和英语一窍不通,如果说错了不要打我(请求)
(此答案仅供参考,具体等楼主回答)
强行甩锅

经 SPG dalao 这么一说,发现两个并不是等价的(无论是性能还是效果)
第2个:先 as 所有盔甲架 ,再 as 所有 marker,那么所有没有 type=minecraft:armor_stand,但是有 tag=marker 也会执行函数 c 。举个例子:有 m 个只有 type=minecraft:armor_stand 和 n 个只有 tag=marker 以及 type 和 tag 两者都有的 k 个,那么函数 c 会执行:(m+k)(n+k)次 。

当然如果把函数 b 改成 as @s[tag=marker] ,我说的应该是对的。
Spg dalao 快来帮我!

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

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

作者: SPGoding    时间: 2019-8-3 23:28
RF_Tar_Railt 发表于 2019-8-2 00:59
那性能上execute as @e[tag=marker,type=minecraft:armor_stand] run fuction foo:c与
a.mcfunction:
execu ...

久等了。


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
遍历了一次盔甲架的列表、一次全部实体列表,肯定会慢一些。我觉得把 b 里面的 @e 改成 @s 的话,两种写法性能差别就不是很大了。


@e[tag=marker,nbt={A:{B:1b}}]
@e[tag=marker] if entity @s [nbt={A:{B:1b}}]
我个人认为区别不大…众所周知,@s 超级快(
作者: SPGoding    时间: 2019-8-3 23:31
Teenager_Yang 发表于 2019-8-2 08:48
我也要来问问题!
dalao 分析一下:
effect give @a[tag=233] minecraft:speed 10 0 true

不知道,别纠结这没用的



从命令解析的角度,第一句最快,只读取了一次命令树,没有奇怪的 redirect(等一下,你在讲什么,帖子里一句没提到这个啊)
剩下三种写法的话,都挺…那什么的。我不知道谁更快,也不想知道。

另外,一般第一种写法最便于理解,剩下的都太诡异了,选择器参数这儿一块儿那一块儿的,为了你自己的视力着想,也不能选择后三种啊!
作者: mon-yu    时间: 2019-8-4 12:05
?(.???.)?来份????
作者: DOPING_DEFINED    时间: 2019-8-5 10:07
顶顶顶顶顶顶顶顶顶顶顶顶顶顶!!
作者: 3220392    时间: 2019-8-5 16:25
又是个大佬,萌新表示看不懂
作者: 海豹暴晒    时间: 2019-8-6 01:54
哇刚入门java的我还是完全看不懂
作者: Potat    时间: 2019-8-6 08:43
对小白的我来说,真是头大
作者: 13851214659qwe    时间: 2019-8-6 16:36
6666666666666666
作者: 奋飞的小鸟    时间: 2019-8-6 17:44
你们知道吗?对于一个问心无愧的命令方块渣渣,这个帖子扎了TA多深的心吗?都是因为TA看不懂!!!
作者: 烟火依旧    时间: 2019-8-7 13:41
MCBBS有你更精彩
作者: 歪歪c    时间: 2019-8-9 00:24
牛逼啊,铁子,可以
作者: 趴趴小猪    时间: 2019-8-11 15:25
6666666666
作者: 745056399    时间: 2019-8-12 17:50
很好,楼主很用心,期待楼主能在mcbbs中有更多的作品

作者: 寒冰520    时间: 2019-8-12 18:05
厉害厉害厉害厉害
作者: junkoofpurity    时间: 2019-8-12 23:13
大佬,少见如此细致的
作者: 柘木铃    时间: 2019-8-13 08:51
这样,如果减少遍历的实体数,即使把选择器加得更长,性能也会优化这样?
之后一定可以派上用场,感谢梓榆
梓榆的教程风格还是一如既往地让人安心(某叔写这种代码层面的教程的话铃子可能见标题就跑(?
顺便以后有机会看到更多同系列的作品吗?铃子相当期待
作者: 1755201743    时间: 2019-8-13 12:24
666666666666666666666666666666666
作者:  SPGoding    时间: 2019-8-13 12:37
柘木铃 发表于 2019-8-13 08:51
这样,如果减少遍历的实体数,即使把选择器加得更长,性能也会优化这样?
之后一定可以派上用场,感谢梓榆
...

选择器写得长些也只是解析的时候要多读几个字符而已,性能损耗相比于整个实体列表的减小应该是可以忽略不计的。

谢谢铃子夸奖w 是想继续写下去来着,但人比较懒,鱼身上的毛又那么柔软,让人忍不住想去摸…
作者: 233小星星    时间: 2019-8-13 14:55
1755201743 发表于 2019-8-13 12:24
**** 作者被禁止或删除 内容自动屏蔽 ****

你很六牛批666
作者: AppleCarrot    时间: 2019-8-14 09:00
chyx 发表于 2019-7-26 03:37
你不是上面刚说完前者选不到死了的吗。。。。。。

其实相差不大,实体死的时候很快两个选择器都选不到了,后者可以选中正在死亡的实体,但如果这个uuid是个玩家的,就跟@e[type=player]和@a区别一样了
作者: 末、红尘    时间: 2019-8-14 10:09
加油!!
作者: chyx    时间: 2019-8-14 11:38
AppleCarrot 发表于 2019-8-14 09:00
其实相差不大,实体死的时候很快两个选择器都选不到了,后者可以选中正在死亡的实体,但如果这个uuid是个玩 ...

要是能选择到正在死亡的实体
是可以改大它的生命来把它救回来的
明白我的意思不?
作者: AppleCarrot    时间: 2019-8-14 13:50
chyx 发表于 2019-8-14 11:38
要是能选择到正在死亡的实体
是可以改大它的生命来把它救回来的
明白我的意思不? ...

ee其实并不能只是这么做
标签控制死亡的NBT标签 并不仅仅是是health标签[控制生命值的]还受到deathTime标签[控制死亡动画]影响,后者在为0是就是实体活着
前者是触发后者的条件,然而反过来当后者触发时候修改前者为正数,并不会阻止后者的增加
换句话说当死亡动画已经播放时,增加这个实体的生命值并不能挽救它,除非同时更改前者为正数且后者为0
作者: QAQexe    时间: 2019-8-15 17:26
看看,感谢分享
作者: PhonixSupheria    时间: 2019-8-15 20:39
赞赞赞,码上
作者: bhyzgzz    时间: 2019-8-17 10:17
牛逼啊大触
作者: hiahiahi    时间: 2019-8-17 13:20
666666666666666666666666
作者: 紫云SAMA_    时间: 2019-8-20 22:03

作者: 2047283952    时间: 2019-8-22 15:55
牛逼6665555

作者: 亿岁    时间: 2019-8-23 16:18
看着有点晕
作者: MC520520    时间: 2019-8-23 20:57
有点复杂.....
作者: 念旧丶无心    时间: 2019-8-24 20:40
很棒棒的啊
作者: Just_Suning    时间: 2019-8-27 21:28
6666666666
作者: 51691    时间: 2019-8-30 15:51
这操作太真实
作者: Vioket_QAQ    时间: 2019-9-4 20:04
6666666666
作者: 森林蝙蝠    时间: 2019-9-5 08:34
predicate<T>在Java中不是“断言”,而是“谓词”,指那种T->return boolean的lambda表达式,例如ruhua->ruhua.isThirty()==true;
而Java里的断言叫做assert,用于单元测试,比如assertTrue(ruhua.getAge(),30),如果getAge的结果不是30说明有问题,会弹消息。
作者: apzt    时间: 2019-9-6 21:25
很有帮助,感谢感谢
作者: o320481446    时间: 2019-9-6 22:45
太复杂了
作者: ZhuJingYe    时间: 2019-9-7 10:46
实体选择器折磨好啊
作者: lzk122001333    时间: 2019-9-7 23:18
感谢大佬 就是没看懂
作者: HHQart    时间: 2019-9-9 17:10
作为基岩版玩家我对此一窍不通【而且基岩版@r后面加type参数就可以选择非玩家实体了】
作者: loubi    时间: 2019-9-9 18:57
6666666666666666666666
作者: 邪恶草    时间: 2019-9-19 14:46
看来要准备学习Java了
作者: liness    时间: 2019-9-21 16:47
233333333333333333

作者: deatin6long    时间: 2019-9-24 12:50
5555555555
作者: 1272097077    时间: 2019-9-28 20:57
很好 ,学习一下
作者: ‮ebotanihS    时间: 2019-12-26 19:24
刚刚学完java的萌新来拜读此文,然后发现

predicate不就是一种filter嘛,叫“滤器”不就非常棒

请lz轻喷(
作者:  SPGoding    时间: 2019-12-27 01:10
Shinatobe 发表于 2019-12-26 19:24
刚刚学完java的萌新来拜读此文,然后发现

predicate不就是一种filter嘛,叫“滤器”不就非常棒[:.. ...

确实,filter 和 predicate 经常一起出现,比如
  1. Predicate<Integer> isEven = e -> e % 2 == 0;
  2. ...
  3. x.stream().filter(isEven)
复制代码

(我不会编程,所以代码是复制的:在这里

但因为我不懂编程,我也不敢瞎翻译。只好听取博学的编程 dalaoes 的建议喽
作者: ‮ebotanihS    时间: 2019-12-27 12:33
梓榆 发表于 2019-12-27 01:10
确实,filter 和 predicate 经常一起出现,比如

(我不会编程,所以代码是复制的:在这里)

我明白了。更精细地说,与其说它是“滤器”,不如说是“过滤条件”。

或者filter criteria……

……或者“滤网”?(逃


作者: fukemansite    时间: 2020-1-7 15:41
真的是大佬哦。佩服佩服耶
作者: Evasi0n丶    时间: 2020-1-19 22:58
楼主大大你好!想问一下,我是1.12.2的版本,参数里没有distance什么的,我想知道如果我设定了x=,y=,z=,r=的参数,可不可以有效地帮我缩小实体的遍历范围呢?
作者: brooke_zb    时间: 2020-2-15 08:54
@SPGoding 出来答题(
假设tag为foo的盔甲架只存在一个,那么是不是
@e[type=armor_stand,tag=foo,limit=1]的效率会比@e[type=armor_stand,tag=foo]的效率要高?(除非很不幸遍历到最后一个盔甲架才符合结果)

作者: SPGoding    时间: 2020-2-15 16:06
brooke_zb 发表于 2020-2-15 08:54
@SPGoding 出来答题(
假设tag为foo的盔甲架只存在一个,那么是不是
@e[type=armor_stand,tag=foo,limit=1] ...

不是

limit 是最后处理的,事实上加了以后反而还要多一步移除多余实体的操作。
作者: 13626464852    时间: 2020-7-15 08:45
从头看到尾懵逼的我
作者: kunkun520    时间: 2020-7-29 20:37
mcbbs有你更精彩!!!




欢迎光临 Minecraft(我的世界)中文论坛 (https://www.mcbbs.net/) Powered by Discuz! X3.5