本篇教程的视频

本篇教程的源代码

GitHub地址:TutorialMod-1.20.1-Forge-Building Block

注册方块

本期教程我们来为模组添加一系列建材类的方块,比如说楼梯台阶栅栏等等方块,这些方块大多用于建筑装饰方面

经过了前面的这些教程,其实写这些方块也是很简单的,并不需要我们自定义方块类,直接用原版已有的方块类进行注册就好了

我们也可以通过这期教程,初步探索一下方块状态 BlockState为何物

源代码的话这里就不带大家看了,想看源代码的同学可自行研究Blocks类中相应的方块

这里我们直接来注册

楼梯

1
2
3
public static final RegistryObject<StairBlock> ICE_ETHER_STAIRS =
registerBlock("ice_ether_stairs",
() -> new StairBlock(() -> ICE_ETHER_BLOCK.get().defaultBlockState(), BlockBehaviour.Properties.of().strength(3.0F, 2.0F)));

楼梯方块对应的是StairBlock类,它接受两个参数,第一个参数是方块状态,第二个参数是方块属性

台阶

1
2
3
public static final RegistryObject<SlabBlock> ICE_ETHER_SLAB =
registerBlock("ice_ether_slab",
() -> new SlabBlock(BlockBehaviour.Properties.of().strength(3.0F, 2.0F)));

台阶方块对应的是SlabBlock类,它接受一个参数,方块属性

按钮

1
2
3
public static final RegistryObject<ButtonBlock> ICE_ETHER_BUTTON =
registerBlock("ice_ether_button",
() -> new ButtonBlock(BlockBehaviour.Properties.of().strength(3.0F, 2.0F), BlockSetType.OAK, 40, false));

按钮方块对应的是ButtonBlock类,它接受四个参数,第一个是方块属性,第二个是方块类型,第三个是按钮从按下到弹起的时间(tick),第四个是能否被箭等实体激活

这里的方块类型实际上没什么特殊作用,而到了1.21之后,能否被箭激活以及压力板的敏感度都归到方块类型中了,不过这里就不展开讲了

压力板

1
2
3
public static final RegistryObject<PressurePlateBlock> ICE_ETHER_PRESSURE_PLATE =
registerBlock("ice_ether_pressure_plate",
() -> new PressurePlateBlock(PressurePlateBlock.Sensitivity.EVERYTHING, BlockBehaviour.Properties.of().strength(1.0F, 2.0F), BlockSetType.OAK));

压力板对应的是PressurePlateBlock类,它接受三个参数,第一个是敏感度,第二个是方块属性,第三个是方块类型

栅栏门

1
2
3
public static final RegistryObject<FenceGateBlock> ICE_ETHER_FENCE_GATE =
registerBlock("ice_ether_fence_gate",
() -> new FenceGateBlock(BlockBehaviour.Properties.of().strength(3.0F, 2.0F), WoodType.OAK));

栅栏门对应的是FenceGateBlock类,它接受两个参数,第一个是方块属性,第二个是木头类型(也不知道它有什么用,而且原版的栅栏门好像都是木制的)

栅栏

1
2
3
public static final RegistryObject<FenceBlock> ICE_ETHER_FENCE =
registerBlock("ice_ether_fence",
() -> new FenceBlock(BlockBehaviour.Properties.of().strength(3.0F, 2.0F)));

栅栏对应的是FenceBlock类,它接受一个参数,方块属性

1
2
3
public static final RegistryObject<WallBlock> ICE_ETHER_WALL =
registerBlock("ice_ether_wall",
() -> new WallBlock(BlockBehaviour.Properties.of().strength(3.0F, 2.0F)));

墙对应的是WallBlock类,它接受一个参数,方块属性

1
2
3
public static final RegistryObject<DoorBlock> ICE_ETHER_DOOR =
registerBlock("ice_ether_door",
() -> new DoorBlock(BlockBehaviour.Properties.of().strength(3.0F, 2.0F).noOcclusion(), BlockSetType.IRON));

门对应的是DoorBlock类,它接受两个参数,第一个是方块属性,第二个是方块类型

这里的方块类型有个作用,它决定了门的打开方式,比如这里的IRON是铁门,那么就需要用红石信号打开这个门,而没法直接手动打开

活板门

1
2
3
public static final RegistryObject<TrapDoorBlock> ICE_ETHER_TRAPDOOR =
registerBlock("ice_ether_trapdoor",
() -> new TrapDoorBlock(BlockBehaviour.Properties.of().strength(3.0F, 2.0F).noOcclusion(), BlockSetType.OAK));

活板门对应的是TrapDoorBlock类,它接受两个参数,第一个是方块属性,第二个是方块类型‘

方块类型的作用和门的一样

这里再补充一点,我们在方块属性中添加了noOcclusion()方法,这表示这个方块是非实心的;
这一般用于非完整方块以及透明或半透明的方块,以防止通过这个方块透视其他的地方,这个内容还会在之后的教程中详细介绍

添加到物品栏

那么到此为止,我们已经注册好了一堆方块,虽然很多,但实际上都大差不差

接下来我们将方块添加到物品栏里面

1
2
3
4
5
6
7
8
9
pOutput.accept(ModBlocks.ICE_ETHER_STAIRS.get());
pOutput.accept(ModBlocks.ICE_ETHER_SLAB.get());
pOutput.accept(ModBlocks.ICE_ETHER_BUTTON.get());
pOutput.accept(ModBlocks.ICE_ETHER_PRESSURE_PLATE.get());
pOutput.accept(ModBlocks.ICE_ETHER_FENCE.get());
pOutput.accept(ModBlocks.ICE_ETHER_FENCE_GATE.get());
pOutput.accept(ModBlocks.ICE_ETHER_WALL.get());
pOutput.accept(ModBlocks.ICE_ETHER_DOOR.get());
pOutput.accept(ModBlocks.ICE_ETHER_TRAPDOOR.get());

数据文件

那么接下来就是这些方块的各种数据文件了

我们依旧采用数据生成来编写这些文件,这里我们也能感受到使用数据生成的便利

战利品列表

1
2
3
4
5
6
7
8
9
10
11
dropSelf(ModBlocks.ICE_ETHER_STAIRS.get());
add(ModBlocks.ICE_ETHER_SLAB.get(),
block -> createSlabItemTable(ModBlocks.ICE_ETHER_SLAB.get()));
dropSelf(ModBlocks.ICE_ETHER_BUTTON.get());
dropSelf(ModBlocks.ICE_ETHER_PRESSURE_PLATE.get());
dropSelf(ModBlocks.ICE_ETHER_FENCE_GATE.get());
dropSelf(ModBlocks.ICE_ETHER_FENCE.get());
dropSelf(ModBlocks.ICE_ETHER_WALL.get());
add(ModBlocks.ICE_ETHER_DOOR.get(),
block -> createDoorTable(ModBlocks.ICE_ETHER_DOOR.get()));
dropSelf(ModBlocks.ICE_ETHER_TRAPDOOR.get());

一般的方块还是采用dropSelf()方法来生成战利品列表

不过有两个特殊的,即台阶,它们需要分别调用createSlabItemTable()createDoorTable()方法

对于台阶来说,有时我们会用两个台阶组成一个完整的方块,但破坏这个完整方块时,仍需要掉落两个台阶而不是完整方块

对于门来说,门它是两个方块组成的,分上下两部分,但不论我们破坏哪个部分,都会掉落整个门

方块模型

在写相应的语句之前,我们先添加两个方法

1
2
3
4
5
6
private <T extends Block> void blockItem(RegistryObject<T> block) {
simpleBlockItem(block.get(), new ModelFile.UncheckedModelFile(TutorialMod.MOD_ID + ":block/" + block.getId().getPath()));
}
private <T extends Block> void blockItem(RegistryObject<T> block, String append) {
simpleBlockItem(block.get(), new ModelFile.UncheckedModelFile(TutorialMod.MOD_ID + ":block/" + block.getId().getPath() + append));
}

这两个方法待会用于生成一部分方块物品模型

接下来就来写其他的语句

1
2
3
4
5
6
7
8
9
10
stairsBlock(ModBlocks.ICE_ETHER_STAIRS.get(), blockTexture(ModBlocks.ICE_ETHER_BLOCK.get()));
slabBlock(ModBlocks.ICE_ETHER_SLAB.get(), blockTexture(ModBlocks.ICE_ETHER_BLOCK.get()), blockTexture(ModBlocks.ICE_ETHER_BLOCK.get()));
buttonBlock(ModBlocks.ICE_ETHER_BUTTON.get(), blockTexture(ModBlocks.ICE_ETHER_BLOCK.get()));
pressurePlateBlock(ModBlocks.ICE_ETHER_PRESSURE_PLATE.get(), blockTexture(ModBlocks.ICE_ETHER_BLOCK.get()));
fenceBlock(ModBlocks.ICE_ETHER_FENCE.get(), blockTexture(ModBlocks.ICE_ETHER_BLOCK.get()));
fenceGateBlock(ModBlocks.ICE_ETHER_FENCE_GATE.get(), blockTexture(ModBlocks.ICE_ETHER_BLOCK.get()));
wallBlock(ModBlocks.ICE_ETHER_WALL.get(), blockTexture(ModBlocks.ICE_ETHER_BLOCK.get()));

doorBlockWithRenderType(ModBlocks.ICE_ETHER_DOOR.get(), modLoc("block/ice_ether_door_bottom"), modLoc("block/ice_ether_door_top"), "cutout");
trapdoorBlockWithRenderType(ModBlocks.ICE_ETHER_TRAPDOOR.get(), modLoc("block/ice_ether_trapdoor"), true, "cutout");

这里除了门和活板门,其他的方块都可以调用各自对应的方法来生成方块模型

blockTexture()方法用于指定方块的材质路径

而门和活板门则需要调用doorBlockWithRenderType()trapdoorBlockWithRenderType()方法

这里我们的门和活板门是带透明通道的,所以需要给它设置RenderType,否则透明区域会变成实心的,就不通透了

RenderType参数表示这个方块的渲染类型,这里我们传入"cutout",这一般适用于全透明的方块;
半透明的方块则传入"translucent"

最后使用我们写的方法生成方块物品模型,因为上面的方块生成不能生成对应的方块物品模型文件,所以还得自己写

1
2
3
4
5
blockItem(ModBlocks.ICE_ETHER_STAIRS);
blockItem(ModBlocks.ICE_ETHER_SLAB);
blockItem(ModBlocks.ICE_ETHER_PRESSURE_PLATE);
blockItem(ModBlocks.ICE_ETHER_FENCE_GATE);
blockItem(ModBlocks.ICE_ETHER_TRAPDOOR, "_bottom");

这里活板门稍微特殊一点,因为它有开启和关闭两个状态,我们需要使用它的关闭状态来生成方块物品模型

另外的栅栏、墙和门则待会到物品模型生成类中单独编写

方块标签

为了让我们的栅栏和墙能正确连接,还需要编写Tag,因为原版的连接判定就是通过Tag来判断的,大家也可以到相应的源代码中查看

1
2
3
4
5
6
tag(BlockTags.FENCES)
.add(ModBlocks.ICE_ETHER_FENCE.get());
tag(BlockTags.FENCE_GATES)
.add(ModBlocks.ICE_ETHER_FENCE_GATE.get());
tag(BlockTags.WALLS)
.add(ModBlocks.ICE_ETHER_WALL.get());

不过这里需要注意一点,栅栏还有一个标签是WOODEN_FENCES,如果你需要让自己的栅栏与原版的那些木制栅栏连接,则需要加入这个标签

而默认的FENCES标签只能与砖制的栅栏(下界砖栅栏)连接,二者不可兼得

语言文件

1
2
3
4
5
6
7
8
9
add(ModBlocks.ICE_ETHER_STAIRS.get(), "Ice Ether Stairs");
add(ModBlocks.ICE_ETHER_SLAB.get(), "Ice Ether Slabs");
add(ModBlocks.ICE_ETHER_BUTTON.get(), "Ice Ether Button");
add(ModBlocks.ICE_ETHER_PRESSURE_PLATE.get(), "Ice Ether Pressure Plate");
add(ModBlocks.ICE_ETHER_FENCE.get(), "Ice Ether Fence");
add(ModBlocks.ICE_ETHER_FENCE_GATE.get(), "Ice Ether Fence Gate");
add(ModBlocks.ICE_ETHER_WALL.get(), "Ice Ether Wall");
add(ModBlocks.ICE_ETHER_DOOR.get(), "Ice Ether Door");
add(ModBlocks.ICE_ETHER_TRAPDOOR.get(), "Ice Ether Trapdoor");

这是常规操作了

物品模型文件

在写物品模型文件之前,我们先写几个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private <T extends Block> void buttonItem(RegistryObject<T> block, RegistryObject<Block> base) {
this.withExistingParent(block.getId().getPath(), mcLoc("block/button_inventory"))
.texture("texture", ResourceLocation.fromNamespaceAndPath(TutorialMod.MOD_ID,
"block/" + base.getId().getPath()));
}
private <T extends Block> void fenceItem(RegistryObject<T> block, RegistryObject<Block> base) {
this.withExistingParent(block.getId().getPath(), mcLoc("block/fence_inventory"))
.texture("texture", ResourceLocation.fromNamespaceAndPath(TutorialMod.MOD_ID,
"block/" + base.getId().getPath()));
}
private <T extends Block> void wallItem(RegistryObject<T> block, RegistryObject<Block> base) {
this.withExistingParent(block.getId().getPath(), mcLoc("block/wall_inventory"))
.texture("wall", ResourceLocation.fromNamespaceAndPath(TutorialMod.MOD_ID,
"block/" + base.getId().getPath()));
}

这是分别用于生成按钮栅栏物品模型文件的方法

而后我们来写物品模型文件的生成语句

1
2
3
4
5
buttonItem(ModBlocks.ICE_ETHER_BUTTON, ModBlocks.ICE_ETHER_BLOCK);
fenceItem(ModBlocks.ICE_ETHER_FENCE, ModBlocks.ICE_ETHER_BLOCK);
wallItem(ModBlocks.ICE_ETHER_WALL, ModBlocks.ICE_ETHER_BLOCK);

basicItem(ModBlocks.ICE_ETHER_DOOR.get().asItem());

按钮栅栏的生成方法与一般的物品不一样,它们有自己独立的物品栏模型文件

比如说栅栏,实际我们放置时,如果边上没有东西,那么它就是一根柱子,但在物品栏中,它实际上是两根柱子加两根横杆这么一个模型,
所以它的模型文件并不是像其他方块那样直接引用方块模型作为父级文件的

就更特殊一点,它的物品模型文件只是一张二维的贴图,不是三维的模型

贴图文件

配方和中文的语言文件这里我就不写了,大家根据自己的需要去编写

最后我们来讲讲它的贴图文件

我们写的这一堆方块中,除了门和活板门外,其他的方块都会引用ice_ether_block.png这个贴图(见方块模型生成类中相关内容)

所以我们只需要准备门和活板门的贴图即可

不过注意,门的方块贴图有两个,一个_bottom,一个_top,同时还要准备一张门的物品贴图

具体见源代码仓库