上回书说到
彼时的璃月,海中有大魔侵扰,山间有恶魑盘踞……等下,我拿错稿子了?
回到正题,之前我们成功让模组跑起来了,今天咱们来点物品相关的东西。
你的第一个物品
代码实现部分
我们先在模组本体文件的package下新建一个item package,然后新建一个名为ModItems的java类:
我们需要定义一个Register变量来注册这个类的所有物品:
public static final DeferredRegister<Item> ITEMS =
DeferredRegister.create(ForgeRegistries.ITEMS, TutorialMod.MOD_ID);
我们浅浅地分析以下为什么要使用如上格式。首先,我们需要了解Java中各种变量类型及修饰符的作用。
借着这个机会学java
常规变量如int,bool等都和C系语言特别类似,最大的差别就是适用于面向对象的,不论函数还是变量,又或者说类和对象都能使用的修饰符。访问修饰符如下:
default (即默认,什么也不写): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
private : 仅在其所属类内可见与访问。使用对象:变量、方法。 注意:不能修饰类(外部类)
public : 对所有类可见,能够被任何其它类访问,其所在方法也必须是共有类型。使用对象:类、接口、变量、方法
protected : 对同一包内的类和所有子类可见,但其它类对其的调用权限由其子类的调用方式决定,正常来讲,protected变量只有该类本身及其子类允许正常访问。使用对象:变量、方法。 注意:不能修饰外部类,接口及接口的成员变量和成员方法不能声明为 protected。
非访问修饰符如下:
static : 使对象独立于其它对象成为静态对象,它在同一类当中独一无二,且无法主动互动非静态对象,但能被非静态对象调用。
final : 使对象成为可以被引用,但无法被二次赋值的“常变量”,此变量必须被赋初值。
abstract : 不能用于定义对象,但可以用于定义类和方法,使之在代码层面上“可拓展”
synchronized : 使被定义的方法只能同时被一个线程访问
transient : 序列化的对象包含被 transient 修饰的实例变量时,java 虚拟机(JVM)跳过该特定的变量。
volatile : 修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值。而且,当成员变量发生变化时,会强制线程将变化值回写到共享内存。
java由于面向对象,所以对于“面向过程”语言的“函数”和“变量”概念的重新解读会比较抽象。举个简单的例子,在你的书架上,你往往会把文具、学习用书和休闲书
分置,则这三堆物件为所谓的“类”,而具体到铅笔、橡皮之类的文具则是“文具类”下的
“对象”,同时也是对“类”所定义的概念具现化的一种“实例”。
也正因如此,一般情况下,类与类之间并不互通,所以你书架上的物品都是private变量。直到有一天,你搞到了一本电子书,你非常纳闷该把它放哪,因为它既符合文具类“物件”的用途,又有作为学习用书、休闲书使用的需要。那么,电子书就是一个public
变量。但是你习惯用电子书了,学着学着,又意识到在上大课的时候看武侠小说会卷不过同学,于是就把电子书里的各种小说扬了,只用来存取电子课本,这时它又变成了一个protect变量。但其实,电子书一开始什么都没做,它纯粹根据你的需要被定义成了各种样式,所以,它一开始也只是一个default变量。
如果一本书上的内容不需要你进行增删,甚至是校方严格规定的,比方说你的学生手册,那么它就是一个final变量,再比如,你为了完成你的毕业设计,不知从哪找来了一篇没有复本的冷门论文,你的毕设随时都要用到它,但你不可能用它的内容去修改你书架上其它已经发布的论文,它就是一个static变量。
总之,java对各种量和函数的定义非常复杂,结合现实实际抽象理解,可能会帮到你不少。
回到正题
由是,我们在设计的时候往往会想到,一个注册端口必须是稳定,启动后不可变动,并且可被其它类随意调用的(想想看你坐飞机,你也不希望起飞前半个小时突然告诉你登机口变更,其他人要是赶时间,肯定更不想),所以它就很适合被定义为一个public static final型的变量。同样的,对于我们注册进列表中的每一个物品也是如此。
至于DeferredRegister,它则是forge提供的注册你的minecraft对象的方式。它在RegisterEvent期间维护你提供的各类物品的列表,并将其注册为游戏内物品。详细参数可以参考forge官网的教程以及网络上各路dalao的教程。
DeferredRegister.create需要提供两个参数,一个是你需要调用的注册表的类型,物品用ITEMS,方块用BLOCKS,以此类推。另一个则是你的MODID,这个变量之前就提到过,贯穿你的整个modding过程,所以,如果想修改,现在还来得及。
之前提到过,forge mod是基于对游戏本体的订阅和消息处理来完成与本体的交互的,所以我们在此基础上再加上对订阅消息的接口:
public static void register(IEventBus eventBus){
ITEMS.register(eventBus);
}
IEventBus是OpenMod API提供的对接方式之一,用于事件的订阅和管理。
既然物品类都加上了物品对象的订阅接口,本体自然也是要加的,我们在负责订阅、进程监听与交互的TutorialMod方法中加上:
ModItems.register(modEventBus);
接下来,回到ModItems类,我们将我们的第一个物品塞进注册表(doge只是我起的名字,各位可以换成其他的名称):
public static final RegistryObject<Item> DOGE = ITEMS.register("doge",
() -> new Item(new Item.Properties()));
至于Item类下的对象及方法,可以在Intellij中使用Ctrl+Shift+A查找对应类查看:
Item下的Properties子类应该算是其中最基础的方法了,用于定义一个物品最为基础的状态。当然,如果你想设计一个定义上占据小容量的轻小物件,比方说某种细小的螺丝钉,你可以设计一个子类,将maxStackSize参数调到256,这里就不细说了。
public static class Properties {
int maxStackSize = 64;
int maxDamage;
@Nullable
Item craftingRemainingItem;
Rarity rarity = Rarity.COMMON;
@Nullable
FoodProperties foodProperties;
boolean isFireResistant;
FeatureFlagSet requiredFeatures = FeatureFlags.VANILLA_SET;
private boolean canRepair = true;
public Item.Properties food(FoodProperties p_41490_) {
this.foodProperties = p_41490_;
return this;
}
在做完定义之后,我们还需要让它在创造模式下可视以及可存取,于是我们在Mod本体类下的addCreative加上如下代码:
if(event.getTab() == CreativeModeTabs.INGREDIENTS){
event.accept(ModItems.DOGE);
}
这样,我们就可以在原版的创造模式物品列表里(INGREDIENTS特指材料列表,1.19.3以后的创造列表和之前有较大出入,调用时还请留意)看到它的身影了。
完全代码如下(package取决于你自己定义的模组名称):
package net.drivenhank.tutorialmod.item;
import net.drivenhank.tutorialmod.TutorialMod;
import net.minecraft.world.item.Item;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;
public class ModItems {
public static final DeferredRegister<Item> ITEMS =
DeferredRegister.create(ForgeRegistries.ITEMS, TutorialMod.MOD_ID);
public static final RegistryObject<Item> DOGE = ITEMS.register("doge",
() -> new Item(new Item.Properties()));
public static void register(IEventBus eventBus){
ITEMS.register(eventBus);
}
}
ps:一定要确保引用的class或者库是正确的,否则就会出现一些不必要的接口调用混乱等情况,并不是作者亲身经历过,但防患于未然
ps:Intellij的代码补全真是好用的一批
资源部分
且慢,是不是少了什么?没错,是模型。更准确来讲,是物品显示的资源文件。
莫得模型的物品没有灵魂,在mc里的显示也只会是undefine默认的紫黑块。我们来看看官方是怎么配置模型相关的资源文件的。
如下,我们需要仿照官方的格式去列出调用的资源包:
如图,blockstates代表方块类物品放置后呈现的不同状态下的样式,lang代表不同语言下的物品名称显示,models即模型,textures即材质。
先从简单的物品名开始,中国人不放洋屁,但洋老爷看不懂中文,所以我们需要两个json语言包,分别命名为en_us和zh_cn。
设定好的语言文件格式如下:
然后,我们需要设定一下物品的模型调用,格式如下:
“parent”代表我们调用的模型父类,这里使用了一个通用的模板item/generate。“layer”则代表模型层数,这里只定义了“layer0”,是一个比较简单的模型。
当然,如果你不怕设计复杂模型,你甚至可以定义一个多layer的物品,当然,你背后的美工,或者你自己的工作量可能就大了起来。
顺带一提,你在翻阅客户端文件以作参考的时候,可能会发现一些特殊物品的模型没有对应的layer调用,比方说美西螈刷怪蛋:
{
"parent": "minecraft:item/template_spawn_egg"
}
这应该是程序员为了节省工作量,而对刷怪蛋的模型作了统一定义,于是只需要调用统一模型的公用模板,代码方面会自动生成其变体。至于方块类物品,甚至是门、船之类模型特殊的物品的定义,就留到之后再说吧。
再者,我们需要制作一个模型。这里你可以使用各种在线插件,但如果你美工能力够自信,你可以直接使用PS(反正我是这么干的),做好的模型按设定的物品id命名,存入textures文件夹。顺带,minecraft支持偶数倍数大小的方形模型文件,但你最好不要设计一个超过32*32的模型(除非像拔刀剑那样有比较高效的自定义统一模板)
将模型图片放到texture下,我们就可以打开游戏(gradle runClient),看到我们可爱的doge了:
额外的创造模式列表
但是,有一个小问题,我们平常所熟知的mod,除非是用于多mod在原版物品上的合成兼容,否则基本都有自己的创造模式物品栏。
对此,我们需要额外创建一个类来构建自己的物品栏。在item目录下创建ModCreativeModeTabs类,然后输入如下代码:
package net.drivenhank.tutorialmod.item;
import net.drivenhank.tutorialmod.TutorialMod;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.event.CreativeModeTabEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
@Mod.EventBusSubscriber(modid = TutorialMod.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD)
public class ModCreativeModeTabs {
public static CreativeModeTab TUTORIAL_TAB;
@SubscribeEvent
public static void registerCreativeModeTabs(CreativeModeTabEvent.Register event){
TUTORIAL_TAB = event.registerCreativeModeTab(new ResourceLocation(TutorialMod.MOD_ID, "tutorial_tab"),
builder -> builder.icon(() -> new ItemStack(ModItems.DOGE.get()))
.title(Component.translatable("creativemodetab.tutorial_tab")));
}
}
如上,我们设定了一个订阅接口,用于模组文件的交互,又设定了一个全新的事件,用于创建一个新的创造模式物品栏分类,调用的部分包括模组id,模组标签图片,以及语言栏里的标题。具体格式及源文件,各位可以在自有库中查阅,本文不再赘述。
同时,我们需要把模组本体文件中addCreative的event get改成:
if(event.getTab() == ModCreativeModeTabs.TUTORIAL_TAB){
event.accept(ModItems.DOGE);
}
语言栏里也要加上ItemStack.title调用的标题:
"creativemodetab.tutorial_tab": "Doge Doge"
最终效果如下:
如是,我们终于得到了我们的第一个物品。
这一期的内容似乎有点复杂,但别急,更复杂的还在后头。
mc除了物品,还有方块,方块有更复杂的物理、模型和功能需要我们处理,这些留到接下来再说。