本篇教程的视频:

本篇教程源代码

GitHub地址:TutorialMod-Wood-1.21

介绍

木头作为我们在游戏初期必不可少的资源,想必大家都很熟悉了吧

本篇教程我们将会增加原木木头木板树叶,为之后的以及树的世界生成做准备,毕竟树最基本的组成部分就是原木和树叶

木头其实有很多,有原木去皮原木木头去皮木头这四类,衍生的还有木板

木头和原木的区别在于其是否六面皆为树皮纹理,当然,自然生成的只有原木

查看源代码

我们来看看原版和木头有关的注册方法

原木

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 Block createLogBlock(MapColor topMapColor, MapColor sideMapColor) {
return new PillarBlock(
AbstractBlock.Settings.create()
.mapColor(state -> state.get(PillarBlock.AXIS) == Direction.Axis.Y ? topMapColor : sideMapColor)
.instrument(NoteBlockInstrument.BASS)
.strength(2.0F)
.sounds(BlockSoundGroup.WOOD)
.burnable()
);
}

我们可以看到,其注册方法也是和前面一样的,只是后面返回的block用一个createLogBlock方法提炼出来了

这个方法返回的是一个PillarBlock,这个类是一个柱状方块,也就是我们常见的原木,上下两面的材质和侧面的材质是不一样的

这里的设置多了一个burnable方法,这个方法是用来设置方块是否可以被火点燃的,因为木头是可燃物嘛

去皮原木同理

木头

1
2
3
4
5
6
7
8
9
10
11
12
13
public static final Block OAK_WOOD = register(
"oak_wood",
new PillarBlock(
AbstractBlock.Settings.create().mapColor(MapColor.OAK_TAN).instrument(NoteBlockInstrument.BASS).strength(2.0F).sounds(BlockSoundGroup.WOOD).burnable()
)
);

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

木头和原木的注册方法基本一样,也是实例化一个PillarBlock

不过木头明明是六面材质相同的方块,为什么这里还是PillarBlock呢?(说实话我也不知道)

木板

1
2
3
4
5
6
public static final Block OAK_PLANKS = register(
"oak_planks",
new Block(
AbstractBlock.Settings.create().mapColor(MapColor.OAK_TAN).instrument(NoteBlockInstrument.BASS).strength(2.0F, 3.0F).sounds(BlockSoundGroup.WOOD).burnable()
)
);

木板就常规多了,就是一个普通的方块,不过这里也是burnable

树叶

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static final Block OAK_LEAVES = register("oak_leaves", createLeavesBlock(BlockSoundGroup.GRASS));

public static Block 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)
);
}

树叶的注册方法和原木类似,用一个createLeavesBlock方法提炼出来

nonOpaque方法是用来设置方块是否为实心的,显然树叶是空心的,所以这里是nonOpaque

allowsSpawning方法是用来设置方块是否允许生物生成在其上的,这里的判断方法是canSpawnOnLeaves

suffocates方法是用来设置方块是否会使生物窒息的,这里设置的是never,也就是不会,方块默认是会使生物窒息的

blockVision方法是用来设置方块是否会阻挡视线的,这里也是不会

pistonBehavior方法是用来设置方块被活塞推动时的行为,这里是DESTROY,也就是会被破坏

solidBlock方法是用来设置方块是否为实心方块的,这里也不是

树叶额外设置的方法比较多,当然,如果你不想写这么一串,就直接用这里的方法或者copy原有方块

其他东西

原木与去皮原木

这里我们再往下挖掘一点,来看看原木去皮原木有什么关系

我们可以在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();

我们可以看到,这里是一个Map,用来存储原木去皮原木的对应关系

但是这玩意是protected的,根据我们之前的经验,用Mixin

不过,其实Fabric已经有轮子(API)了,那我们就用呗,虽然Fabric的轮子没有Forge多,但有就用好了

可燃物方块

再挖一挖(其实能挖的东西很多很多),我们区看看这种可燃物方块的燃烧属性是怎么样的,我们可以在FireBlock中找到这样一段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void registerDefaultFlammables() {
FireBlock fireBlock = (FireBlock)Blocks.FIRE;
fireBlock.registerFlammableBlock(Blocks.OAK_PLANKS, 5, 20);

fireBlock.registerFlammableBlock(Blocks.OAK_LOG, 5, 5);

fireBlock.registerFlammableBlock(Blocks.STRIPPED_OAK_LOG, 5, 5);

fireBlock.registerFlammableBlock(Blocks.STRIPPED_OAK_WOOD, 5, 5);

fireBlock.registerFlammableBlock(Blocks.OAK_WOOD, 5, 5);

fireBlock.registerFlammableBlock(Blocks.OAK_LEAVES, 30, 60);
...
}

我们先解释一下这里的参数,第一个参数是方块,第二个参数是点燃这个方块的几率(有火源的情况下,比如有火在边上),第三个参数是传播给其他方块的几率(火势蔓延)

相比较而言,原木类烧得就慢一点,木板烧得就快一点,树叶烧得就更快一点

别急,我知道你想干什么,Fabric也给我们提供了相关的API

方块注册

那么现在我们就来注册这些方块吧

注册原木、去皮原木、木头、去皮木头、木板、树叶

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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_WOOD)));

public static final Block STRIPPED_ICE_ETHER_LOG = register("stripped_ice_ether_log",
new PillarBlock(AbstractBlock.Settings.copy(Blocks.STRIPPED_OAK_LOG)));

public static final Block STRIPPED_ICE_ETHER_WOOD = register("stripped_ice_ether_wood",
new PillarBlock(AbstractBlock.Settings.copy(Blocks.STRIPPED_OAK_WOOD)));

public static final Block ICE_ETHER_PLANKS = register("ice_ether_planks",
new Block(AbstractBlock.Settings.copy(Blocks.OAK_PLANKS)));

public static final Block ICE_ETHER_LEAVES = register("ice_ether_leaves",
new LeavesBlock(AbstractBlock.Settings.copy(Blocks.OAK_LEAVES)));

方便起见,我们这里全部copy原版的方块,这样就不用一个一个设置了

加入物品栏

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_PLANKS);
entries.add(ModBlocks.ICE_ETHER_LEAVES);

这里我们把这些方块加入到物品栏中,这样我们就可以在创造模式中找到这些方块了

注册原木与去皮原木的对应关系

这里我们就用FabricAPI来注册这些对应关系

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

StrippableBlockRegistry.register(ModBlocks.ICE_ETHER_WOOD, ModBlocks.STRIPPED_ICE_ETHER_WOOD);

我们将这段语句写在模组主类的onInitialize方法中,这样就可以在游戏加载时注册这些对应关系了

注册可燃物方块

不过这里我们写之前先做一个区分,我们的可燃物方块和前面讲的燃烧物不是一个东西

燃烧物是可以当作燃料使用的物品或者方块,而可燃物方块是可以被火点燃的方块,这俩还是有点区别的

我们在模组主类的onInitialize方法中注册这些可燃物方块

1
2
3
4
5
6
7
8
9
10
11
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);

这里的参数和原版的一样,这里就不再解释了

数据文件

语言文件

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");

Tags文件

不过,为了让我们的木头可以烧制成木炭,我们还需要一个logs_that_burn文件

不过,写了这个文件以后,就不用去写燃烧物的方法了,因为在原版中是用logs这个Tag来设置原木的燃烧属性的(木头、木板同理),而logs_that_burn是属于logs这个Tag

Block Tags

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);

Item Tags

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

两边都写一下,虽然差不多

模型文件

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方法,这里是对应的原木和木头

而木板和树叶则使用registerSimpleCubeAll方法,这里是一个简单的六面同材质的方块

另外,贴图文件不要忘记了,随后我们就可以启动我们的游戏了