前言
上一期,我们已经准备好了开发环境。这一期我们将带领大家进行代码实现。本期我们将尝试在Minecraft中添加物品。
实现
Mod 入口点
什么是Mod入口点呢。其实这是笔者形象表述的,而非一个专业术语。读者不妨自己设想一下,既然给出一个Mod,自然是要按照代码运行。那么问题来了,该从哪里开始运行呢?这样的一个起始点,我们就称之为Mod入口点。
学过编程的同学都知道,程序的运行需要从一个入口点开始,在面向过程的语言中,这个点通常是入口函数;而对于面向对象的语言,这个点则通常是一个对象的某个方法。开发Minecraft Mod,我们使用的是Java语言,它是一款面向对象的语言。因此,我们需要为Mod的运行提供一个入口点方法。
如上文所述,入口点方法是存在于某个对象中的,我们不妨在项目试图里打开src/main/java/org/tutorial/tutorialmod
我们可以发现,默认情况下,这个路径对应的有一个源代码和一个包含了源代码的文件夹。原因在于,我们在上一期创建Mod时,在Environment
一栏选择的时Both选项,即客户端和服务端。Mod运行分为客户端运行和服务端运行。对于普通玩家而言,游玩的都是客户端,而对于服务器而言,则运行服务端。具体原理这里不过多赘述。这里只以客户端作为讲解。
我们打开TutorialModClient文件,可以发现文件中已经为我们定义好了TutorialModClient类。这就是我们这个Mod(项目)的对象。接下来就是提供入口方法了。由于Fabric有一套自己的API,我们只需使用Fabric提供的API即可。因此,我们需要在文件开头导入Fabric API
import net.fabricmc.api.ClientModInitializer;
这里导入的是Fabric为我们准备好的Mod初始化的模板。我们需要使Mod对象遵循这个模板进行初始化,只需如下即可:
public class TutorialModClient implements ClientModInitializer {
@Override
public void onInitializeClient() {
}
}
值得注意的是,@Override
是一种注解,表示以下方法重写了父类的方法(即我们导入的模板中要求给出的方法)。这个方法原型是固定的,必须是void
型,且方法名必须是onInitializeClient
。
【服务端对应的模板是ModInitializer
,其入口方法为onInitialize
】
接下来我们可以进行编译运行。按下Ctrl+F9
或手动构建。构建成功后,我们选择Minecraft Client
任务运行。为了方便观察Mod是否加载以及便于后期对Mod信息修改。这里推荐大家安装Mod Menu,选择1.16.5 Fabric版本。安装后运行界面如下。
到这里,Mod的入口点问题已经解决了!
注册表机制
Minecraft中采用注册表机制(与Windows系统注册表不完全类似)。注册表分为很多种。具体种类参见如下链接。
tutorial:registry_types [Fabric Wiki]
名称空间
使用give
命令给予玩家物品时,还记得我们是如何表示物品的吗?比方说,铁锭我们的表述为’minecraft:iron_ingot’。冒号前我们称之为名称空间namespace
。
那么名称空间是用来干什么的呢?简单来说就是为了兼容的。有时我们发现,一个模组加了一个物品比如说钢片,另一个模组也有这个物品但却不是同一个,总不能一个叫钢片的物品被两个模组注册吧。所以就有了名称空间的说法,每个模组都有一个自己的名称空间,注册的物品也都属于这个空间,就不会和其他模组的同名物品冲突(尽管玩家看到相同的连个物品依旧困扰无奈)。
Coding…
知道了以上几点知识,我们大致有了开发的思路。接下来让我们用代码实现吧!
首先,让我们来实现Mod入口点。Fabric是一个成熟的框架,它为我们准备好了一切,我们只需要照着模子一点点雕刻即可。创建一个类,并指定接口。
public class TutorialModClient implements ClientModInitializer {
}
接下来,我们需要具体实现onInitializeClient
方法。注意方法名前要加上@Override
表示对接口的实现。
@Override
public void onInitializeClient() {
}
构建项目,同时选择执行Minecraft Client
任务,有的同学的“Build”窗口中输出很多下载信息,那是项目正在补全所需文件及环境。过一会,就会弹出一个Minecraft游戏窗口。为了方便以后的开发,这里推荐安装一个Mod——Mod Menu
。
进入游戏后在主菜单选择模组即可看到我们自己写的Mod。
注册物品
我们在TutorialModClient
下创建一个类TutorialItems
用于存放我们需要注册的物品并且提供一个注册的方法。代码如下:
public class TutorialItems {
public static final Item BRONZE_INGOT = new Item(new Item.Settings().group(ItemGroup.MISC).maxCount(64));
public static void RegisterItems()
{
Registry.register(Registry.ITEM,
new Identifier("tutorial", "bronze_ingot"),
BRONZE_INGOT);
}
}
这里我们创建了一个物品Item
用来存放物品信息,该变量称之为BRONZE_INGOT
青铜锭。按照Java的初始化方法,我们调用构造方法Item()
,其参数是类型是Item.Settings
,接下里我们需要弄明白为什么会这么写,以及各个方法设置的属性如何。
这里为大家扒了Item.Settings
的代码。方法的实现已经略去,注释即为解释。
public class Item
implements ItemConvertible {
public static class Settings {
private int maxCount = 64; //物品最大堆叠数量
private int maxDamage; //物品最大耐久值
private Item recipeRemainder; //合成剩余物,合成物品后返还给玩家的物品
private ItemGroup group; //物品所在的组
private Rarity rarity = Rarity.COMMON; //稀有度
private FoodComponent foodComponent; //食品属性,能让物品变得可食用
private boolean fireproof; //是否防火、防岩浆,能否被其破坏
//以下分别是修改上述属性的方法
public Settings food(FoodComponent foodComponent) {...}
public Settings maxCount(int maxCount) {...}
public Settings maxDamageIfAbsent(int maxDamage) {...}
public Settings maxDamage(int maxDamage) {...}
public Settings recipeRemainder(Item recipeRemainder) {...}
public Settings group(ItemGroup group) {...}
public Settings rarity(Rarity rarity) {...}
public Settings fireproof() {...}
}
}
观察可以发现,其中所有方法返回的对象都是Settings
,同时,源文件里所有方法的返回也都是this
,也就说,方法执行完以后,把自己所在的对象给出去,是不是就相当于一种传递的过程?因此,设置Item
属性时,创建Item
的子类Settings
,接下来直接调用方法进行属性设置即可。那么同时呢,介于这种方法的传递性质,实际上调用的顺序是没有硬性要求的。
举些例子,方便大家理解(只保留关键代码):
//创建一个物品,使得其最大堆叠为16、防火、属于杂项
new Item(new Item.Settings().maxCount(16).fireproof().group(ItemGroup.MISC)
//创建一个物品,使得其最大耐久200、史诗级稀有度、属于武器
new Item(new Item.Settings().rarity(Rarity.EPIC).group(ItemGroup.COMBAT)
接下来就是将物品真正地加入MC世界中,我们可以看到使用了Registry
类中的register
方法。Registry
表示的就是注册表,register
表示注册。我们来看看注册所需要的参数。
这里给出了方法原型:
public static <V, T extends V> T register(net.minecraft.util.registry.Registry<V> registry, net.minecraft.util.Identifier id, T entry);
参数的类型很长,没关系,我们通俗解释即可。第一个参数registry
,表明我们要注册的类型,具体类型参考上文已给出的关于注册表类型的链接。这里我们要注册一个物品Item
,所以传入Registry.ITEM
;接下来是id
,这相当于为物品分配一个身份(ID),那由于这个物品我们是自己新加的,没有自己的身份,所以,我们新建一个——new Identifier("tutorial", "bronze_ingot")
。前者字符串是确定这个物品所在的命名空间,后者是物品的id;最后一个参数entry
,其实这就是我们要注册的对象的属性,想到属性,我们是不是在前面已经创建了一个BRONZE_ITEM
,传入即可。
物品已经注册完毕,但细心地读者想必想到了一个问题,我们并没有给出物品的材质。
因此,我们现在来解决这个问题。
在项目resources
目录下创建如下目录及文件。
其中,bronze_ingot.png
就是青铜锭的材质,icon.png
Mod本身的图标。bronze_ingot.json
是来指定物品材质的。
首先展示一下这两张图片。
接下来,我们给出bronze_ingot.json
的内容。
{
"parent": "item/generated",
"textures": {
"layer0": "tutorial:item/bronze_ingot"
}
}
编译并点击启动Minecraft Client
,随便进入一个存档,我们可以找到,在杂项目录下,出现了我们自己加入的青铜锭。
至此,我们成功添加了一个物品。