方块(Blocks)
方块是Minecraft世界的基础。它们构成了所有地形、结构和机器。如果您有兴趣制作模组,那么您可能会想添加一些方块。本页将指导您创建方块,并介绍一些可以用它们做的事情。
独一无二的方块(One Block to Rule Them All)
在我们开始之前,重要的是要理解游戏中每种方块都只有一个实例。一个世界由对该方块在不同位置的数千个引用组成。换句话说,同一个方块只是被显示了很多次。
因此,一个方块应该只实例化一次,那就是在注册期间。一旦方块被注册,您就可以根据需要使用的注册引用。
与大多数其他注册表不同,方块可以使用一个特殊版本的DeferredRegister,称为DeferredRegister.Blocks。DeferredRegister.Blocks基本上像一个DeferredRegister<Block>,但有一些细微差别:
- 它们通过
DeferredRegister.createBlocks("yourmodid")创建,而不是常规的DeferredRegister.create(...)方法。 #register返回一个DeferredBlock<T extends Block>,它扩展了DeferredHolder<Block, T>。T是我们正在注册的方块类的类型。- 有一些用于注册方块的辅助方法。更多细节请参见下文。
那么现在,让我们注册我们的方块:
//BLOCKS is a DeferredRegister.Blocks
public static final DeferredBlock<Block> MY_BLOCK = BLOCKS.register("my_block", registryName -> new Block(...));
注册方块后,对新my_block的所有引用都应使用 此常量。例如,如果您想检查给定位置的方块是否为my_block,代码将如下所示:
level.getBlockState(position) // returns the blockstate placed in the given level (world) at the given position
.is(MyBlockRegistrationClass.MY_BLOCK);
这种方法还有一个方便的效果,即block1 == block2可以工作,并且可以替代Java的equals方法(当然,使用equals仍然有效,但因为没有必要,因为它是通过引用比较的)。
不要在注册之外调用new Block()!一旦您这样做,事情可能会并且将会出错:
- 必须在注册表未冻结时创建方块。NeoForge会为您解冻注册表并在之后冻结它们,因此注册是您创建方块的窗口期。
- 如果您尝试在注册表再次冻结时创建和/或注册方块,游戏将崩溃并报告一个
null方块,这可能非常令人困惑。 - 如果您仍然设法拥有一个悬挂的方块实例,游戏在同步和保存时将无法识别它,并将其替换为空气。
创建方块
如前所述,我们首先创建我们的DeferredRegister.Blocks:
public static final DeferredRegister.Blocks BLOCKS = DeferredRegister.createBlocks("yourmodid");
基础方块
对于不需要特殊功能的简单方块(如圆石、木板等),可以直接使用Block类。为此,在注册期间,使用BlockBehaviour.Properties参数实例化Block。这个BlockBehaviour.Properties参数可以使用BlockBehaviour.Properties#of创建,并且可以通过调用其方法进行自定义。其中最重要的方法有:
setId- 设置方块的资源键(Resource Key)。- 这必须在每个方块上设置;否则,将抛出异常。
destroyTime- 确定方块需要被破坏的时间。- 石头的破坏时间为1.5,泥土为0.5,黑曜石为50,基岩为-1(不可破坏)。
explosionResistance- 确定方块的爆炸抗性。- 石头的爆炸抗性为6.0,泥土为0.5,黑曜石为1200,基岩为3,600,000。
sound- 设置方块被敲击、破坏或放置时发出的声音。- 默认值为
SoundType.STONE。更多细节请参阅声音页面。
- 默认值为
lightLevel- 设置方块的光照发射。接受一个带有BlockState参数的函数,返回一个介于0到15之间的值。- 例如,荧石使用
state -> 15,火把使用state -> 14。
- 例如,荧石使用
friction- 设置方块的摩擦系数(光滑度)。- 默认值为0.6。冰使用0.98。
因此,一个简单的实现将如下所示:
//BLOCKS is a DeferredRegister.Blocks
public static final DeferredBlock<Block> MY_BETTER_BLOCK = BLOCKS.register(
"my_better_block",
registryName -> new Block(BlockBehaviour.Properties.of()
.setId(ResourceKey.create(Registries.BLOCK, registryName))
.destroyTime(2.0f)
.explosionResistance(10.0f)
.sound(SoundType.GRAVEL)
.lightLevel(state -> 7)
));
更多文档,请参见BlockBehaviour.Properties的源代码。更多示例,或查看Minecraft使用的值,请查看Blocks类。
重要的是要理解,世界中的方块与物品栏中的方块不是同一个东西。在物品栏中看起来像方块的实际上是一个BlockItem,一种特殊的物品类型,使用时放置一个方块。这也意味着诸如创造标签页或最大堆叠数之类的事情由相应的BlockItem处理。
BlockItem必须与方块分开注册。这是因为方块不一定需要物品,例如如果它不打算被收集(例如火就是这种情况)。
更多功能
直接使用Block只允许非常基础的方块。如果您想添加功能,比如玩家交互或不同的碰撞箱,需要 一个扩展Block的自定义类。Block类有许多可以重写以执行不同操作的方法;有关更多信息,请参见Block、BlockBehaviour和IBlockExtension类。另请参阅下面的使用方块部分,了解方块最常见的一些用例。
如果您想制作具有不同变体的方块(想想有底部、顶部和双变体的台阶),您应该使用方块状态。最后,如果您想要一个存储额外数据的方块(想想存储其物品栏的箱子),应使用方块实体。这里的经验法则是,如果您有有限且数量合理的状态(最多几百个状态),请使用方块状态;如果您有无限或接近无限的状态数量,请使用方块实体。
方块类型
方块类型是用于序列化和反序列化方块对象的MapCodecs。这个MapCodec通过BlockBehaviour#codec设置,并注册到方块类型注册表中。目前,它的唯一用途是在生成方块列表报告时使用。应为Block的每个子类创建一个方块类型。例如,FlowerBlock#CODEC代表大多数花的方块类型,而其子类WitherRoseBlock具有单独的方块类型。
如果方块子类只接受BlockBehaviour.Properties,则可以使用BlockBehaviour#simpleCodec来创建MapCodec。
// For some block subclass
public class SimpleBlock extends Block {
public SimpleBlock(BlockBehavior.Properties properties) {
// ...
}
@Override
public MapCodec<SimpleBlock> codec() {
return SIMPLE_CODEC.get();
}
}
// In some registration class
public static final DeferredRegister<MapCodec<? extends Block>> REGISTRAR = DeferredRegister.create(BuiltInRegistries.BLOCK_TYPE, "yourmodid");
public static final Supplier<MapCodec<SimpleBlock>> SIMPLE_CODEC = REGISTRAR.register(
"simple",
() -> BlockBehaviour.simpleCodec(SimpleBlock::new)
);
如果方块子类包含更多参数,则应使用RecordCodecBuilder#mapCodec来创建MapCodec,为BlockBehaviour.Properties参数传递BlockBehaviour#propertiesCodec。
// For some block subclass
public class ComplexBlock extends Block {
public ComplexBlock(int value, BlockBehavior.Properties properties) {
// ...
}
@Override
public MapCodec<ComplexBlock> codec() {
return COMPLEX_CODEC.get();
}
public int getValue() {
return this.value;
}
}
// In some registration class
public static final DeferredRegister<MapCodec<? extends Block>> REGISTRAR = DeferredRegister.create(BuiltInRegistries.BLOCK_TYPE, "yourmodid");
public static final Supplier<MapCodec<ComplexBlock>> COMPLEX_CODEC = REGISTRAR.register(
"simple",
() -> RecordCodecBuilder.mapCodec(instance ->
instance.group(
Codec.INT.fieldOf("value").forGetter(ComplexBlock::getValue),
BlockBehaviour.propertiesCodec() // represents the BlockBehavior.Properties parameter
).apply(instance, ComplexBlock::new)
)
);
尽管方块类型目前基本上未被使用,但随着Mojang继续朝着以编解码器为中心的结构发展,预计它将来会变得更加重要。
DeferredRegister.Blocks 辅助方法
我们已经讨论了如何创建DeferredRegister.Blocks上文,以及它返回DeferredBlock。现在,让我们看看这个专门的DeferredRegister还提供了哪些其他实用工具。让我们从#registerBlock开始:
public static final DeferredRegister.Blocks BLOCKS = DeferredRegister.createBlocks("yourmodid");
public static final DeferredBlock<Block> EXAMPLE_BLOCK = BLOCKS.register(
"example_block", registryName -> new Block(
BlockBehaviour.Properties.of()
// The ID must be set on the block
.setId(ResourceKey.create(Registries.BLOCK, registryName))
)
);
// Same as above, except that the block properties are constructed eagerly.
// setId is also called internally on the properties object.
public static final DeferredBlock<Block> EXAMPLE_BLOCK = BLOCKS.registerBlock(
"example_block",
Block::new, // The factory that the properties will be passed into.
BlockBehaviour.Properties.of() // The properties to use.
);