Skip to main content
Version: 1.21.4

数据与网络(Data and Networking)

一个没有数据的实体(entity)几乎毫无用处,因此,在实体上存储数据是非常关键的。所有实体都会存储一些默认数据,比如它们的类型(type)和位置(position)。本文将讲解如何为你的实体添加自定义数据,以及如何同步这些数据。

最简单的添加数据的方式,就是在你的 Entity 类中添加一个字段(field)。你可以随意操作这些数据。然而,一旦你需要同步这些数据,这种方式就会变得非常繁琐。这是因为大多数实体逻辑只在服务器端执行,只有在特定情况下(取决于 EntityTypeclientUpdateInterval 值)才会向客户端发送更新;这也是当服务器 tick 速度过慢时,实体出现明显“卡顿”的原因。

因此,原版 Minecraft 提供了一些专门的系统来协助数据同步,每种系统都有其特定的用途。当然,你也始终可以在需要时,发送自定义数据

SynchedEntityData

SynchedEntityData 是一个用于在运行时存储值并通过网络同步的系统。它被拆分为三个类:

  • EntityDataSerializer 实际上是 StreamCodec 的包装器。
    • Minecraft 使用一个硬编码的序列化器(serializer)映射。NeoForge 将这个映射转换成了一个注册表(registry),这意味着如果你想添加新的 EntityDataSerializer,必须通过 注册 的方式添加。
    • Minecraft 在 EntityDataSerializers 类中定义了各种默认的 EntityDataSerializer
  • EntityDataAccessor 由实体持有,用于获取和设置数据值。
  • SynchedEntityData 本身持有实体的所有 EntityDataAccessor,并在需要时自动调用 EntityDataSerializer 来同步数据。

开始使用时,需要在你的实体类中创建一个 EntityDataAccessor

public class MyEntity extends Entity {
// 泛型类型必须与下面第二个参数的类型一致。
public static final EntityDataAccessor<Integer> MY_DATA =
SynchedEntityData.defineId(
// 实体的类。
MyEntity.class,
// 实体数据访问器类型。
EntityDataSerializers.INT
);
}
danger

虽然编译器允许你在 SynchedEntityData#defineId() 的第一个参数中使用非所属类作为类型,但这样做极易导致难以调试的问题,因此应当尽量避免。(这同样适用于通过 mixin 或类似方法添加字段的情况。)

接下来,我们需要在 defineSynchedData 方法中定义默认值,示例如下:

public class MyEntity extends Entity {
public static final EntityDataAccessor<Integer> MY_DATA = SynchedEntityData.defineId(MyEntity.class, EntityDataSerializers.INT);

@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
// 我们的默认值是零。
builder.define(MY_DATA, 0);
}
}

最后,我们可以像下面这样获取和设置实体数据(假设你在 MyEntity 的某个方法中):

int data = this.getEntityData().get(MY_DATA);
this.getEntityData().set(MY_DATA, 1);

readAdditionalSaveDataaddAdditionalSaveData

这两个方法用于将数据读写到磁盘。它们通过从 NBT 标签 读取/保存你的值来实现,例如:

// 假设类中存在一个 `int data` 变量。
@Override
protected void readAdditionalSaveData(CompoundTag tag) {
this.data = tag.getInt("my_data");
}

@Override
protected void addAdditionalSaveData(CompoundTag tag) {
tag.putInt("my_data", this.data);
}

自定义生成数据(Custom Spawn Data)

在某些情况下,你的实体在客户端生成时需要一些自定义数据,但这些数据在后续不会再发生变化。当遇到这种情况时,你可以让你的实体实现 IEntityWithComplexSpawn 接口,并使用它的两个方法 #writeSpawnData#readSpawnData,将数据写入/读取到网络缓冲区:

@Override
public void writeSpawnData(RegistryFriendlyByteBuf buf) {
buf.writeInt(1234);
}

@Override
public void readSpawnData(RegistryFriendlyByteBuf buf) {
int i = buf.readInt();
}

此外,你也可以在实体生成时发送自定义的数据包。为此,重写 IEntityExtension#sendPairingData,像发送普通数据包一样在此处发送你的数据包:

@Override
public void sendPairingData(ServerPlayer player, Consumer<CustomPacketPayload> packetConsumer) {
// 调用父类方法以获得基础功能。
super.sendPairingData(player, packetConsumer);
// 添加你自己的数据包。
packetConsumer.accept(new MyPacket(...));
}

更多关于自定义网络数据包的信息,请参考 网络通信(Networking)相关文章

数据附件(Data Attachments)

实体现在已经扩展自 AttachmentHolder,因此支持通过 数据附件 存储自定义数据。其主要用途是在非你自己实现的实体(比如 Minecraft 或其他模组添加的实体)上定义自定义数据。更多详情请参阅相关链接文章。

自定义网络消息(Custom Network Messages)

在同步数据时,你也可以选择使用自定义数据包,在需要时发送额外的信息。请参考 网络通信(Networking)相关文章 了解更多内容。