MCBE红石系统运行不完全分析(4)——活塞
SAC_Official
2021年10月17日 13:32
收录于文集
共5篇

0. 介绍

活塞是Minecraft的红石电路系统中极为重要的一个原件。其基本作用是在被红石信号激活时能将方块(或实体)向着特定方向推出。这篇专栏通过逐gt测试、反编译代码、通过特殊手段查看内存数据以及与经验丰富的红石玩家交流(对线)等方式对基岩版的活塞特性做了一个简单的总结。

在这篇专栏中你能看到对以下问题的解答:

  • BE活塞的推出和收回时间到底是多少

  • 为什么BE的活塞更慢

  • 所谓的CD到底存在吗

  • ...

1.  基本前提

在阅读本篇之前请先浏览第二篇

MCBE红石系统运行不完全分析(1)​

MCBE红石系统运行不完全分析(2)​

MCBE红石系统运行不完全分析(3)​

以及务必知晓如下前提

  1. 虽然本篇的所有内容基于1.14.6和1.16.4这两个正式版本,但是自上次(几年前)活塞特性改变之后一直都是成立的,因此也适用于新版。

  2. 时序分析的基本时间单位是gt而不是rt.

2. 活塞及其状态

2.1 玩家的直观感受

在玩家眼中活塞有未伸出,伸出一半以及完全伸出这三个阶段,如下同所示:

玩家对活塞的直观感受是这样的:未收到红石信号的活塞位于第一个状态,收到红石信号的活塞会从第一个状态开始经过第二个状态过渡后最终停留在第三个状态,并将活塞臂方向的方块往外推出一格。在红石信号小时候,活塞会进行相反的过程,如果是粘性活塞还会把方块拉回到原的位置。

2.2 实际的四状态

在游戏内部,活塞有且仅有四个状态:分别为收回,半伸出,伸出,半收回。下文用0、1、2、3四个数字来代表这四个状态。如下图所示:

左下角是收回状态(状态0),左上是半伸出状态(状态1),右上是伸出状态(状态2),右下是半收回状态(状态3).这四个状态的互相转换构成了BE版活塞的所有性质。

3. 活塞的更新

3.1 两部分分别运作

在谈论这个问题之前首先要明确的是,活塞内部参与更新的部分有消费者(Piston consumer)和方块实体(Piston block actor)两个部分。前者负责活塞内部红石信号的更新,后者负责活塞的运动状态(也就是和世界的交互),只发生在模拟距离之内。

并且在同1gt内,方块实体总是先于消费者而更新的。下面的伪代码描述了这个更新顺序:

代码块
JavaScript
自动换行
复制代码
bool isRedstoneTick = false;
void BlockActorTick(){} //更新方块实体

void RedstoneTick(){} //消费者更新在这里面

void LevelTick(){ //游戏更新
    isRedstoneTick = !isRedstoneTick;
    BlockActorTick();
    if(isRedstoneTick){
        RedstoneTick();
    }
}
int main() {
    while(true){ //游戏主循环
        LevelTick();
    }
}
复制成功

活塞消费者属于消费者的一种,它的更新在第二篇中做过详细的解释,这里不再赘述。下面重点来看活塞方块实体的更新。

3.2 活塞方块实体更新

在省去无关部分(如和实体交互)并做了适当简化后,该更新过程分依次为如下两个部分:

  1. 根据当前状态以及红石信号的强度来计算新状态

  2. 根据新状态来更新与世界(及方块)的交互状态

下面分成两小节来分别讲这两个步骤:

3.2.1 新状态的计算

状态更新主要依据下面四条规则(用C代表当前装状态,N代表新状态,S表示红石信号的值,&&表示且):

注意:这里为了方便理解做了一定的简化,mojang这部分判定的代码太乱了,没啥规章。

下面这幅图很好地描述了这一状态循环。

不过请注意,这里仅仅是计算出新状态并设置,不会产生外部行为,因此这些改变暂时不会在游戏中表示出来。

3.2.2 更新与世界的交互状态

当且仅当当前游戏刻为红石刻且新旧状态不一样时该行为才发生。

这一步的目的是根据当前不同的状态来对活塞附着的方块进行处理,行为如下表所示:

MB和方块具体如何转换请看第五节。

4. 时序分析

讲了这么多理论分析,下面就活塞的推出和缩回做一些时序分析。

4.1 推出

下图是活塞的推出过程,现在来做一个简单的解释:

  1. 假设在在红石刻1上活塞收到了激活信号

  2. 在红石刻2的时候活塞检测到自己收到了红石信号,并且当前装状态为0,就计算新状态为1,并在更新推出状态为一般,其附着的方块变成MB

  3. 在下一个非红石刻上,活塞不会做任何检查和动作,状态还是1,MB还是MB,但是这时候客户端已经将活塞渲染成全1推出状态

  4. 在红石刻3上,活塞再次做检测,发现自己收到了红石信号,且状态为1,于是计算新状态为2,并将MB变回方块

这就是一个完整的活塞推出流程,请注意,这个图并不会向左或者向右边偏移1gt,因为实际的状态变化一定发生在红石刻。到这里,你会怎么定义活塞的推出时间呢?

  • 从状态0到状态2所花的时间?

  • 状态1持续的时间?

  • MB存活的时间?

怎么说都有理。因此这里不会定义活塞的推出时间。至于cd也是如此,只要你的定义能够描述这个事实,怎么定义都可以。

4.2 收回

收回的过程和推出完全相反,其示意图如下所示,这里不再赘述。

4.3 推出一半就收回的活塞存在吗

答案是不存在。因为处于状态1或者状态3的活塞在下一个红石刻会无视红石信号变成状态2和0,如果不这样的设计的话,BE的活塞会出现瞬推和瞬收的动画(但是状态变化还是连续的),这样也不好管理MB和方块之间的互相转换。

5. Moving Block的产生与灭亡

方块在被推后还没有复原的状态也是一种方块,在Java版中该方块的id为36,因此被称之为36号方块。而在BE版中这个方块同样存在,但是方块id是250,名称是Moving Block,以下和以上简称为MB。

Mojang在早期版本添加方块的时候特地预留了id为36的位置,但是这个位置最终没有分配给MB,而是被教育版中的0号元素方块所占据。

方块和MB之间的转化没什么要点,这里简单提一下。

5.1 方块转为MB

在活塞由状态0转向状态1以及由状态2转向状态3的时候会触发该行为。

  1. 检查附着在其上的所有无法被推动的方块(如果小麦等),先生成掉落物,范围为1,不会产生任何时运效果,然后移除该方块;

  2. 对于可以被推动的方块,先在新位置上生成MB,然含再移除该方块。原来的方块信息会被存储到MB内部

这里面还有对流动水,大箱子等特殊方块做了一些特殊处理,这里不赘述

5.2 MB转为方块

在活塞由状态1转向状态2以及由状态3转向状态0的时候会触发该行为。

  1. 在新位置上生成方块,替换掉原来的MB

5.3 多方块的移动顺序问题

如果不实现规定好活塞附着的方块的移动顺序问题,就会出现各种拉扯和数据丢失。活塞内部维护了一个需要移动的方块列表,并按照特定的规则对方块进行排序。排序需要依据一个严格的二元比较规则,该规则如下所示:

对于附着的方块A,和B,先计算A到B的位置向量AB,然含给定活塞的推动方向P,如果AB和P的点乘(数量积)大于0,也就是这俩相向量的夹角是锐角,那么B会比A先动,否则A先动,下面有一个简单的示意图:

6. 简单总结

BE的活塞在各方面来看都是简单清晰而明了的。一句话就是在每个红石刻根据红石信号更新自身状态并处理与世界的交互,其自身的有限状态机也是出奇地简单和易于理解。所谓的推出时间、收回时间在不同的定义下有不同的结果。在非红石刻中BE的活塞基本不会做任何更新和改变,除了动画,这也就是为什么玩家感受到的BE的活塞伸出和缩回很慢的原因。

除此之外,在随机的区块更新顺序以及随机的方块实体更新顺序的加持下,BE的活塞之间也没有明显的优先级,随机之下,众生平等。

7. 实例分析

略

8. Credit

  • origin0110

  • _hhhxiao

  • 群内其它玩家