注意:本文代码只表现个人实现方式及习惯,本文解释只体现个人理解,不一定符合规范(除非声明了这是规范)
自定义物品
一般物品
定义物品所属类
M
i
n
e
c
r
a
f
t
Minecraft
Minecraft中所有物品都继承类
I
t
e
m
Item
Item,我们要添加的物品也不例外,它需要有一个描述自己的类并且这个类也继承
I
t
e
m
Item
Item
具体操作为在<author>.<modid>.item包下新建一个ModItem.java并输入以下代码
package author.modid.item;
import author.modid.Modid; //调用Mod的主类
import net.minecraft.item.Item;
public class ModItem extends Item { //继承Item类
public ModItem() { //构造方法
this.setUnlocalizedName(Modid.MODID + ".modItem");
//设置本地化时.lang文件中夹在"item."和".name"中间那一段
//参数可以直接输入'"modid.modItem"'
this.setRegistryName("mod_item");
//设置物品ID(只要写ID中"modid:"后面的部分就可以了)
this.setMaxStackSize(64);
//设置最大堆叠数
}
}
//unlocalized name和registry name的命名规范可以从代码中看出
这样一个名为 M o d I t e m ModItem ModItem的类就创建好了
注册物品
创建好类之后还要在游戏启动时注册这种类的物品
具体操作为在<author>.<modid>.item包下新建一个RegistryOfItemHandler.java并输入以下代码
package author.modid.item;
import ... //可能调用的类会比较多,就不一一列举了
@EventBusSubscriber
public class RegistryOfItemHandler {
public static final ModItem MOD_ITEM = new ModItem(); //定义一个ModItem类的物品
@SubscribeEvent //监听
public static void onRegistry(Register<Item> event) { //注册物品
event.getRegistry().registerAll( //一次注册多个物品
MOD_ITEM
);
}
}
//MOD_ITEM的命名规范可以从代码中看出
添加材质
在RegistryOfItemHandler.java的 R e g i s t r y O f I t e m H a n d l e r RegistryOfItemHandler RegistryOfItemHandler类中增加一个方法,代码如下
@SubscribeEvent
@SideOnly(Side.CLIENT) //只用于客户端
public static void onModelRegistry(ModelRegistryEvent event) { //添加材质
ModelLoader.setCustomModelResourceLocation(MOD_ITEM, 0,
new ModelResourceLocation(MOD_ITEM.getRegistryName(), "inventory"));
//参数分别为注册的物品,物品的metadata,物品为该metadata时的资源位置(其中的"inventory"是针对物品的)
}
指定了位置之后就要在相应位置放入资源以供调用
先在main.resources.assets.modid下新建一个blockstates文件夹(虽然这是item),在文件夹中新建一个mod_item.json文件,输入以下内容
{
"forge_marker" : 1, //forge默认
"defaults" : {
"model" : "minecraft:builtin/generated", //说明外观与原版物品一样
"textures" : { "layer0" : "modid:items/mod_item" } //指定图片文件位置为items下的mod_item.png
},
"variants" : {
"inventory" : [{ "transform" : "forge:default-item" }] //说明显示方式与原版物品一样
}
}
指定了.png的位置后要在assets.modid新建textures文件夹,再在其下新建items文件夹,并在items文件夹里放入mod_item.png文件(一定要是.png),该文件像素一般为16x16,32x32也可以,这样你的物品就有了纹理
本地化
本地化十分简单,先在main.resources.assets.modid下新建一个lang文件夹,在文件夹中新建一个zh_cn.lang文件(该文件用于中文,en_us.lang用于英文),之后该mod所有的本地化都在此处完成
想必翻过mod的.jar文件的小伙伴已经知道这里要写什么了,对于刚才注册的物品,我们这么本地化:
item.modid.modItem.name=模组物品
含metadata的物品
设置不同材质
原版含metadata的物品有各种石头,各色的羊毛,床,染料等,它们本质都是一个物品在不同metadata时的不同样式
含metadata的物品的类在构造函数中需要加上这些
setHasSubtypes(true); //说明该物品含有metadata
setMaxDamage(0); //将最大损害值设定为0,避免一些奇妙的“修复物品的设备”产生的刷物品 Bug
setNoRepair(); //防止被修复。避免一些奇妙的“修复物品的设备”强行修复该物品
为避免每个含metadata的物品的类都要加入上述代码而略显麻烦,添加一个新的类
S
u
b
t
y
p
e
I
t
e
m
SubtypeItem
SubtypeItem(继承
I
t
e
m
Item
Item)并在它的构造方法中写入上述代码,再让那些含metadata的物品的类继承这个类即可
注册物品的方法和一般物品一样,但是要为每个metadata分别指定资源位置,这有多种方法,以下是我使用的类似一般物品的指定方法
先把“setCustomModelResourceLocation”的第二个参数改为相应的metadata
再把“setCustomModelResourceLocation”的第三个参数改为相应metadata的资源位置
想得到相应metadata的资源位置,可以在
S
u
b
t
y
p
e
I
t
e
m
SubtypeItem
SubtypeItem中添加一个方法,代码如下
public ResourceLocation getSubtypeName(String meta) {
return new ResourceLocation(this.getRegistryName().getResourceDomain(),
this.getRegistryName().getResourcePath() + meta);
//相应metadata的资源位置的前面部分一致(即ResourceDomain一样),只需要修改.json文件的名称(即ResourcePath),这里是在一般物品的名称后加入了一个名为meta的字符串
//此时你的编辑器可能会报错“方法调用 'getResourceDomain' 可能产生 'NullPointerException'”,这是因为this.getRegistryName()可能为空
//为了方便debug时知道何处为空,我们需要将ResourceLocation的第一个实参改为Objects.requireNonNull(this.getRegistryName()).getResourceDomain()
//因为第一个实参更改了,如果this.getRegistryName()为空,报错时就会具体指出其为空,所以第二个实参就不用更改了
}
将该方法整合入“setCustomModelResourceLocation”替代掉第三个参数中的"getRegistryName()",代码如下
ModelLoader.setCustomModelResourceLocation(MOD_ITEM, 1,
new ModelResourceLocation(MOD_ITEM.getSubtypeName("1"), "inventory"));
你可以设置"getSubtypeName"的参数为任意字符串,比如这里是让MOD_ITEM的metadata为1时的资源位置为一般物品的.json文件同目录下的"mod_item1.json",如果你设置"getSubtypeName"的参数为"_1",那么位置就会是同目录下的"mod_item_1.json"
最后是在同目录新建一个mod_item1.json文件,复制原来的内容,只需要把"{ “layer0” : “modid:items/mod_item” }“改为”{ “layer0” : “modid:items/mod_item1” }“(当然如果你的参数为”_1",上述的"1"就换为"_1")
但是当一个物品有效的metadata很多时就需要在onModelRegistry方法中添加很多条ModelLoader.setCustomModelResourceLocation(,),若此时刚好又有很多含metadata的物品,onModelRegistry方法就会显得臃肿且麻烦
为此可以在
S
u
b
t
y
p
e
I
t
e
m
SubtypeItem
SubtypeItem中添加一个方法,使用循环为每一个metadata分别指定材质位置,代码如下
public void setSubtypeResourceLocation(int maxData) { //传入最大的有效metadata,用于循环中止的条件
for (int i = 0; i <= maxData; ++i) { //分别为每个metadata指定材质位置
String meta = Integer.toString(i); //将整型i变为字符串
ModelLoader.setCustomModelResourceLocation(this, i,
new ModelResourceLocation(this.getSubtypeName(meta), "inventory"));
}
}
//如果你完全理解了这段语句的意思,你会发现metadata为0时会指向mod_item0.json
//你当然可以对metadata为0时单独指定位置,但是我为了方便就统一用循环了
这样onModelRegistry方法中就只需要写入
MOD_ITEM.setSubtypeResourceLocation(n); //n为最大的有效metadata
真是简单了不少呢
设置不同名称
想要为不同metadata设置不同的本地化需要重写
I
t
e
m
Item
Item的getUnlocalizedName方法(如果你查看forge的源码,会发现
I
t
e
m
Item
Item有好几个getUnlocalizedName方法,我们重写的是有一个
I
t
e
m
S
t
a
c
k
ItemStack
ItemStack实例作参数的)
在你为物品定义的类中加入这样一段代码
@Override //注解,用于表示此处为重写方法
@Nonnull //不可返回null
public String getUnlocalizedName(ItemStack stack) {
switch (stack.getMetadata()) { //得到这个ItemStack的metadata并判断
case 0 : return this.getUnlocalizedName() + ".blue";
case 1 : return this.getUnlocalizedName() + ".green";
case 2 : return this.getUnlocalizedName() + ".yellow";
default : return this.getUnlocalizedName() + ".red"; //为不同的metadata返回不同的unlocalizedname
}
}
这里的unlocalizedname是在原来的基础上为不同的metadata加了一个不同的".color",这个字符串仍然由你决定,不过要注意在.lang文件中的等号左侧也要进行相应的改变
此时你可能注意到了一个新的类
I
t
e
m
S
t
a
c
k
ItemStack
ItemStack,游戏中实际操作的对象是
I
t
e
m
S
t
a
c
k
ItemStack
ItemStack而不是
I
t
e
m
Item
Item,你可以在你为物品定义的类中定义一批
I
t
e
m
S
t
a
c
k
ItemStack
ItemStack实例,每个对应一个metadata,再在该物品类的构造方法中为每个
I
t
e
m
S
t
a
c
k
ItemStack
ItemStack实例赋值从而绑定matadata
定义实例:
public ItemStack blue, green, yellow, red;
//可惜这里不可以使用数组,导致下面不可以使用循环简化代码,我也不知道原因
在构造方法中添加:
this.blue = new ItemStack(this, 1, 0);
this.green = new ItemStack(this, 1, 1);
this.yellow = new ItemStack(this, 1, 2);
this.red = new ItemStack(this, 1, 3);
//三个参数分别表示所属物品,数量(我也讲不清楚,但是一般为1),metadata
这种方式无法使用循环,简直是太麻烦了
如果你查看forge的源码,会发现
I
t
e
m
D
y
e
ItemDye
ItemDye等含有metadata的物品类都重写了一个方法getSubItems,重写这个方法我们可以设置物品的subtype,所以我们也来重写
@Override
@ParametersAreNonnullByDefault //参数可以为null
public void getSubItems(CreativeTabs tab, NonNullList<ItemStack> items) {
if(this.isInCreativeTab(tab)) { //检验创造模式物品栏是否一致(我也不知道为什么要判断这个,但是源码有这个东西)
for(int i = 0; i < 4; ++i) {
items.add(new ItemStack(this, 1, i));
}
}
}
重写了这个方法后,就不再需要上面的定义实例和修改构造方法了,使用循环也使代码简单了不少
以上两种方式可能有区别,但是我并不知道
参考
Harbinger
土球球的《我的世界Minecraft模组开发指南》
Forge文档
我的世界开发者中文指南
打赏
制作不易,若有帮助,欢迎打赏!