方块状态
方块状态用于表达一个方块的属性组合。本页介绍该系统的背景、适用范围和使用方式。
原创说明图,依据 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。