本篇教程的视频

本篇教程的源代码

GitHub地址:TutorialMod-Block-1.20.1

本篇教程目标

  • 理解原版方块的注册
  • 理解方块方块物品的关系,以及方块物品的注册
  • 学会简单的方块状态文件的编写
  • 学会简单的方块模型方块物品模型文件的编写

查看源代码

这期教程我们来添加第一个方块

方块比物品稍微复杂一点,不过没关系,我们还是先来看看源代码

这里我们要找的自然是Blocks类,这个类注册了原版所有的方块

我们还是先挑一个来看看,就以STONE为例吧

1
2
3
public static final Block STONE = register(
"stone", new Block(AbstractBlock.Settings.create().mapColor(MapColor.STONE_GRAY).instrument(Instrument.BASEDRUM).requiresTool().strength(1.5F, 6.0F))
);

注册方法

首先,我们来看register方法

1
2
3
public static Block register(String id, Block block) {
return Registry.register(Registries.BLOCK, id, block);
}

是的,它就这么一个方法,并不像物品注册中那样层层堆叠的

它这里用的是Registry.register的另一个重载方法

1
2
3
4
5
6
7
8
9
10
11
12
static <T> T register(Registry<? super T> registry, String id, T entry) {
return (T)register(registry, new Identifier(id), entry);
}

static <V, T extends V> T register(Registry<V> registry, Identifier id, T entry) {
return (T)register(registry, RegistryKey.of(registry.getKey(), id), entry);
}

static <V, T extends V> T register(Registry<V> registry, RegistryKey<V> key, T entry) {
((MutableRegistry)registry).add(key, entry, Lifecycle.stable());
return entry;
}

我们将这几个放一起看,方块注册用的是第一个,第一个调用第二个,第二个再调用第三个

我们自己写的时候呢,因为要传入我们模组的命名空间,所有还是用第二个就好了

方块属性

那么现在,我们回过头去看看方块注册了哪些属性

1
2
3
4
5
6
7
8
public static final Block STONE = register(
"stone", new Block(
AbstractBlock.Settings.create()
.mapColor(MapColor.STONE_GRAY)
.instrument(Instrument.BASEDRUM)
.requiresTool()
.strength(1.5F, 6.0F))
);

我们从new Block开始看,
这里调用了AbstractBlockSettings中的create方法,
这是抽象方块类中嵌套的,用于创建方块属性的一个类

mapColor是显示在地图上方块的颜色,里面的参数也是原版已经定义好的

instrument是音符盒放在方块上时,音符盒发出的音色

requiresTool是方块是否需要工具才能破坏,这个我们会在战利品列表的相关教程中重点讲解

strength是方块的强度,第一个参数是方块硬度,第二个参数是方块韧性
这第一个参数决定方块的挖掘所需的时间,第二个参数可以理解为爆炸抗性
TNT爆炸或者苦力怕爆炸时,这个参数会决定方块是否被破坏

岩浆黑曜石基岩等方块的爆炸抗性为100
所以它们是无法被爆炸破坏的(原版)

其他的方块会有别的一些属性,不过这里我们就先讲这么一点,
感兴趣的同学可以先自己研究,未来我们遇到其他方块时,再来讲解

方块物品

那么,方块还没完,我们之前看物品注册时,看到过一堆方块物品,即BlockItem

因为在我们游戏中,方块是只有放置在世界中时,它才能被称为方块
但当它在物品栏中时,它还是物品

像游戏中的熔炉高炉等方块,只有当我们放下它时,我们才能与其交互,
但它的物品栏中时,也就只是普普通通的物品

这里我们还是看STONE,只是现在是在Items类中

1
public static final Item STONE = register(Blocks.STONE);

方块物品注册

这里调用了Items类中的register方法,当然它也叠了好几层

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 Item register(Block block) {
return register(new BlockItem(block, new Item.Settings()));
}

public static Item register(BlockItem item) {
return register(item.getBlock(), item);
}

public static Item register(Block block, Item item) {
return register(Registries.BLOCK.getId(block), item);
}

public static Item register(Identifier id, Item item) {
return register(RegistryKey.of(Registries.ITEM.getKey(), id), item);
}

public static Item register(RegistryKey<Item> key, Item item) {
if (item instanceof BlockItem) {
((BlockItem)item).appendBlocks(Item.BLOCK_ITEMS, item);
}

return Registry.register(Registries.ITEM, key, item);
}

虽然说这里叠了好多,但有意思的是,它最后还是调用了Registry.register方法,殊途同归了

那么同样的,它还是能够合并简化一下的

只是,与一般物品不同的是,在第一个方法中实例化的,并不是Item,而是BlockItem,即方块物品

BlockItem需要我们传入两个参数,第一个是Block,即方块
第二个是Item.Settings,即物品属性,这倒是和之前的差不多

在最后一个方法中,有一个if语句,用于判断当前物品是否为BlockItem的实例,
如果是,则调用appendBlocks方法,将当前物品添加到Item.BLOCK_ITEMS

这个是Item类中的一个Map<Block, Item>类型的变量,
用于存储方块及其对应物品的键值对

因为在游戏中,我们是使用方块物品来放置对应的方块

好了,源代码要了解的基本上就这一些了,接下来我们就来写我们自己的方块吧

方块注册

创建ModBlocks类

我们创建一个ModBlocks类,用于注册我们模组的方块

1
2
3
public class ModBlocks {

}

注册方块方法

接下来我们创建方块的注册方法,按照原版的来写就可以,不过要将我们模组的命名空间加入

1
2
3
public static Block register(String id, Block block) {
return Registry.register(Registries.BLOCK, new Identifier(TutorialMod.MOD_ID, id), block);
}

注册方块物品方法

注册方块物品的话,你可以像原版那样,给它分开来写,也可以写在这里

这里的话,因为我们的方块物品暂时还没有特殊用途,所以就先写在ModItems中了

(这里的特殊用途,例如作物中的马铃薯胡萝卜,它们既可以作为食物
又与相应的作物方块相联系)

我们先写一个方块物品的注册方法

1
2
3
4
public static void registerBlockItems(String id, Block block) {
Registry.register(Registries.ITEM, new Identifier(TutorialMod.MOD_ID, id),
new BlockItem(block, new Item.Settings()));
}

这里实例化的是BlockItem,而不是Item,写法上是和Items中注册方法一样的,
另外关于Items中最后一个方法中的if,你也可以写在这里,也可以不写

Fabric底层的Mixin会将没有加入到Item.BLOCK_ITEMS的方块物品和方块加入其中

随后我们在上面的注册方块的方法中调用这个方法

1
2
3
4
public static Block register(String id, Block block) {
registerBlockItems(id, block);
return Registry.register(Registries.BLOCK, new Identifier(TutorialMod.MOD_ID, id), block);
}

这样在我们注册方块的同时,方块物品也会被注册了

方块

接下来我们就来注册我们的方块

1
2
3
public static final Block ICE_ETHER_BLOCK = register("ice_ether_block", new Block(AbstractBlock.Settings.copy(Blocks.STONE)));
public static final Block RAW_ICE_ETHER_BLOCK = register("raw_ice_ether_block", new Block(AbstractBlock.Settings.create().strength(0.2f, 0.2f)));
public static final Block ICE_ETHER_ORE = register("ice_ether_ore", new Block(AbstractBlock.Settings.create().strength(3.0f, 3.0f)));

这里我们直接写了3个,这些都是我们的老朋友了

第一个ICE_ETHER_BLOCK,我们直接复制了STONE的属性,也就是用AbstractBlock.Settings.copy方法

那么这里的ICE_ETHER_BLOCK就具备原版STONE的属性,像地图颜色方块强度音色等都复制给了我们的方块

那么另外的两个,我们就直接用AbstractBlock.Settings.create方法,从头创建一些方块的属性

初始化方法

那么和物品注册一样,因为现在这个类没有被调用,而要完成方块的注册,这个类就必须初始化

所以和ModItems中一样,我们要创建一个用于初始化的方法

1
2
3
public static void registerModBlocks() {

}

并在我们的模组主类onInitialize方法下调用

1
2
3
4
5
6
7
8
9
@Override
public void onInitialize() {
ModItems.registerItems();
ModItemGroups.registerGroups();

ModBlocks.registerModBlocks();

LOGGER.info("Hello Fabric world!");
}

加入物品栏

最后,我们还要将我们的方块加入到物品栏

1
2
3
4
5
6
7
8
9
10
11
12
public static final ItemGroup TUTORIAL_GROUP2 = Registry.register(
Registries.ITEM_GROUP,
new Identifier(TutorialMod.MOD_ID, "tutorial_group2"),
ItemGroup.create(null, -1)
.displayName(Text.translatable("itemGroup.tutorial_group2"))
.icon(() -> new ItemStack(ModItems.CARDBOARD))
.entries((displayContext, entries) -> {
...
entries.add(ModBlocks.ICE_ETHER_BLOCK);
entries.add(ModBlocks.RAW_ICE_ETHER_BLOCK);
entries.add(ModBlocks.ICE_ETHER_ORE);
}).build());

这样我们就不用使用命令来获取我们的方块了

好了,到这里,我们的方块就已经注册完成

当然,现在进游戏也只是一堆黑紫块,我们还要处理它们的资源文件

资源文件

方块状态文件

方块状态BlockState)是方块特有的,它决定着方块应当以哪种模型显示

比如楼梯方块,它会根据你放置的位置及周边的方块,来决定它的方块模型

栅栏类的方块同理,也是根据它的方块状态决定它四个方向是否连接

不过,方块状态将会在我们未来的教程中重点讲解,它是要和方块类、方块属性配合起来使用的

这里呢,我们就写一个最简单的方块状态文件,我们在assets/<modid>下创建一个blockstates文件夹

再在其中创建ice_ether_block.json文件,并写入以下内容

1
2
3
4
5
6
7
{
"variants": {
"": {
"model": "tutorial-mod:block/ice_ether_block"
}
}
}

这个就是最简单的方块状态文件,感兴趣的同学也可以先去看看楼梯栅栏等方块的方块状态文件

model指向的对应的方块模型

不过,方块状态也是分为两类的,一类是我们常用的variants,即变种
另外一种还有是栅栏类用的multipart,即多部分

这两个的区别我们也会在未来的教程中来讲

另外两个方块的方块状态也是类似的

ICE_ETHER_ORE

1
2
3
4
5
6
  "variants": {
"": {
"model": "tutorial-mod:block/ice_ether_ore"
}
}
}

RAW_ICE_ETHER_BLOCK

1
2
3
4
5
6
7
{
"variants": {
"": {
"model": "tutorial-mod:block/raw_ice_ether_block"
}
}
}

方块模型文件

接下来在我们之前创建的models文件夹下创建block文件夹,
在下面就放我们的方块模型文件

我们创建ice_ether_block.json文件,并写入以下内容

1
2
3
4
5
6
{
"parent": "minecraft:block/cube_all",
"textures": {
"all": "tutorial-mod:block/ice_ether_block"
}
}

这里我们还是用到了parent,指向的是父模型,这里我们使用的cube_all
即常规的立方体模型,六个面的材质都是一样的

textures中的all,指向的是材质
这里我们使用的是tutorial-mod:block/ice_ether_block

另外的两个同理

ICE_ETHER_ORE

1
2
3
4
5
6
{
"parent": "minecraft:block/cube_all",
"textures": {
"all": "tutorial-mod:block/ice_ether_ore"
}
}

RAW_ICE_ETHER_BLOCK

1
2
3
4
5
6
{
"parent": "minecraft:block/cube_all",
"textures": {
"all": "tutorial-mod:block/raw_ice_ether_block"
}
}

语言文件

语言的翻译也差不多

1
2
3
4
5
{
"block.tutorial-mod.ice_ether_block": "Ice Ether Block",
"block.tutorial-mod.raw_ice_ether_block": "Raw Ice Ether Block",
"block.tutorial-mod.ice_ether_ore": "Ice Ether Ore"
}

和物品不一样的,它是由block.命名空间.方块id组成的

其他都是一样的

方块材质文件

同样也在textures文件夹下新建block文件夹,然后在下面放我们的方块材质

测试

现在我们就可以启动游戏来测试了,方块物品不是黑紫块
在世界中放置方块是正常显示材质的

那么我们的方块就已经注册完成

如果是黑紫块,那么就看日志,日志中肯定会有WARN警告),
提示你文件找不到或者材质加载错误等

按照它提示的来解决即可