方块(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是我们正在注册的方块的类类型。- 有一些用于注册方块的辅助方法。有关更多详细信息,请参见下面。
所以现在,让我们注册我们的方块:
//BLOCKS是一个DeferredRegister.Blocks
public static final DeferredBlock<Block> MY_BLOCK = BLOCKS.register("my_block", registryName -> new Block(...));
注册方块后,所有对新my_block的引用都应使用此常量。例如,如果您想检查给定位置的方块是否是my_block,代码将如下所示:
level.getBlockState(position) // 返回给定层级(世界)中给定位置放置的方块状态
.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- 设置方块的资源键。- 这必须在每个方块上设置;否则,将抛出异常。
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",