方块(Blocks)
方块是Minecraft世界的核心。它们构成了所有的地形、结构和机器。如果你对制作模组感兴趣,那么你很可能想要添加一些方块。本页将指导你创建方块,以及你可以用它们做的一些事情。
一统万方的方块(One Block to Rule Them All)
在开始之前,理解游戏中每种方块只有一个实例是非常重要的。一个世界由成千上万个在不同位置引用该方块的引用组成。换句话说,同一个方块只是被显示了很多次。
因此,一个方块应该只被实例化一次,那就是在[注册(registration)]期间。方块注册后 ,你可以根据需要使用的注册引用。
与大多数其他注册表不同,方块可以使用一个专门版本的DeferredRegister,称为DeferredRegister.Blocks。DeferredRegister.Blocks基本上类似于DeferredRegister<Block>,但有一些细微差别:
- 它们通过
DeferredRegister.createBlocks("yourmodid")创建,而不是常规的DeferredRegister.create(...)方法。 #register返回一个DeferredBlock<T extends Block>,它扩展了DeferredHolder<Block, T>。T是我们正在注册的方块类的类型。- 有一些用于注册方块的辅助方法。更多细节请参见[下文(below)]。
那么现在,让我们注册我们的方块:
//BLOCKS是一个DeferredRegister.Blocks
public static final DeferredBlock<Block> MY_BLOCK = BLOCKS.register("my_block", registryName -> new Block(...));
注册方块后,所有对新my_block的引用都应使用此常量。例如,如果你想检查给定位置的方块是否为my_block,代码将如下所示:
level.getBlockState(position) // 返回给定世界(level)中给定位置的方块状态(blockstate)
.is(MyBlockRegistrationClass.MY_BLOCK);
这种方法还有一个便利的效果,即block1 == block2可以工作,并且可以用来替代Java的equals方法(当然,使用equals仍然有效,但由于它是按引用比较的,所以毫无意义)。
不要在注册之外调用new Block()!一旦你这样做,事情就会并且将会出错:
- 方块必须在注册表未冻结时创建。NeoForge为你解冻注册表并在稍后冻结它们,所以注册是你创建方块的时间窗口。
- 如果你尝试在注册表再次冻结时创 建和/或注册方块,游戏将崩溃并报告一个
null方块,这可能非常令人困惑。 - 如果你仍然设法拥有一个悬空的方块实例,游戏在同步和保存时将无法识别它,并将其替换为空气。
创建方块(Creating Blocks)
如前所述,我们首先创建我们的DeferredRegister.Blocks:
public static final DeferredRegister.Blocks BLOCKS = DeferredRegister.createBlocks("yourmodid");
基础方块(Basic Blocks)
对于不需要特殊功能的简单方块(想想圆石、木板等),可以直接使用Block类。为此,在注册期间,使用BlockBehaviour.Properties参数实例化Block。这个BlockBehaviour.Properties参数可以使用BlockBehaviour.Properties#of创建,并可以通过调用其方法进行自定义。最重要的方法有:
setId- 设置方块的资源键(resource key)。- 这必须在每个方块上设置;否则将抛出异常。
destroyTime- 确定方块需要被破坏的时间。- 石头的破坏时间为1.5,泥土为0.5,黑曜石为50,基岩为-1 (不可破坏)。
explosionResistance- 确定方块的爆炸抗性。- 石头的爆炸抗性为6.0,泥土为0.5,黑曜石为1,200,基岩为3,600,000。
sound- 设置方块被敲击、破坏或放置时发出的声音。- 默认值为
SoundType.STONE。更多细节请参见声音页面(Sounds page)。
- 默认值为
lightLevel- 设置方块的发光等级。接受一个带有BlockState参数的函数,返回0到15之间的值。- 例如,荧石使用
state -> 15,火把使用state -> 14。
- 例如,荧石使用
friction- 设置方块的摩擦系数(滑度)。- 默认值为0.6。冰使用0.98。
例如,一个简单的实现将如下所示:
//BLOCKS是一个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,一种特殊类型的[物品(item)],使用时放置一个方块。这也意味着像创造模式标签页或最大堆叠数这样的东西由相应的BlockItem处理。
BlockItem必须与方块分开注册。这是因为方块不一定需要物品,例如如果它不打算被收集(就像火的情况一样)。
更多功能(More Functionality)
直接使用Block只允许非常基础的方块。如果你想添加功能,比如玩家交互或不同的碰撞箱,需要一个扩展Block的自定义类。Block类有许多可以重写的方法来做不同的事情;更多信息请参见Block、BlockBehaviour和IBlockExtension类。另请参见下面的使用方块(Using blocks)部分,了解方块的一些最常见用例。
如果你想制作一个具有不同变体的方块(想想一个具有底部、顶部和双倍变体的台阶),你应该使用[方块状态(blockstates)]。最后,如果你想要一个存储额外数据的方块(想想存储其物品栏的箱子),应该使用方块实体(block entity)。这里的经验法则是:如果你有有限且合理数量的状态(最多几百个状态),使用方块状态;如果你有无限或接近无限数量的状态,使用方块实体。
方块类型(Block Types)
方块类型是用于序列化和反序列化方块对象的MapCodecs。这个MapCodec通过BlockBehaviour#codec设置并注册(registered)到方块类型注册表。目前,它的唯一用途是在生成方块列表报告时。每个Block的子类都应该创建一个方块类型。例如,FlowerBlock#CODEC代表大多数花的方块类型,而其子类WitherRoseBlock有一个单独的方块类型。
如果方块子类只接受BlockBehaviour.Properties,那么可以使用BlockBehaviour#simpleCodec来创建MapCodec。
// 对于某个方块子类
public class SimpleBlock extends Block {
public SimpleBlock(BlockBehavior.Properties properties) {
// ...
}
@Override
public MapCodec<SimpleBlock> codec() {
return SIMPLE_CODEC.get();
}
}
// 在某个注册类中
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。
// 对于某个方块子类
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;
}
}
// 在某个注册类中
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() // 代表BlockBehavior.Properties参数
).apply(instance, ComplexBlock::new)
)
);
虽然方块类型目前基本上没有使用,但随着Mojang继续向以编解码器为中心的结构发展,预计未来会变得更加重要。
DeferredRegister.Blocks辅助方法
我们已经讨论了如何创建DeferredRegister.Blocksabove