方块状态

方块状态

方块状态用于表达一个方块的属性组合。本页介绍该系统的背景、适用范围和使用方式。

两个布尔属性生成四种不可变 BlockState;setValue 返回对应的另一状态,再由 Level 设置到世界。

原创说明图,依据 Forge 1.21.x 官方文档 绘制。

旧版行为

在 Minecraft 1.7 及更早版本中,需要保存放置或状态数据、但没有方块实体的方块使用 metadata。它是与方块一起保存的额外数字,可表示不同旋转、朝向,甚至完全不同的行为。

但元数据系统既令人困惑又受限制:它只是与方块 ID 一同保存的数字,除了代码注释之外没有含义。例如,实现一个具有朝向、且可位于方块空间上半部或下半部的方块(如楼梯):

switch (meta) {
  case 0: { ... } // 朝南,位于下半部
  case 1: { ... } // 朝南,位于上半部
  case 2: { ... } // 朝北,位于下半部
  case 3: { ... } // 朝北,位于上半部
  // ...等等...
}

数字本身没有含义,只有看到源代码和注释才能知道它们代表什么。

状态的引入

Minecraft 1.8 及以上版本弃用了元数据和方块 ID 系统,最终以方块状态系统取代。该系统将方块属性细节从方块的其他行为中抽象出来。

方块的每个属性由一个 Property<?> 实例描述,例如乐器 EnumProperty<NoteBlockInstrument>、朝向 DirectionProperty、通电状态 Property<Boolean> 等。每个属性的值具有 Property<T> 的类型参数 T 所指定的类型。

一个 Block 和一个从 Property<?> 到对应值的映射可以构成唯一组合,称为 BlockState。原来没有意义的元数据值被更易理解和处理的属性系统取代。例如,朝东且通电或按下的石按钮以前表示为带元数据 9 的 minecraft:stone_button,现在表示为 minecraft:stone_button[facing=east,powered=true]。

正确使用方块状态

BlockState 灵活而强大,但也有局限:状态不可变,所有属性组合在游戏启动时生成。属性及可能值过多会拖慢加载,也会让理解方块逻辑变得困难。

并非所有方块和情况都需要 BlockState;只有最基本的方块属性应放入其中,其他情况更适合使用 BlockEntity 或独立 Block。始终考虑是否确实需要方块状态。

经验原则:如果名称不同,就应当是不同的方块。

例如制作椅子时,椅子的方向应是属性,而不同木材种类应是独立方块。朝东的“橡木椅” oak_chair[facing=east] 不同于朝西的“云杉木椅” spruce_chair[facing=west]。

实现方块状态

在 Block 类中,为每个属性创建或引用 static final Property<?> 对象。你可以自行实现 Property<?>,本文不涉及实现方式。原版提供以下便捷实现:

  • IntegerProperty:实现 Property<Integer>,保存整数;用 IntegerProperty#create(String propertyName, int minimum, int maximum) 创建。
  • BooleanProperty:实现 Property<Boolean>,保存 true 或 false;用 BooleanProperty#create(String propertyName) 创建。
  • EnumProperty<E extends Enum<E>>:实现 Property<E>,取枚举类中的值;用 EnumProperty#create(String propertyName, Class<E> enumClass) 创建。也可以只使用部分枚举值,例如 16 种 DyeColor 中的 4 种,参见 EnumProperty#create 的重载。
  • DirectionProperty:EnumProperty<Direction> 的便捷实现,并提供多个便捷谓词。例如,用 DirectionProperty.create("<name>", Direction.Plane.HORIZONTAL) 表示水平四方向,用 DirectionProperty.create("<name>", Direction.Axis.X) 表示 X 方向。

BlockStateProperties 包含共享的原版属性,应尽可能使用或引用它们,而不是自行创建。

准备好 Property<> 后,在 Block 类中重写 Block#createBlockStateDefinition(StateDefinition$Builder),调用 StateDefinition$Builder#add(...) 并将方块所需的每个 Property<?> 作为参数传入。

每个方块还有自动选择的默认状态。可在构造函数中调用 Block#registerDefaultState(BlockState) 更改它。放置方块时会使用默认状态。以下是 DoorBlock 的例子:

this.registerDefaultState(
  this.stateDefinition.any()
    .setValue(FACING, Direction.NORTH)
    .setValue(OPEN, false)
    .setValue(HINGE, DoorHingeSide.LEFT)
    .setValue(POWERED, false)
    .setValue(HALF, DoubleBlockHalf.LOWER)
);

若要更改放置方块时使用的 BlockState,可重写 Block#getStateForPlacement(BlockPlaceContext),例如根据玩家放置时的位置设置方块方向。

由于 BlockState 不可变,且所有属性组合在游戏启动时生成,调用 BlockState#setValue(Property<T>, T) 只会从 Block 的 StateHolder 请求具有所需值组合的 BlockState。也正因为所有可能状态都在启动时生成,可以而且建议使用引用相等运算符 == 比较两个 BlockState。

使用 BlockState

调用 BlockState#getValue(Property<?>) 并传入属性,可获取属性值。要获取具有不同值的状态,调用 BlockState#setValue(Property<T>, T) 并传入属性及其值。

使用 Level#setBlockAndUpdate(BlockPos, BlockState) 和 Level#getBlockState(BlockPos) 可在世界中放置及获取状态。放置 Block 时,调用 Block#defaultBlockState() 获取默认状态,再按上述方法调用 BlockState#setValue(Property<T>, T) 得到所需状态。

原文:Block States。