我的世界幽灵方块详解 关于幽灵方块的研究分析。那下面给大家介绍的则是我的世界游戏中的幽灵方块哦~那下面就带啊大家详细的认识一下什么是幽灵方块吧!有想要了解的玩家点我查看吧!
游戏园我的世界官方群:325049520 或 256070479 欢迎各路喜爱我的世界的小伙伴们加入讨论!
玩服务器的小伙伴们可以加入:141931866 群一起联机玩游戏哦!
如果你是腐竹的话可以给我们投稿你的服务器哦~投稿地址:点我进入
如果你有心仪的作品或者心得分享的话,欢迎来游戏园投稿,大家可以点击>>>投稿<<<进行投稿哦~ 有奖品哦~
一、什么是幽灵方块
mc有两个部分,客户端和服务端。客户端和服务端共同完成了游戏的算法。服务端和客户端各有分工:服务端需要计算实体的行为,客户端需要渲染GUI。每个世界都会有一个服务端作为终端,发送Packet(包)来和客户端交流。至于Packet怎么实现,是通过网络(多人游戏)还是通过本地(单人游戏)来交流就不是游戏这个层面需要管的事情了。为了节约IO资源,mc需要尽可能地减少之间的通讯。于是客户端和服务端分别进行运算,只在必须的情况下才会传递信息,进行同步。幽灵方块就是在同一位置上,服务端和客户端不同的方块。为了节约版面,我们把幽灵方块称为 G方块,客户端称为C,服务端称为S 。幽灵方块在客户端和服务端的值分别为G方块的C值和S值。
二、幽灵方块的产生
G方块四处存在。在任意一个方块同步的包没有发送到C时,这都是一个G方块。不过有时,方块更新并不会产生一个方块同步。这点是程序员可控的。
制造幽灵方块会有很多可能的方法,只是许多方法我们并不知道而已。最简单粗暴的如写mod,强制在S设置一个不同步到C的方块,或者直接在C设置一个方块,都可以完成这一点。
利用活塞是最普遍的方法了。为了节约资源,S的活塞并不同步方块更新,而是同步一个叫做方块事件的东西,让C自己完成推出的计算。在这个计算过程中可能会发生时序等的一些差异,导致幽灵方块的产生。这也是我们将会重点分析的一个部分。
另外,我猜想通过区块加载的某些方式可以使S认为这个方块更新不需要同步到C。不过还没有实验。
在研究活塞的行为是如何导致G方块产生的,我们需要先理解游戏的微观时序和活塞的微观行为。
S端每tick更新的时间轴如下:
Next Tick Entry(NTE)
PlayerManager Update(PMU)
这个是我新加的项目,主要负责区块和同步的管理等,卸载区块就是在这里执行的。S端在这一tick中,每个区块(Chunk)需要进行方块同步的位置都会按照顺序添加在一个数组里,在PMU统一发送Packet给C。
语文老师:这里的“数组”是这篇文章的一个伏笔!高!实在是高!
注:不要在意上一句话...老实说我觉得PlayerManager这名字起的不好
Block Event(BE)
Entity Update 为了不要和工业mod里的电力单位(Electric Unit)搞混,就不要简称了...(雾)老实说我觉得这个成员在幽灵方块的生成中起不到影响,不过为了方便以后修改,就加上了。
Tile Entity Update(TEU)在这里,由于我打“Tile Entity”和“Tileentity”都看着不顺眼,于是就玩个简称吧..用于方块存放特殊数据,或者在每个循环执行任务。被活塞推出的方块会变成36号方块,其中存放了一个TE。在每个TEU,应该被推动完成了的36号方块就会变成原方块。
没错,这5个重要的成员就是我们研究G方块的生成必不可少的元素了。
接下来是活塞的具体行为。由于不好描述,本文将由伪代码的形式形容。对于36号方块(推动中的方块),清除的意思是把它变成正在推的那个方块。
checkForMove(当活塞收到更新时调用,只由S执行){
判断自己是否有信号和自己的状态,如果是有信号的收回态,调用addblockevent。如果是无信号的推出态,先设置自己为收回态(更新,同步,再调用addblockevent。
}
addblockevent{
在一个列表里按顺序添加方块事件,但不会重复添加相同的事件(推出和收回不算相同)
}
tick(S世界主循环节选){
依次调用对应方块的onblockevent received,如果它返回了true,则把一个表示blockevent的包发送到C,让C调用onblockeventreceived
}
onblockevent received{
只在S执行{
如果要伸出但是没信号,返回false
如果要收回但是有信号,返回false,并把状态设置为推出态(同步到C)
}
若要推出{
调用domove,如果domove返回了false,则返回false
设置为推出态(同步),再返回true
}
若要收回{
清除原来活塞臂的位置的方块
把自身设置为36号方块(同步)
如果这是粘性活塞{
如果将会拉回的方块是36号方块,把它清除掉
如果前面的方块是可以移动的,调用domove
}否则,把活塞臂的位置设为空气(同步)
}
返回true
}
domove{
如果要收回的话,把原来的活塞臂位置设为空气(同步)
计算将要移动的方块和将要掉落的方块。如果不能移动,返回false
把所有将要掉落的方块设置为空气(同步),然后掉落。
把所有将要移动的方块设为空气(同步),即将移动到的位置设为一个36号方块(不同步)
如果要推出的话,把活塞的前方设置为一个包含活塞臂的36号方块(不同步)
}
既然这是一个bug,我看了半天代码也没有任何思路...于是我们转变思路,采用debug的方式,从幽灵方块生成器来入手!
这是Logdotzip的幽灵方块生成器,是最早的,也是最直观的一个。当拉杆输出下降沿时,会把钻石块以幽灵方块的形式推出,就像这样:
不过这个东西看上去还是有点复杂。我们可以把中间这个机器拆分成左右两个部分:
左边是G方块生成器的核心,右面只是决定了推出哪个G方块而已。
我们发现,当拉下左边的装置的拉杆时,一个G活塞被推了出去。
而右面的装置所做的事情呢,就是在推出两个G后(G1:C=钻石块,S=空气,G2:C=活塞,S=钻石块)把G2收回了,用了一个方法(这个方法以后再谈)消除了G2,于是只剩下G1了。所以,我们的关注点转移到了左边的装置上。
接下来我们就是要解析左边的这个电路
根据前文的内容,我们梳理出了这样的S时间轴。其中把向上的普通活塞称为P1,对着粘液块的活塞称为P2,推出称为+,收回称为-
以下是电路对于活塞的操作:
P1退激活(NTE)
{P1激活(NTE)
{P2激活(NTE)
注意,P1和P2的激活顺序是不定的,但显然这并不影响幽灵方块的产生。
接下来我们要把这个时间轴转化为活塞的行为:
P1:EXTENDED=false(NTE)
P1:addBlockEvent -(NTE)
{P2:addBlockEvent +(NTE)
{P1:addBlockEvent +(NTE)
P1:receiveBlockEvent(BE)
P1:EXTENDED=true(BE)
{P2:推出(BE)
{P1:尝试推出自己的活塞臂,但是并未成功(搞笑)(BE)
我们发现,addBlockEvent的唯一影响就是收到BlockEvent的顺序,所以它是不需要表示的。最后那个搞笑的P1也是没有意义的。于是,我们精简成了这样:
P1:EXTENDED=false(NTE)
P1:receiveBlockEvent(BE)
P1:EXTENDED=true(BE)
P2:推出
可以发现,在S,P1设置EXTENDED=true的时序永远比P2的推出要早。所以在S中P1不会被推出。
那么,C是怎么操作的呢?我们将以S的时序为主线,将它向C发送的Packet标在时间轴上:
P1:Extended=false(NTE)
sendPacket BlockChange:P1.Extended=false(PMU)
P1:receiveBlockEvent(BE)
P1:Extended=true(BE)
P2:receiveBlockEvent(BE)(上一层楼忘写了)
P2:推出(BE)
sendPacket BlockEvent:P2推出(BE)
sendPacket BlockChange:P1.Extended=true(PMU)
可以发现,因为当P1的Extended=true时,已经过了那个tick的PMU了,只好等到下一tick发送。所以P2的推出事件就抢到了P1的前面,从而先发制人地把P1推出了。
那么粘液块是干什么的呢?C想要让P2推出,就需要让S的P2成功完成一次推出。而能黏住P1却不会被P1阻挡的方法,只有利用史莱姆块了。
这个时间轴就是我们制作幽灵方块生成器的指明灯。只要满足这个时间轴就可以产生幽灵方块,不需要我们实际操作才能判断它是否能产生了。比如说,这个装置就是原生成器一个小小的变体:
关于幽灵方块的生成原理就到这里了,虽然看上去简单,不知道烧了我多少脑细胞啊...
三.幽灵方块的清除
G方块的清除的方法极其多样。只要是服务端在原方块坐标处发送了一个blockchange包,G方块就会被同步掉。水结冰,作物的生长都会产生同步到C的方块更新。在活塞门中,最常见的设计是水流。
下图的内容是甘蔗替换掉了G方块。
不过,除了放置方块产生的包,还会有一些方法来引起同步.....
1.区块同步。杨月华发现,同时推出多个G方块会清除区块内的所有G方块。于是我被启发而做了下面的实验:通过打指令在一个区块中填充了64个石头,然后G方块消失了。
当在每次pmu同步一个区块中的方块时,如果需要同步的方块到达64个,则会产生一次区块同步,同步所有区块内的方块。G方块就这么消失了。这也是在文章开头说"数组"的原因。这个数组的长度就是64!
不过,仅仅是64个方块,就引起了65536个方块的巨大同步,是不是太浪费了点?所以,minecraft把每16个方块分成一份,我们习惯称其为light chunk(lc)。每次只同步这个lc里4096个方块的内容。于是,y=16产生的64个方块改变就不能影响到y=0的G方块了。