本篇教程的视频

(待发布)

本篇教程的源代码

GitHub地址:TutorialMod-Wood-1.20.1

本篇教程目标

  • 理解原版各类木头相关方块注册
  • 学会编写原木、木头,以及绑定与它们各自对应的削皮原木、削皮木头

查看源代码

后面的教程我们将讲到,以及它们的世界生成

不过,在有树之前,得先有各种木头,和往常一样,我们来看看源代码

方块注册

我们到Blocks类中找到LOGWOOD相关的内容,这里我们就以橡木为例

1
2
3
4
5
6
7
8
9
10
11
12
13
public static final Block OAK_LOG = register("oak_log", createLogBlock(MapColor.OAK_TAN, MapColor.SPRUCE_BROWN));

public static final Block STRIPPED_OAK_LOG = register("stripped_oak_log", createLogBlock(MapColor.OAK_TAN, MapColor.OAK_TAN));

public static final Block OAK_WOOD = register(
"oak_wood",
new PillarBlock(AbstractBlock.Settings.create().mapColor(MapColor.OAK_TAN).instrument(Instrument.BASS).strength(2.0F).sounds(BlockSoundGroup.WOOD).burnable())
);

public static final Block STRIPPED_DARK_OAK_WOOD = register(
"stripped_dark_oak_wood",
new PillarBlock(AbstractBlock.Settings.create().mapColor(MapColor.BROWN).instrument(Instrument.BASS).strength(2.0F).sounds(BlockSoundGroup.WOOD).burnable())
);

原木的注册虽然是有一个createLogBlock方法,但实际上它也是实例化PillarBlock

1
2
3
4
5
6
7
8
9
10
public static PillarBlock createLogBlock(MapColor topMapColor, MapColor sideMapColor) {
return new PillarBlock(
AbstractBlock.Settings.create()
.mapColor(state -> state.get(PillarBlock.AXIS) == Direction.Axis.Y ? topMapColor : sideMapColor)
.instrument(Instrument.BASS)
.strength(2.0F)
.sounds(BlockSoundGroup.WOOD)
.burnable()
);
}

可以看到,原木的注册方法与木头类似,只不过原木的材质颜色是会根据方向来改变的

因为原木它的侧面和它的年轮面(顶面和底面)并不是一样的,所以显示在地图上的颜色会不一样

而木头的话就没有这个特性,所以mapColor()方法中直接返回一个颜色即可

其他的方块设置我们也基本上见过了,burnable()方法就是让这个方块可以燃烧,当然具体的燃烧速度、几率我们后面再说,它们由其他类定义

另外我们也顺便看看木板树叶的注册,我们在这期教程中也一起加入了,后面就直接讲树了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static final Block OAK_PLANKS = register(
"oak_planks",
new Block(AbstractBlock.Settings.create().mapColor(MapColor.OAK_TAN).instrument(Instrument.BASS).strength(2.0F, 3.0F).sounds(BlockSoundGroup.WOOD).burnable())
);

public static final Block OAK_LEAVES = register("oak_leaves", createLeavesBlock(BlockSoundGroup.GRASS));

public static LeavesBlock createLeavesBlock(BlockSoundGroup soundGroup) {
return new LeavesBlock(
AbstractBlock.Settings.create()
.mapColor(MapColor.DARK_GREEN)
.strength(0.2F)
.ticksRandomly()
.sounds(soundGroup)
.nonOpaque()
.allowsSpawning(Blocks::canSpawnOnLeaves)
.suffocates(Blocks::never)
.blockVision(Blocks::never)
.burnable()
.pistonBehavior(PistonBehavior.DESTROY)
.solidBlock(Blocks::never)
);
}

木板简单,实例化普通的方块

而树叶也有一个方法来注册,实例化LeavesBlock,我们也可以来看看它的一些方块设置

ticksRandomly()方法就是让树叶有随机刻,因为要检测这个树叶能否存在,当我们砍完树之后,树上的树叶会一点点消失

nonOpaque()方法指定方块是非实心的,因为树叶是有透明通道

allowsSpawning()方法指定方块上能否生成生物,这里指定的是能够生成在树叶上的生物

suffocates()blockVision()方法指定方块是否阻挡生物的视线和移动,这里指定的是不阻挡

pistonBehavior()方法指定方块在活塞推动时的行为,这里指定的是破坏方块

solidBlock()方法指定方块是否是实心的,这里指定的是不是实心的

当然我们后面也可以来研究研究树叶方块的具体实现

燃烧属性

上面我们也都看到了burnable()方法,这个方法就是让方块可以燃烧,但没有涉及燃烧速度和几率

这里我们就得到FireBlock中找到相关内容了

不过先说一下,FireBlock其实是火焰方块,也就是打火石生成出来的火,在这个方块类中定义了方块燃烧的属性

1
2
3
4
5
6
7
8
9
10
11
12
public static void registerDefaultFlammables() {
FireBlock fireBlock = (FireBlock)Blocks.FIRE;
fireBlock.registerFlammableBlock(Blocks.OAK_PLANKS, 5, 20);
fireBlock.registerFlammableBlock(Blocks.OAK_SLAB, 5, 20);
fireBlock.registerFlammableBlock(Blocks.OAK_FENCE_GATE, 5, 20);
...
fireBlock.registerFlammableBlock(Blocks.OAK_LOG, 5, 5);
fireBlock.registerFlammableBlock(Blocks.STRIPPED_OAK_LOG, 5, 5);
fireBlock.registerFlammableBlock(Blocks.OAK_WOOD, 5, 5);
fireBlock.registerFlammableBlock(Blocks.OAK_LEAVES, 30, 60);
...
}

我们可以在这个类中找到一个registerDefaultFlammables方法,在这其中就定义了可以燃烧的方块它们的燃烧属性

registerFlammableBlock方法就是注册一个方块的燃烧属性,它接受三个参数,第一个参数是方块,第二个参数是几率(有火源在边上时,自身着火的概率),第三个参数是燃烧传播几率(引燃其他可燃方块的概率)

相当而言,原木类的几率会小一点,原木加工后的产物,比如木板、台阶这种,传播概率会高一点,而树叶要比它们更加易燃,概率更高

另外也说一下,要作为燃料还是要写Tag的,原版熔炉中定义的燃料是用Tag指定的

去皮与不去皮对应关系

原木与去皮原木的话,如果你留意过的话,就能在AxeItem中发现它们

1
2
3
4
5
protected static final Map<Block, Block> STRIPPED_BLOCKS = new Builder<Block, Block>()
.put(Blocks.OAK_WOOD, Blocks.STRIPPED_OAK_WOOD)
.put(Blocks.OAK_LOG, Blocks.STRIPPED_OAK_LOG)
...
.build();

AxeItem中有一个Map,里面定义了原木与去皮原木的对应关系,我们可以通过Mixin来注入我们自己的木头,不过Fabric也提供给我们了API

注册

注册方块

这里我们就来注册我们自己的各种木头

1
2
3
4
5
6
7
8
public static final Block ICE_ETHER_LOG = register("ice_ether_log",
new PillarBlock(AbstractBlock.Settings.copy(Blocks.OAK_LOG)));
public static final Block ICE_ETHER_WOOD = register("ice_ether_wood",
new PillarBlock(AbstractBlock.Settings.copy(Blocks.OAK_LOG)));
public static final Block STRIPPED_ICE_ETHER_LOG = register("stripped_ice_ether_log",
new PillarBlock(AbstractBlock.Settings.copy(Blocks.OAK_LOG)));
public static final Block STRIPPED_ICE_ETHER_WOOD = register("stripped_ice_ether_wood",
new PillarBlock(AbstractBlock.Settings.copy(Blocks.OAK_LOG)));

这里我们就注册了原木木头去皮原木去皮木头,方便起见,我们就直接用原版对应木头的设置了

它们都是实例化PillarBlock,也就是柱体方块,不过注意一下,我们之前的教程也搞了个柱类方块,不要和之前那个搞混了

另外顺便把木板和树叶也注册了

1
2
3
4
public static final Block ICE_ETHER_LEAVES = register("ice_ether_leaves",
new LeavesBlock(AbstractBlock.Settings.copy(Blocks.OAK_LEAVES)));
public static final Block ICE_ETHER_PLANKS = register("ice_ether_planks",
new Block(AbstractBlock.Settings.copy(Blocks.OAK_PLANKS)));

同样也是复制了原版方块的属性

注册去皮对应内容

前面我们在AxeItem中看到的内容,我们也得写一下,不然用斧头右键方块时不会转化

前面我也说了,可以用Mixin,也可以用Fabric提供的API来写,这里我们采用后者

到模组主类的初始化方法中写上

1
2
StrippableBlockRegistry.register(ModBlocks.ICE_ETHER_LOG, ModBlocks.STRIPPED_ICE_ETHER_LOG);
StrippableBlockRegistry.register(ModBlocks.ICE_ETHER_WOOD, ModBlocks.STRIPPED_ICE_ETHER_WOOD);

StrippableBlockRegistry.register方法就是注册原木与去皮原木的对应关系

它接受两个参数,第一个参数是原木,第二个参数是去皮原木

原木和木头都是这样操作

不过,除了去皮原木这种,其他用斧头右键方块会转化的,也可以用这个方法来写

注册燃烧属性

同样,我们也在主类中来注册这些方块的燃烧属性

1
2
3
4
5
6
FlammableBlockRegistry.getDefaultInstance().add(ModBlocks.ICE_ETHER_LOG, 5, 5);
FlammableBlockRegistry.getDefaultInstance().add(ModBlocks.ICE_ETHER_WOOD, 5, 5);
FlammableBlockRegistry.getDefaultInstance().add(ModBlocks.STRIPPED_ICE_ETHER_LOG, 5, 5);
FlammableBlockRegistry.getDefaultInstance().add(ModBlocks.STRIPPED_ICE_ETHER_WOOD, 5, 5);
FlammableBlockRegistry.getDefaultInstance().add(ModBlocks.ICE_ETHER_LEAVES, 30, 60);
FlammableBlockRegistry.getDefaultInstance().add(ModBlocks.ICE_ETHER_PLANKS, 5, 20);

同样,我们也是用FabricAPI来写

FlammableBlockRegistry.getDefaultInstance().add方法就是注册一个方块的燃烧属性

它接受三个参数,和FireBlockregisterFlammableBlock方法一样,第一个参数是方块,第二个参数是几率,第三个参数是燃烧传播几率

加入物品栏

最后,我们还要把我们的方块加入物品栏,不然我们看不到它们

1
2
3
4
5
6
entries.add(ModBlocks.ICE_ETHER_LOG);
entries.add(ModBlocks.ICE_ETHER_WOOD);
entries.add(ModBlocks.STRIPPED_ICE_ETHER_LOG);
entries.add(ModBlocks.STRIPPED_ICE_ETHER_WOOD);
entries.add(ModBlocks.ICE_ETHER_LEAVES);
entries.add(ModBlocks.ICE_ETHER_PLANKS);

数据文件

常规的,我们还是用数据生成来生成数据文件

语言文件

1
2
3
4
5
6
translationBuilder.add(ModBlocks.ICE_ETHER_LOG, "Ice Ether Log");
translationBuilder.add(ModBlocks.ICE_ETHER_WOOD, "Ice Ether Wood");
translationBuilder.add(ModBlocks.STRIPPED_ICE_ETHER_LOG, "Stripped Ice Ether Log");
translationBuilder.add(ModBlocks.STRIPPED_ICE_ETHER_WOOD, "Stripped Ice Ether Wood");
translationBuilder.add(ModBlocks.ICE_ETHER_PLANKS, "Ice Ether Planks");
translationBuilder.add(ModBlocks.ICE_ETHER_LEAVES, "Ice Ether Leaves");

Tag

方块

1
2
3
4
5
getOrCreateTagBuilder(BlockTags.LOGS_THAT_BURN)
.add(ModBlocks.ICE_ETHER_LOG)
.add(ModBlocks.ICE_ETHER_WOOD)
.add(ModBlocks.STRIPPED_ICE_ETHER_LOG)
.add(ModBlocks.STRIPPED_ICE_ETHER_WOOD);

物品

1
2
3
4
5
6
7
8
9
10
11
12
getOrCreateTagBuilder(ItemTags.PLANKS)
.add(ModBlocks.ICE_ETHER_PLANKS.asItem());
getOrCreateTagBuilder(ItemTags.LOGS)
.add(ModBlocks.ICE_ETHER_LOG.asItem())
.add(ModBlocks.STRIPPED_ICE_ETHER_LOG.asItem())
.add(ModBlocks.ICE_ETHER_WOOD.asItem())
.add(ModBlocks.STRIPPED_ICE_ETHER_WOOD.asItem());
getOrCreateTagBuilder(ItemTags.LOGS_THAT_BURN)
.add(ModBlocks.ICE_ETHER_LOG.asItem())
.add(ModBlocks.STRIPPED_ICE_ETHER_LOG.asItem())
.add(ModBlocks.ICE_ETHER_WOOD.asItem())
.add(ModBlocks.STRIPPED_ICE_ETHER_WOOD.asItem());

主要还是物品的标签,方块的加到LOGS_THAT_BURN标签中即可

木板加到物品标签中的PLANKS标签中,用于合成木棍之类的

另外标签是加入到燃料中

模型文件

1
2
3
4
blockStateModelGenerator.registerLog(ModBlocks.ICE_ETHER_LOG).log(ModBlocks.ICE_ETHER_LOG).wood(ModBlocks.ICE_ETHER_WOOD);
blockStateModelGenerator.registerLog(ModBlocks.STRIPPED_ICE_ETHER_LOG).log(ModBlocks.STRIPPED_ICE_ETHER_LOG).wood(ModBlocks.STRIPPED_ICE_ETHER_WOOD);
blockStateModelGenerator.registerSimpleCubeAll(ModBlocks.ICE_ETHER_PLANKS);
blockStateModelGenerator.registerSimpleCubeAll(ModBlocks.ICE_ETHER_LEAVES);

原木和木头的特殊一点,用registerLog来注册它们,然后用logwood方法分别传入原木和木头

木板和树叶就很简单了,用registerSimpleCubeAll方法来注册,它们是六面相同的

测试

那么跑完数据生成之后,放好材质文件,注意原木的材质文件分为顶(底)面和侧面,有两个

然后就可以去游戏里测试了