本站为非官方 Pylon 文档站,大量内容均为 AI 生成·前往官方文档站● ● ●This is an unofficial Pylon documentation site, most content is AI-generated·Visit Official Docs● ● ●中文开发者交流·加入 QQ 群● ● ●本站为非官方 Pylon 文档站,大量内容均为 AI 生成·前往官方文档站● ● ●This is an unofficial Pylon documentation site, most content is AI-generated·Visit Official Docs● ● ●中文开发者交流·加入 QQ 群● ● ●
Pylon 中文文档
开发文档参考方块

持久化数据

了解如何在 Rebar 方块中存储和加载持久化数据

最后更新时间

持久化数据

由于每个物理方块都有一个对应的实例,您可以在方块类中存储方块数据。

比如下面这个方块,每次右键点击计数器就加 1:

ExampleBlock.java
public class ExampleBlock extends RebarBlock implements RebarInteractBlock {

    private int counter = 0;

    public ExampleBlock(@NotNull Block block, @NotNull BlockCreateContext context) {
        super(block, context);
    }

    public ExampleBlock(@NotNull Block block, @NotNull PersistentDataContainer pdc) {
        super(block, pdc);
    }

    @Override
    public void onInteract(@NotNull PlayerInteractEvent event, @NotNull EventPriority priority) {
        if (event.getAction().isRightClick() && event.getHand() == EquipmentSlot.HAND) {
            counter++;
            event.getPlayer().sendMessage(String.valueOf(counter));
        }
    }
}

目前看起来一切正常,但其实有个隐患:如果方块所在的区块被卸载,计数器就会归零。原因是区块卸载时类的实例会被销毁,而 counter 的值根本没有持久化保存。

为此,我们需要把 counter 的值写入方块的 PersistentDataContainer

PersistentDataContainers

PersistentDataContainer(简称 PDC)是一种简单的持久化数据存储方案。你可以把它理解成类似 JSON 的嵌套键值对结构,每个值都用 NamespacedKey 来标识,PDC 之间还可以互相嵌套。

有关 PDC 的更多信息,请参见Paper 文档

在原版 Minecraft 中,方块是没有 PDC 的,只有实体、区块、物品堆等才有。不过 Rebar 通过底层机制给每个方块都分配了 PDC,让你能在卸载时保存数据、加载时读取数据。

等等,这是怎么做到的? 底层实现上,方块 PDC 实际是保存在区块 PDC 里的。这样做有个很大的好处:Rebar 方块数据始终和区块的其他数据保持同步。 需要注意的是,传给 write 方法和加载构造函数的 PDC 是临时的(transient)——它只在方块被加载或保存的那一瞬间存在。

要在方块的 PDC 中存数据,首先需要一个 key 来标识它:

ExampleBlock.java
public class ExampleBlock extends RebarBlock implements RebarInteractBlock {

    public static final NamespacedKey COUNTER_KEY = new NamespacedKey(ExampleAddon.getInstance(), "counter");

    // ...
}

write 方法里保存数据:

ExampleBlock.java
public class ExampleBlock extends RebarBlock implements RebarInteractBlock {

    // ...

    @Override
    public void write(@NonNull PersistentDataContainer pdc) {
        pdc.set(COUNTER_KEY, RebarSerializers.INTEGER, counter);
    }

    // ...
}

不要假设 write 只在方块卸载时才会被调用 write 可能因为各种原因被触发,不限于方块卸载。如果你需要在方块卸载时执行特定逻辑,请参见 RebarUnloadBlock

在加载构造函数中读取数据:

ExampleBlock.java
public class ExampleBlock extends RebarBlock implements RebarInteractBlock {

    // ...

    public ExampleBlock(@NotNull Block block, @NotNull PersistentDataContainer pdc) {
        super(block, pdc);
        counter = pdc.get(COUNTER_KEY, RebarSerializers.INTEGER);
    }

    // ...
}

合到一起,完整代码如下:

ExampleBlock.java
public class ExampleBlock extends RebarBlock implements RebarInteractBlock {

    public static final NamespacedKey COUNTER_KEY = new NamespacedKey(ExampleAddon.getInstance(), "counter");

    private int counter = 0;

    public ExampleBlock(@NotNull Block block, @NotNull BlockCreateContext context) {
        super(block, context);
    }

    public ExampleBlock(@NotNull Block block, @NotNull PersistentDataContainer pdc) {
        super(block, pdc);
        counter = pdc.get(COUNTER_KEY, RebarSerializers.INTEGER);
    }

    @Override
    public void write(@NonNull PersistentDataContainer pdc) {
        pdc.set(COUNTER_KEY, RebarSerializers.INTEGER, counter);
    }

    @Override
    public void onInteract(@NotNull PlayerInteractEvent event, @NotNull EventPriority priority) {
        if (event.getAction().isRightClick() && event.getHand() == EquipmentSlot.HAND) {
            counter++;
            event.getPlayer().sendMessage(String.valueOf(counter));
        }
    }
}

这样,示例方块就能持久保存计数器的值,重启后也不会丢失。

PersistentDataTypes

上面我们给 setget 传了 PylonSerializers.INTEGER,这就是持久数据类型(PersistentDataType,简称 PDT)。磁盘上只能保存特定类型的数据(你总不能直接把一个 World 对象写进磁盘吧?)。PDT 的作用就是告诉系统:怎么把你的数据类型序列化成基本类型,以及怎么反序列化回来。听起来有点抽象?举个例子就明白了。

Rebar 的 BlockPosition 包含 X、Y、Z 坐标和世界 UUID:

BlockPositionPersistentDataType.kt
class BlockPosition(val worldId: UUID?, val x: Int, val y: Int, val z: Int) {
    // ...
}

假设我们要写一个 PDT 来持久化 BlockPosition。做法是把 X、Y、Z 和 world ID 写入一个新的 PDC,然后返回它:

BlockPositionPersistentDataType.kt
object BlockPositionPersistentDataType : PersistentDataType<PersistentDataContainer, BlockPosition> {

    val worldKey = rebarKey("world")
    val xKey = rebarKey("x")
    val yKey = rebarKey("y")
    val zKey = rebarKey("z")

    // ...

    // 将 `BlockPosition` 转换为 `PersistentDataContainer`
    override fun toPrimitive(complex: BlockPosition, context: PersistentDataAdapterContext): PersistentDataContainer {
        val pdc = context.newPersistentDataContainer() // 创建新 pdc
        pdc.set(xKey, PersistentDataType.INTEGER, complex.x) // 写入 X 坐标
        pdc.set(yKey, PersistentDataType.INTEGER, complex.y) // 写入 Y 坐标
        pdc.set(zKey, PersistentDataType.INTEGER, complex.z) // 写入 Z 坐标
        complex.worldId?.let { pdc.set(worldKey, RebarSerializers.UUID, it) } // 仅当世界存在时写入 world UUID
        return pdc
    }

    // 将 `PersistentDataContainer` 转换为 `BlockPosition`
    override fun fromPrimitive(
        primitive: PersistentDataContainer,
        context: PersistentDataAdapterContext
    ): BlockPosition {
        val x = primitive.get(xKey, PersistentDataType.INTEGER)!! // 读取 X 坐标
        val y = primitive.get(yKey, PersistentDataType.INTEGER)!! // 读取 Y 坐标
        val z = primitive.get(zKey, PersistentDataType.INTEGER)!! // 读取 Z 坐标
        val worldId = primitive.get(worldKey, RebarSerializers.UUID) // 读取 world UUID
        return BlockPosition(worldId, x, y, z)
    }
}

Paper 本身已经提供了不少 PDT,包括 byte、long、byte 数组、list 等。在此基础上,Rebar 又补充了很多实用的序列化器,涵盖 UUID、Set、Map、枚举、物品堆、材料、流体、坐标等等。为了方便使用,所有 Rebar 的 PDT 都可以通过 RebarSerializers 类来访问,Paper 自带的序列化器也一并包含在内。

你可以在这里查看 PDT 完整列表。当然,有需要的话你也可以自己写 PDT。

有关 PDT 的更多信息,请参见Paper 文档

调试

你可以用 Debug Waxed Weathered Cut Copper Stairs 来查看方块的 PDC 内容。执行 /pdc debug 获取物品,然后右键点击一个方块。这会调用方块的 write 方法,把结果 PDC 序列化成可读格式并显示在聊天栏中。

目录