本帖最后由 kongbaiyo 于 2020-6-6 09:07 编辑


Minecraft Ray Collision Detector

超精准射线碰撞检测器


这个数据包从根本上、低消耗地解决了原版非完整方块碰撞判定的问题。


特点
  • 碰撞极为精准,和自带的黑框别无二致
  • 开销较低,穿过一个方块平均消耗命令200条左右
  • 可任意控制判定距离,灵活度高
  • 建模方便,可增加新的自定义碰撞箱

如何使用

给任意一个药水云的三个计分板(mrcd_x0、mrcd_y0和mrcd_z0)分别在三个方向上要前进的距离(单位是毫块,也就是坐标乘1000),再执行mrcd:generic/start这个函数即可。该药水云会传送至碰撞终点。若发生碰撞,则会产生mrcd_touch_edge和mrcd_touch_DIRECTION这些tag。其中后一个tag表示该药水云碰撞的面。(包括x_plus、x_minus、y_plus、y_minus、z_plus和z_minus)

如果希望药水云可以穿透一些玩家可穿透的方块,给该药水云加上mrcd_bullet这个tag即可。

如果希望药水可以和实体碰撞的话,给该药水云加上mrcd_entity这个tag即可。若碰撞到实体,则会产生mrcd_touch_entity这个tag,同时被碰撞的实体会被打上mrcd_tagget_entity这个tag。默认情况下不识别玩家,如果想要更改的话就在mrcd:target这个实体标签下添加player即可。可以与mrcd_bullet标签同时使用。

具体实现可以查看mrcd:raycast,mrcd:bullet和mrcd:entity这三个函数,分别表示了视线追踪的应用、发射子弹和识别实体的应用。


限制

由于游戏内花花和竹子的碰撞箱是根据方块坐标计算变化的,因此暂时没有将其实现,视为完整方块。

没有任何限制啦!在最新版v1.3中MRCD已能够计算竹子和花花的坐标偏移。没有任何方块能逃脱MRCD的眼睛!


图片欣赏




更新日志
  • v1.0
    • 初次发布
  • v1.1
    • 添加浆果丛支持
  • v1.2
    • 现在AEC可以碰撞实体了
    • 一些命令改进
  • v1.2.1
    • 现在AEC将会传送到被碰撞实体的边界上了
  • v1.2.2
    • bug修复:触碰完整方块x+,y+和z+面的AEC将会停留在碰撞前的一个方块内
    • 例子函数经过了一些修改
  • v2.0
    • 更名为超精准射线碰撞检测器(Minecraft Ray Collision Detector)
    • bug修复:蘑菇被当做完整方块
    • bug修复:子弹可穿过竹子
    • 添加花花、竹笋和竹子的支持
  • v2.1
    • 支持1.16
    • 添加火、灵魂火、灵魂灯笼、灵魂火把、灵魂营火、锁链、下界藤蔓、下界草、下界菌的支持
    • 为红石线、墙添加新的方块状态
    • bug修复:藤蔓被当作完整方块



版本对应
  • 数据包版本2.1 对应游戏版本1.16
  • 数据包版本2.0 对应游戏版本1.14.4, 1.15.2


特别鸣谢




原理

将药水云的三个计分板视为位移向量,将MC中的方块每个面视为平面,计算线面交点,若交点坐标在某一范围内,则发生碰撞。具体实现如下:

药水云的位移向量是:(#total_x, #total_y, #total_z)

首先获取当前位置的坐标,并把它转换成方块坐标,保存到(#block_x, #block_y, #block_z)里。(也就是以所在方块xyz值最小那个点为原点的坐标)
  1. scoreboard players set #const_1000 mrcd_system 1000
  2. execute store result score #block_x mrcd_system run data get entity @s Pos[0] 1000
  3. execute store result score #block_y mrcd_system run data get entity @s Pos[1] 1000
  4. execute store result score #block_z mrcd_system run data get entity @s Pos[2] 1000
  5. scoreboard players operation #block_x mrcd_system %= #const_1000 mrcd_system
  6. scoreboard players operation #block_y mrcd_system %= #const_1000 mrcd_system
  7. scoreboard players operation #block_z mrcd_system %= #const_1000 mrcd_system
复制代码

位移向量转化为空间直线方程,用点向式:(x-#block_x)/#total_x=(y-#block_y)/#total_y=(z-#block_z)/#total_z。
这里以x=0平面为例,平面方程为x=0,带入可解得:
y=(-#block_x)#total_y/#total_x+#block_y
z=(-#block_x)#total_z/#total_x+#block_z
用命令表示就是这样:
  1. # y
  2. scoreboard players set #target_y mrcd_system 0
  3. scoreboard players operation #target_y mrcd_system -= #block_x mrcd_system
  4. scoreboard players operation #target_y mrcd_system *= @s mrcd_y0
  5. scoreboard players operation #target_y mrcd_system /= @s mrcd_x0
  6. scoreboard players operation #target_y mrcd_system += #block_y mrcd_system
  7. # z
  8. scoreboard players set #target_z mrcd_system 0
  9. scoreboard players operation #target_z mrcd_system -= #block_x mrcd_system
  10. scoreboard players operation #target_z mrcd_system *= @s mrcd_z0
  11. scoreboard players operation #target_z mrcd_system /= @s mrcd_x0
  12. scoreboard players operation #target_z mrcd_system += #block_z mrcd_system
复制代码

之后进行判定,若yz坐标值均在0-1000范围内,则说明发生碰撞。
  1. execute if score #target_y mrcd_system matches 0..1000 if score #target_z mrcd_system matches 0..1000 run tag @s add mrcd_touch_edge
复制代码

具体数据包内对以上过程进行了简化和整合,此处特别感谢SPGoding提出的简化想法!让我不至于建模到头凸。


翻阅Minecraft源码,得到坐标偏移计算代码如下(经过简化):
  1. public Vec3d getOffsetPos(BlockState state, BlockView view, BlockPos blockPos) {
  2.     long long6 = MathHelper.hashCode(blockPos.getX(), 0, blockPos.getZ());
  3.     return new Vec3d(((long6 & 0xFL) / 15.0f - 0.5) * 0.5, 0.0, ((long6 >> 8 & 0xFL) / 15.0f - 0.5) * 0.5);
  4. }

  5. public static long hashCode(int x, int y, int z) {
  6.     long long4 = (long)(x * 3129871) ^ z * 116129781L ^ (long)y;
  7.     long4 = long4 * long4 * 42317861L + long4 * 11L;
  8.     return long4 >> 16;
  9. }
复制代码

其中hashcode计算涉及到long类型的乘、加和异或运算。但注意到,getOffsetPos函数调用hashcode时,仅使用到了hashcode的低16位。而在hashcode函数最后,又将hash运算结果右移了16位。因此,实际只需运算结果的低32位。又因为乘、加和异或运算数据高位对低位没有影响,因此不用另写大数计算的方法,直接调用计分板运算即可。
这里最关键问题是两个int类型的异或,这是消耗命令数最多的地方,我消耗了300+条命令,不知道有没有简便方法[groupid=546]Command Block Logic[/groupid]