实体数据与网络同步
没有数据的实体几乎没有用途,因此为实体保存数据至关重要。所有实体都会保存一些默认数据,例如类型和位置。本文介绍如何添加自己的数据,以及如何同步这些数据。
最简单的方式是在 Entity 类中添加字段,然后按需读写。但一旦需要同步,就会变得麻烦:大多数实体逻辑仅在服务端运行,只有每隔一段时间才会向客户端发送更新,间隔由 EntityType 的 clientUpdateInterval 决定。这也是服务端每秒游戏刻数过低时,实体容易出现明显卡顿的原因。
为此,原版提供了几个各有用途的系统。必要时,也始终可以发送自定义数据。
SynchedEntityData
SynchedEntityData 用于在运行时保存值,并通过网络同步。它分为三个类:
EntityDataSerializer基本上是对StreamCodec的封装。- Minecraft 使用硬编码的序列化器映射;NeoForge 将其转换成了注册表,因此新增
EntityDataSerializer时必须进行注册。 - Minecraft 在
EntityDataSerializers中定义了多种默认序列化器。
- Minecraft 使用硬编码的序列化器映射;NeoForge 将其转换成了注册表,因此新增
EntityDataAccessor由实体持有,用于获取和设置数据值。SynchedEntityData本身保存实体的所有EntityDataAccessor,并按需自动调用EntityDataSerializer同步数值。
首先,在实体类中创建 EntityDataAccessor:
public class MyEntity extends Entity {
// The generic type must match the one of the second parameter below.
public static final EntityDataAccessor<Integer> MY_DATA =
SynchedEntityData.defineId(
// The class of the entity.
MyEntity.class,
// The entity data accessor type.
EntityDataSerializers.INT
);
}
重要警告 编译器虽然允许在
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) {
// Our default value is zero.
builder.define(MY_DATA, 0);
}
}
最后,可以按以下方式获取和设置实体数据,假设当前代码位于 MyEntity 的方法中:
int data = this.getEntityData().get(MY_DATA);
this.getEntityData().set(MY_DATA, 1);
readAdditionalSaveData 与 addAdditionalSaveData
这两个方法用于从磁盘读取和向磁盘写入数据,具体通过值输入输出加载和保存数值:
// Assume that an `int data` exists in the class.
@Override
protected void readAdditionalSaveData(ValueInput input) {
this.data = input.getIntOr("my_data", 0);
}
@Override
protected void addAdditionalSaveData(ValueOutput output) {
output.putInt("my_data", this.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) {
// Call super for some base functionality.
super.sendPairingData(player, packetConsumer);
// Add your own packets.
packetConsumer.accept(new MyPacket(...));
}
自定义网络数据包的更多信息请参阅网络通信文档。
数据附件
实体通过补丁继承了 AttachmentHolder,因此支持使用数据附件保存数据。它主要用于为并非由自己定义的实体添加自定义数据,例如 Minecraft 或其他 Mod 添加的实体。更多信息请参阅链接文档。
自定义网络消息
同步时,也可以按需使用自定义数据包发送附加信息。更多信息请参阅网络通信文档。
来源: NeoForged/Documentation(26.1)。中文翻译依据提交
4a680cdd4d15。