概述

我们知道,除了一些几乎不会变动的固有数据之外,实体、包括生物也有一些会需要频繁变动的数据,包括一些运动坐标等。

实体的运动坐标的修改非常简单,Minecraft对其相关的修改方式也非常成熟,教程就不再说明了。不过,实体生物还有一些数据,不仅会频繁变动,甚至还需要客户端和服务端之前的频繁同步,包括生物的血量、生物的特殊名称等。除此之外,我们还有一些特殊实体生物对应的数据,比如羊身上的颜色、猪身上是否有着马鞍等,也满足会频繁变动和需要服务端和客户端的同步两条。不过还好,Minecraft本身给我们提供了一种名为DataWatcher的数据同步机制,会尽可能地保证客户端和服务端的数据同步。本部分教程就会讲述如何使用DataWatcher机制。

我们很多时候为生物添加的数据,不仅需要同步,还需要保存入地图存档中,也就是数据存储,这就不得不提到Minecraft提供的一种数据序列化的方式:NBT标签。本部分会简要介绍NBT标签,与如何在特定的地方自定义数据的读写,以完成一些属性的数据存储操作。

自动同步的数据

因为在Minecraft地图上的每一个实体,都是一个Entity类的子类的实例,所以说看起来添加一个实体的属性并保存在内存中,实在是再简单不过的一件事了,只要在子类中加上存储这一属性的字段就可以了。不过当这个属性涉及到客户端和服务端的数据同步的时候,事情就变得有一些复杂了,需要做的可能就是在每一次实体更新的时候,产生一个网络数据包,并通过相应的方式把这个包发送出去。

不过还好,每一个Entity类的实例中,都有一个DataWatcher类的实例。它会在于其中注册的数据产生更改的时候,自动在下一次更新的时候产生网络数据包通知数据变化。我们通过dataWatcher字段或者getDataWatcher方法获取到这个DataWatcher。DataWatcher一共有32个频段,每个频段可以存放八种数据,也就是八个类的实例,分别是:ByteShortIntegerFloatStringItemStackBlockPos、和Rotation

Entity类本身占据了DataWatcher的0-4共五个频段,EntityLiving类相较Entity类多占据了6-9和15五个频段。一些子类往往还占据了12等频段,不过可以肯定的是,除非作为表示特殊动物的类,大于等于16的频段都没有被占据着。一般情况下,包括Minecraft原版和Mod,使用DataWatcher的频段都是从16开始的。这里,我们添加一个表示鸡翅膀转动速度档数的属性,因为表示鸡的EntityChicken类没有多使用任何频段,故占用DataWatcher的第16个频段存放这个属性。

我们覆写实体的entityInit方法,并在其中使用DataWatcher类的addObject方法注册一个DataWatcher的频道还有默认值,如果这个频道在之前已被注册的话,那么Minecraft会抛出一个异常以报错:

src/main/java/com/github/ustc_zzzz/fmltutor/entity/EntityGoldenChicken.java(部分):

    @Override
    protected void entityInit()
    {
        super.entityInit();
        this.dataWatcher.addObject(16, new Byte((byte) 0));
    }

我们通过DataWatcher类的updateObject方法,以修改其中存储的数据的值,并且通知DataWatcher类的实例同步:

src/main/java/com/github/ustc_zzzz/fmltutor/entity/EntityGoldenChicken.java(部分):

    @Override
    public boolean interact(EntityPlayer player)
    {
        if (!super.interact(player))
        {
            byte b = this.dataWatcher.getWatchableObjectByte(16);
            this.dataWatcher.updateObject(16, new Byte((byte) ((b + 1) % 5)));
        }
        return true;
    }

这里作为示例,作者覆写了interact方法以通过右键实体的方式使我们监听的数据在0-5之间轮换。

DataWatcher类有一系列以getWatchableObject开头的方法,为了获取到其中的数据,我们应当选择对应的方法来完成对存储的数据的获取,本部分教程为了突出对数据的同步,刻意选择了一个在客户端才会用到的属性以演示数据同步:

src/main/java/com/github/ustc_zzzz/fmltutor/client/entity/model/ModelGoldenChicken.java(部分):

    protected float getWingSpeed(Entity entity, float rotateFloat)
    {
        float wingSpeed = (float) (((EntityGoldenChicken) entity).getEntityAttribute(EntityGoldenChicken.wingSpeed)
                .getAttributeValue() * rotateFloat);
        switch (entity.getDataWatcher().getWatchableObjectByte(16))
        {
        case 4:
            return wingSpeed / 2;
        case 3:
            return wingSpeed / 4;
        case 2:
            return wingSpeed * 4;
        case 1:
            return wingSpeed * 2;
        case 0:
        default:
            return wingSpeed;
        }
    }

现在打开游戏,右键点击这只黄金鸡,我们就可以看到翅膀扇动速度的变化了。

DataWatcher固然方便,但是我们也不能完全依赖它进行数据同步。那么什么时候不能使用DataWatcher呢?

一方面,DataWatcher致力于尽可能地保证数据的同步,所以不适宜存放大数据和涉及到安全性的数据,比如一个村民的交易数据就不应该使用DataWatcher同步,因为它同时符合上述两点。另一方面,DataWatcher只有32个频道,所以不适宜存放已有实体的附加数据,比如很多Mod都具有为玩家添加属性的功能,这就不适宜使用DataWatcher同步,以免发生冲突。后面的部分会有讲到如何存放实体的附加数据和自定义网络数据包,不过这里就不作讲述了。

数据存储

细心的读者可能注意到了,虽然现在调整黄金鸡的档位可行了,不过在退出游戏重新进入的时候,就会出现档位回归默认的情况。这就是因为我们没有把新添加的数据进行存储入地图的操作。不过说来,这个操作其实很简单,在这之前,我们先简单讲讲Minecraft序列化数据的方式:NBT标签。

NBT全称Named Binary Tag,是Minecraft专用的一种数据格式,其格式和JSON类似,以一棵树的形式显示,树根是一个我们称为Compound的结构,其功能类似于JSON的字典,存放着若干组键值对,在源代码中对应的就是NBTTagCompound类。键值对的索引自然是字符串,而值可以有很多种,包括我们熟悉的Byte(8-bit)、Short(16-bit)、Int(32-bit)、Long(64-bit)、Float(IEEE标准)、Double(IEEE标准),还有一些复杂的如Byte_Array(字节流)、String(UTF-8格式字符串)、List(一串包含着相同类型数据的列表)、Compound(一串包含着键值对的字典)、Int_Array(32-bit整数数组)等。

NBT标签的数据存储极为复杂,不过幸运的是,在源代码中我们不需要考虑那么多,只需要使用NBTTagCompound的一串set开头的设置方法、和一串get开头的获取方法就可以了。这里教程只是简要运用了一下NBT标签,更为详细的了解,可以参见这里

Entity类提供了两个方法,分别名为writeEntityToNBTreadEntityFromNBT,分别用于将数据写入NBT标签和读出NBT标签。我们覆写这两个方法,并加入我们需要存储的数据,就大功告成了:

src/main/java/com/github/ustc_zzzz/fmltutor/entity/EntityGoldenChicken.java(部分):

    @Override
    public void writeEntityToNBT(NBTTagCompound tagCompound)
    {
        super.writeEntityToNBT(tagCompound);
        tagCompound.setByte("WingSpeedMultiplier", this.dataWatcher.getWatchableObjectByte(16));
    }

    @Override
    public void readEntityFromNBT(NBTTagCompound tagCompund)
    {
        super.readEntityFromNBT(tagCompund);
        this.dataWatcher.updateObject(16, tagCompund.getByte("WingSpeedMultiplier"));
    }

Minecraft会在适当的地方调用这两个方法,以实现数据的存储。

打开游戏试试吧~

results matching ""

    No results matching ""