本篇教程的视频

本篇教程的源代码

Github地址:TutorialMod-BuildingBlock-1.21

介绍

本篇教程其实比较简单,我们将仿照原版的各种建筑类方块,增加一系列内容,包括楼梯台阶栅栏栅栏门活板门按钮压力板等等。

根据我们前面所学的东西,要写这些东西其实还算是简单的,无非在于注册,实例化他们各自对应的方块类,然后是一些资源文件和数据文件。

而本篇教程就不再讲解源代码的内容了,如有不清楚的地方可以自行搜索源代码进行研究

同样的,你也可以尝试仿写这些方块所属的方块类,看他们为什么能够实现这些功能。

比如楼梯、栅栏为什么有那么多状态,门的“半门”特性是啥,按钮和压力板为什么能输出红石信号,两个台阶怎么变成一个完整的方块等等。这些其实都可以去研究

注册方块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static final Block  ICE_ETHER_STAIRS = register("ice_ether_stairs",
new StairsBlock(ICE_ETHER_BLOCK.getDefaultState(), AbstractBlock.Settings.copy(ICE_ETHER_BLOCK)));
public static final Block ICE_ETHER_SLAB = register("ice_ether_slab",
new SlabBlock(AbstractBlock.Settings.copy(ICE_ETHER_BLOCK)));
public static final Block ICE_ETHER_BUTTON = register("ice_ether_button",
new ButtonBlock(BlockSetType.OAK, 10, AbstractBlock.Settings.copy(ICE_ETHER_BLOCK)));
public static final Block ICE_ETHER_PRESSURE_PLATE = register("ice_ether_pressure_plate",
new PressurePlateBlock(BlockSetType.OAK, AbstractBlock.Settings.copy(ICE_ETHER_BLOCK)));
public static final Block ICE_ETHER_FENCE = register("ice_ether_fence",
new FenceBlock(AbstractBlock.Settings.copy(ICE_ETHER_BLOCK)));
public static final Block ICE_ETHER_FENCE_GATE = register("ice_ether_fence_gate",
new FenceGateBlock(WoodType.OAK, AbstractBlock.Settings.copy(ICE_ETHER_BLOCK)));
public static final Block ICE_ETHER_WALL = register("ice_ether_wall",
new WallBlock(AbstractBlock.Settings.copy(ICE_ETHER_BLOCK)));
public static final Block ICE_ETHER_DOOR = register("ice_ether_door",
new DoorBlock(BlockSetType.STONE, AbstractBlock.Settings.copy(ICE_ETHER_BLOCK)));
public static final Block ICE_ETHER_TRAPDOOR = register("ice_ether_trapdoor",
new TrapdoorBlock(BlockSetType.IRON, AbstractBlock.Settings.copy(ICE_ETHER_BLOCK)));

这里我们注册了一些方块,楼梯、台阶、栅栏、墙、栅栏门、门、活板门、按钮、压力板

它们各自实例化的方块是不同的,其中一些的参数及其顺序和1.20.X的不太一样,请注意

楼梯是StairsBlock,其中的参数分别是基础方块的状态、设置。这个基础方块是其实也不难理解,我们的楼梯是从一个完整的方块上衍生出来的,比如原版的各种楼梯,都有对应的一个完整的方块,所以我们这里也用之前写的方块作为其基础方块

台阶是SlabBlock,其参数就一个设置,我们可以直接复制我们之前写的方块

按钮是ButtonBlock,其中的参数是按钮的类型(木制、石质等等)、恢复时间(按下到弹起的时间,单位tick)、设置。
原版的按钮有些是被箭或者三叉戟射中也会激活,这个和前面的那个方块类型有关系,还要单独写Tag

压力板是PressurePlateBlock,其中的参数是压力板的类型(木制、石质等等)、设置。在1.20中,我们还有一个参数是关于敏感度的,但是现在没了。
而这个敏感度是与它的类型有关的,木制(任意种类的木头,比如这里是橡木)是可以检测所有实体,包括掉落物;但如果是石质的,只能检测玩家和生物实体

栅栏是FenceBlock,其参数就一个设置,直接复制我们之前写的方块。但是栅栏也得写Tag,不然它无法连接

栅栏门是FenceGateBlock,其参数是栅栏门的类型(木制、石质等等)、设置

墙是WallBlock,其参数就一个设置,直接复制我们之前写的方块。墙和栅栏一样得写Tag,不然它无法连接

门是DoorBlock,其参数是门的类型(木制、石质等等,其中铁质只能用红石信号开)、设置

活板门是TrapdoorBlock,其参数是活板门的类型(木制、石质等等,其中铁质只能用红石信号开)、设置

说实话,这里面有些参数挺迷的,1.20到1.21大改一通,差点没给我绕进去

加入物品栏

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

记得加入物品栏,不然你的方块就没法在游戏中看到了

BlockTag

上面提到了一些方块得写Tag才能正常运行,这里我们就来写一下

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

我们给栅栏、栅栏门、墙写了Tag,这样他们就能正常连接了

但是,如果只是这样写,那么我们的栅栏默认是和砖质的栅栏(原版也就一个下界砖栅栏)连接的,如果要和木制的栅栏连接,我们还得写一个Tag

1
2
getOrCreateTagBuilder(BlockTags.WOODEN_FENCES)
.add(ModBlocks.ICE_ETHER_FENCE);

但请注意,二者只能选其一,写了这个以后就不能和砖质栅栏进行连接

另外还有一个按钮,如果说我们希望我们的按钮被箭或者三叉戟射中也能激活,首先我们得确保注册的时候,其类型是木制的,同时还得写一个Tag

1
2
getOrCreateTagBuilder(BlockTags.BUTTONS)
.add(ModBlocks.ICE_ETHER_BUTTON);

模型文件

在生成模型文件之前,我们还得创建一个类。

虽然说这篇教程我们没去看源代码,但是当我们自己去找原版的那些关于模型数据生成的东西的时候,我们没看到有什么ModelProvider,但是你会发现一个BlockFamilies

1
2
3
4
5
6
7
8
9
10
11
12
13
public static final BlockFamily OAK = register(Blocks.OAK_PLANKS)
.button(Blocks.OAK_BUTTON)
.fence(Blocks.OAK_FENCE)
.fenceGate(Blocks.OAK_FENCE_GATE)
.pressurePlate(Blocks.OAK_PRESSURE_PLATE)
.sign(Blocks.OAK_SIGN, Blocks.OAK_WALL_SIGN)
.slab(Blocks.OAK_SLAB)
.stairs(Blocks.OAK_STAIRS)
.door(Blocks.OAK_DOOR)
.trapdoor(Blocks.OAK_TRAPDOOR)
.group("wooden")
.unlockCriterionName("has_planks")
.build();

我暂且将其直译为方块族,我们取其中的一个BlockFamily,比如OAK,我们可以看到,这个BlockFamily包含了很多关于橡木的方块

进一步思考,它后面的那些方块都是从原本的OAK_PLANKS衍生出来的,然后除了门和活板门,其余方块是共用同一个材质的!(其实进一步挖下去可以发现,门和活板门的材质路径是单独确定的)

另外,我们也在BlockStateModelGenerator中发现了此类的调用

1
2
3
BlockFamilies.getFamilies()
.filter(BlockFamily::shouldGenerateModels)
.forEach(family -> this.registerCubeAllModelTexturePool(family.getBaseBlock()).family(family));

这里是遍历了里面的所有BlockFamily,然后调用了registerCubeAllModelTexturePool方法,这个方法是用来生成模型文件的

同时,我们在BlockStateModelGenerator没有看到上面那些方块的生成语句

所以说我们还得先创建一个ModBlockFamilies类,然后再生成模型文件

创建ModBlockFamilies类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class ModBlockFamilies {
private static final Map<Block, BlockFamily> BASE_BLOCKS_TO_FAMILIES = Maps.<Block, BlockFamily>newHashMap();

public static final BlockFamily ICE_ETHER = register(ModBlocks.ICE_ETHER_BLOCK)
.stairs(ModBlocks.ICE_ETHER_STAIRS)
.slab(ModBlocks.ICE_ETHER_SLAB)
.button(ModBlocks.ICE_ETHER_BUTTON)
.pressurePlate(ModBlocks.ICE_ETHER_PRESSURE_PLATE)
.fence(ModBlocks.ICE_ETHER_FENCE)
.fenceGate(ModBlocks.ICE_ETHER_FENCE_GATE)
.wall(ModBlocks.ICE_ETHER_WALL)
.door(ModBlocks.ICE_ETHER_DOOR)
.trapdoor(ModBlocks.ICE_ETHER_TRAPDOOR)
.unlockCriterionName("has_ice_ether_block")
.build();
public static BlockFamily.Builder register(Block baseBlock) {
BlockFamily.Builder builder = new BlockFamily.Builder(baseBlock);
BlockFamily blockFamily = (BlockFamily)BASE_BLOCKS_TO_FAMILIES.put(baseBlock, builder.build());
if (blockFamily != null) {
throw new IllegalStateException("Duplicate family definition for " + Registries.BLOCK.getId(baseBlock));
} else {
return builder;
}
}
public static Stream<BlockFamily> getFamilies() {
return BASE_BLOCKS_TO_FAMILIES.values().stream();
}
}

这里的很多方法是从BlockFamilies类中搬过来的,然后getFamilies方法是用于数据生成的调用的

这里我们没有告示牌这类东西,就没有写上,group也是可写可不写的

告示牌后面有单独的教程会讲,这玩意是实体,不借助第三方API还有点难写

生成模型文件

1
2
3
4
ModBlockFamilies.getFamilies()
.filter(BlockFamily::shouldGenerateModels).forEach(
blockFamily -> blockStateModelGenerator.registerCubeAllModelTexturePool(blockFamily.getBaseBlock())
.family(blockFamily));

用这里这条语句代替原来的blockStateModelGenerator.registerSimpleCubeAll(ModBlocks.ICE_ETHER_BLOCK);,一定得把原来的语句去掉,不然会运行的时候会报错

随后我们跑数据生成的时候,就可以生成一堆模型文件了

其他数据文件

语言文件

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

战利品列表

这个视频教程里面忘记讲了,我们copy的方块是我们之前写的,设置了requireTool,所以也别忘了写

同时,门和台阶这种具有特殊状态的方块也得注意其写法,它们有单独的方法

1
2
3
4
5
6
7
8
9
10
addDrop(ModBlocks.ICE_ETHER_STAIRS);
addDrop(ModBlocks.ICE_ETHER_FENCE);
addDrop(ModBlocks.ICE_ETHER_FENCE_GATE);
addDrop(ModBlocks.ICE_ETHER_BUTTON);
addDrop(ModBlocks.ICE_ETHER_PRESSURE_PLATE);
addDrop(ModBlocks.ICE_ETHER_WALL);
addDrop(ModBlocks.ICE_ETHER_TRAPDOOR);

addDrop(ModBlocks.ICE_ETHER_DOOR, doorDrops(ModBlocks.ICE_ETHER_DOOR));
addDrop(ModBlocks.ICE_ETHER_SLAB, slabDrops(ModBlocks.ICE_ETHER_SLAB));

配方文件的话就不再赘述,可以根据原版的配方文件进行仿写

随后我们就可以运行游戏,看看我们的方块了