Minecraft 1.12.2 Forge Mod 开发教程重写版¶
目录:¶
构建开发环境¶
这一章介绍了如何构建开发环境。
JDK下载¶
若想制作Mod,必须先构建好开发环境。你需要下载的版本是JDK1.8.0
IDE下载¶

Forge MDK包下载¶
IDEA安装好后,该下载Forge
MDK包了。点击左侧的1.12.2,点击下载MDK。这里用的版本是2847(不同版本之间也是有一些差异的)
包下载好后,将其解压到全英文路径(如果不是全英文路径你会遇到一些奇怪的问题)
正式开始构建开发环境¶
按下Shift+右键,选择在此处打开命令窗口或PowerShell
gradlew setupDecompWorkspace
gradlew genIntellijRuns


还需要设置一下:按下Ctrl+Alt+S,然后按图片中的设置。
按下Ctrl+Alt+Shift+S,将Project Compiler Output
设定为~\out
,其中~
是你的Mod目录。最后,指定Project
SDK为1.8版本。
到此为止,工作环境已经配置好了,下一期我们将配置主类和Mod信息
主类、Mod信息与代理¶
这一章介绍了如何配置主类、Mod信息与代理。
mcmod.info¶
在resources文件夹中,有一个mcmod.info文件,它储存了Mod的信息,我们需要去编辑它。
modid¶
错误的示例: - ExampleMod
modid应以 a-z 0-9 _ 命名,并且不允许大写。
name¶
name可以是Mod的名称。
description¶
用一两段话来介绍你的Mod。
version¶
Mod的版本。具体命名规则会在以后讲到。
mcversion¶
Mod支持的Minecraft的版本。
authorlist¶
Mod作者的列表。
主类¶
每个Mod都会有一个主类,每一个主类都会有一个@Mod
注解。下面这个例子展示出了@Mod
注解如何使用(注:以后所有的例子都会以ExampleMod为准,请根据自己的需求更改):
@Mod(modid = ExampleMod.MODID, name = ExampleMod.NAME, version = ExampleMod.VERSION)
public class ExampleMod
{
public static final String MODID = "examplemod";
public static final String NAME = "Example Mod";
public static final String VERSION = "1.0";
}
MODID, NAME,
VERSION就不必解释了。接下来我们在public static final String VERSION = "1.0";
这段代码的后面添加上下面的代码:
@Mod.Instance(ExampleMod.MODID)
private static ExampleMod instance;
这段代码用于保存Mod的实例。
然后创建两个新类,分别是com.example.examplemod.proxy.ClientProxy
和com.example.examplemod.proxy.CommonProxy
。
在CommonProxy
中添加如下代码:
public void preInit(FMLPreInitializationEvent event) {
}
public void init(FMLInitializationEvent event) {
}
然后在ClientProxy中添加如下代码:
@Override
public void preInit(FMLPreInitializationEvent event) {
super.preInit(event);
}
@Override
public void init(FMLInitializationEvent event) {
super.init(event);
}
注意:ClientProxy
继承于CommonProxy
。
现在回到ExampleMod
类,在private static ExampleMod instance;
的下面加上如下代码:
@SidedProxy
(clientSide = "com.example.examplemod.proxy.ClientProxy",
serverSide = "com.example.examplemod.proxy.CommonProxy"
)
private static CommonProxy proxy;
接下来在主类的preInit
方法中加上proxy.preInit(event);
,在init
方法中加上proxy.init(event);
。
示例代码¶
ExampleMod.java¶
package com.example.examplemod;
import com.example.examplemod.proxy.CommonProxy;
import net.minecraft.init.Blocks;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.Mod.EventHandler;
import net.minecraftforge.fml.common.SidedProxy;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import org.apache.logging.log4j.Logger;
@Mod(modid = ExampleMod.MODID, name = ExampleMod.NAME, version = ExampleMod.VERSION)
public class ExampleMod
{
public static final String MODID = "examplemod";
public static final String NAME = "Example Mod";
public static final String VERSION = "1.0";
@Mod.Instance(ExampleMod.MODID)
private static ExampleMod instance;
@SidedProxy
(clientSide = "com.example.examplemod.proxy.ClientProxy",
serverSide = "com.example.examplemod.proxy.CommonProxy"
)
private static CommonProxy proxy;
private static Logger logger;
@EventHandler
public void preInit(FMLPreInitializationEvent event)
{
logger = event.getModLog();
logger.info("preInit");
proxy.preInit(event);
}
@EventHandler
public void init(FMLInitializationEvent event)
{
// some example code
logger.info("DIRT BLOCK >> {}", Blocks.DIRT.getRegistryName());
proxy.init(event);
}
}
CommonProxy.java¶
package com.example.examplemod.proxy;
import com.example.examplemod.register.*;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
public class CommonProxy {
public void preInit(FMLPreInitializationEvent event) {
}
public void init(FMLInitializationEvent event) {
}
}
ClientProxy.java¶
package com.example.examplemod.proxy;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
public class ClientProxy extends CommonProxy{
@Override
public void preInit(FMLPreInitializationEvent event) {
super.preInit(event);
}
@Override
public void init(FMLInitializationEvent event) {
super.init(event);
}
}
你的Mod最基本的已经配置完成,很快我们将做出第一个物品。
物品¶
这一章介绍了如何添加物品、注册物品以及给物品添加更多功能。
创建物品¶
如果你想偷懒,直接Item item = new Item();
就可以了。但是这只适用于那些无功能的物品,如钻石。我们要做的是一个有功能的物品。
新建物品类¶
Bubble
当例子吧。Item
类,所以我们也要写一个类来继承它。com.example.examplemod.common.item.ItemBubble
类。private static String name = "bubble";
public ItemBubble() {
this.setRegistryName(name);
this.setUnlocalizedName(ExampleMod.MODID+"."+name);
this.setCreativeTab(CreativeTabs.MISC);
}
setRegistryName
:设置物品的注册名称setUnlocalizedName
:设置物品的未本地化名称setCreativeTab
:设置物品的创造模式物品栏位置注册物品¶
com.example.examplemod.register.ItemsRegister
类。public class ItemsRegister
上面添加@Mod.EventBusSubscriber
。public static final Item BUBBLE = new ItemBubble();
。
创建一个构造方法。public ItemsRegister() {
MinecraftForge.EVENT_BUS.register(this);
}
这里我们不使用古老的GameRegistry
方法来注册物品,而是使用Forge推荐的注册方法。
@SubscribeEvent
public static void registerItems(RegistryEvent.Register<Item> event) {
event.getRegistry().registerAll(
BUBBLE
);
}
Forge提供了大量的注册表,其中包括Item
、Block
、Biome
等等。这些我们会在后面讲到。
你还可以在BUBBLE
的下面添加更多的物品,只要确保你用public static final Item xxx = new ItemXXX();
声明了一个物品并创建了对应的类,以及每个物品之间用了逗号隔开。
最后我们只需要在CommonProxy
类中的的preInit
方法中new ItemsRegister()
即可。

本地化与国际化¶
什么是本地化与国际化¶
用官方的话说就是
国际化, 简称为i18n,是一种可以适用于各种语言的代码设计思路。 本地化是指显示适用于用户语言的文字的过程 。
I18n用 _翻译密钥(translation keys)_实现。 翻译密钥是一个字符串,用于标识一段没有特定语言的可显示文本。 例如, 翻译密钥tile.dirt.name指的是泥土方块的名字。 这样,可以引用可显示的文本而不关心特定语言。 加入新语言时,代码不需要修改。
本地化将在游戏的本地设置中发生。 在Minecraft客户端中,位置由语言设置指定。 在专用服务器上,唯一受支持的语言环境是en_US。 可以在 Minecraft Wiki上找到可用语言环境的列表。
因为Minecraft是一个面向全世界的游戏,我们不能忽视 Minecraft 的玩家来自世界各地,他们使用不同的语言。为了让更多人可以游玩这个游戏,所以Minecraft添加了引入了国际化与本地化支持。本地化大部分时间是在翻译外国作品,而这些翻译必须要考虑到地区差异。正如同酒石酸菌说的一样,本地化MOD不要使用机翻,这会使得翻译不准确或无法理解,所以我们在翻译是应该考虑本地人民的语言习惯以及在游戏中实质的作用。
了解一个语言文件¶
我说了那么多,但是我们是来翻译MOD的,我们要知道怎么给我们的MOD创建出来一个语言文件以方便其他人对你的MOD进行翻译。
item.exeamplemod.bubble.name
this.setUnlocalizedName(ExampleMod.MODID+"."+name)
这个语句进行定义的。由此我们可以知道它的名字叫MODID.物品名.name
再加上Minecraft的物品前缀item
,就是他的的本地化名称。如果你实在不知道它的本地化名称,你也可以进入游戏查看它的名称得知它的本地化命名。我们在assets/[modid]/lang
创建一个文件en_us.lang
。这里需要注意不是en_US
,因为1.11 开始,资源包格式版本号更新到了 3。自此,资源包中所有文件名强制使用 小写字母 (正则表达式 [a-z0-9_]+)
。所以你应该使用的是en_us.lang
。
资源包格式版本 | 文件名 | Minecraft 版本 |
---|---|---|
1 | en_US.lang | 1.6 |
2 | en_US.lang | 1.8 |
3 | en_US.lang | 1.11 |
4 | en_us.lang | 1.13 |
5 | en_us.lang | 1.15 |
到这里你可能会疑惑,我们是中国为什么我们要创建en_us.lang
而不是zh_cn.lang
。在这里我们只是以en_us.lang
为示例,zh_cn.lang
同理,在此我推荐你们创建两个语言文件en_us
和zh_cn
。
添加物品贴图¶
物品已经翻译好了,接下来就该添加物品材质了。
首先创建文件夹assets/[modid]/textures/items
,将bubble.png
放进去:
之后还需要创建文件assets/[modid]/models/item/bubble.json
。文件的内容如下:
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "examplemod:items/bubble"
}
}
item/generated
:使用默认的物品渲染方式layer0
:使用第0层图层examplemod:items/bubble
:寻找assets文件夹下的examplemod下的textures下的items下的bubble.png(好长
现在物品模型渲染文件也做好了,接下来,到注册材质的时候了。
注册物品材质¶
ModelsRegister
类。public ModelsRegister() {
MinecraftForge.EVENT_BUS.register(this);
}
注:这次不需要在开头加上注解了。
接着我们需要
@SubscribeEvent
public void registerModels(ModelRegistryEvent event) {
registerModel(ItemsRegister.BUBBLE);
}
private void registerModel(Item item) {
ModelLoader.setCustomModelResourceLocation(item, 0, new ModelResourceLocation(item.getRegistryName(), "inventory"));
}
registerModel(ItemsRegister.BUBBLE);
:调用registerModel
方法注册物品材质,BUBBLE可以换成其他的已经注册的物品ModelLoader.setCustomModelResourceLocation(item, 0, new ModelResourceLocation(item.getRegistryName(), "inventory"));
:注册物品模型。0是Metadata。其中”inventory”一般不需要更改。
不要把方法名搞混了!
preInit
方法中new ModelsRegister();
即可。添加食物¶
我们来添加一个食物。
并不是所有的物品都继承于Item类,例如工具继承于ItemTool类,盔甲继承于ItemArmor类,而食物继承于ItemFood类。
ItemFood类的构造函数有这些:
public ItemFood(int amount, float saturation, boolean isWolfFood)
{
this.itemUseDuration = 32;
this.healAmount = amount;
this.isWolfsFavoriteMeat = isWolfFood;
this.saturationModifier = saturation;
this.setCreativeTab(CreativeTabs.FOOD);
}
public ItemFood(int amount, boolean isWolfFood)
{
this(amount, 0.6F, isWolfFood);
}
其中,从上看出saturation是可选的。默认值为0.6f。
首先创建一个类,名字叫做ItemFoodChestnut
。之后,在类里添加如下代码:
public ItemFoodChestnut() {
super(1, 0.6f, false);
String name = "chestnut";
this.setRegistryName(name).setUnlocalizedName(ExampleMod.MODID+"."+name).setCreativeTab(CreativeTabs.FOOD);
}
只需要注册这个食物以及添加物品贴图,再进行国际化与本地化就可以了。
我们希望这个食物能在饱的时候也能吃,因此,再添加这一段代码:setAlwaysEdible();
我们还希望可以在吃掉后玩家会获得效果,因此,参考原版金苹果的代码,我们需要重写onFoodEaten方法。
@ParametersAreNonnullByDefault
@Override
protected void onFoodEaten(ItemStack stack, World worldIn, EntityPlayer player) {
if (!worldIn.isRemote) {
player.addPotionEffect(new PotionEffect(Potion potionIn, int durationIn, int amplifierIn));
}
super.onFoodEaten(stack, worldIn, player);
}
将这些替换成你想要的数据,就可以了。
注意:一定要将食物类继承ItemFood类!
饮料同理。
示例¶
package com.example.examplemod.common.item;
import com.example.examplemod.ExampleMod;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.MobEffects;
import net.minecraft.item.ItemFood;
import net.minecraft.item.ItemStack;
import net.minecraft.potion.PotionEffect;
import net.minecraft.world.World;
import javax.annotation.ParametersAreNonnullByDefault;
public class ItemFoodChestnut extends ItemFood {
public ItemFoodChestnut() {
super(1, 0.6f, false);
String name = "chestnut";
this.setRegistryName(name).setUnlocalizedName(ExampleMod.MODID+"."+name).setCreativeTab(CreativeTabs.FOOD);
this.setAlwaysEdible();
}
@ParametersAreNonnullByDefault
@Override
protected void onFoodEaten(ItemStack stack, World worldIn, EntityPlayer player) {
if (!worldIn.isRemote) {
player.addPotionEffect(new PotionEffect(MobEffects.SPEED, 1000000, 255));
}
super.onFoodEaten(stack, worldIn, player);
}
}
合成¶
简单的配方文件¶
一个简单的有序合成配方应该如下:
{
"type": "minecraft:crafting_shaped",
"pattern": [
"xxx",
"xax",
"xxx"
],
"key": {
"x": {
"item": "minecraft:water_bucket"
},
"a": {
"type": "forge:ore_dict",
"ore": "gemDiamond"
}
},
"result": {
"item": "examplemod:bubble",
"count": 9
}
}
这个配方允许你用8个水桶来合成9个泡泡。
合成类型¶
你可以把它当作定义用哪一种合成布局,例如minecraft:crafting_shaped
(有序合成)或minecraft:crafting_shapeless
(无序合成)。
你也可以定义你自己的合成类型,只需要使用_factories.json文件。
有序合成需要pattern
和key
两个关键字。
pattern
定义物品的排序,它必须是占位符的形式。每一个物品你都可以选择任意的一个字符作为占位符。
key
定义了占位符对应的物品。可以添加附加属性forge:ore_dict
,它可以定义物品是矿物词典的一部分。例如,无论什么铜矿都可以生成铜锭。要这样的效果的话,要用ore
标签定义物品,而不是用item
定义那个物品。
默认有很多这样的类型,你也可以自己定义。
data
是一个可选标签,用于定义方块或物品的metadata
。
无序合成不需要pattern
和key
关键字。
要定义一个无序合成,你要使用ingredients
列表。它定义了合成中要用的物品,也可以用forge:ore_dict
它函数式的声明在上方。默认有很多这样的类型,你也可以自己定义。它甚至可以一个对象定义多个实例,意味着合成时必须要在合成台里放多个这样的物品。
- 提示
- 你的配方里有多少
ingredients
没有限制,原版合成台没有只允许一个合成里放9个物品。
{
"type": "crafting_shapeless",
"ingredients": [{
"item": "examplemod:bubble"
},
{
"item": "examplemod:bubble"
}
],
"result": {
"item": "minecraft:diamond",
}
}
这个配方允许你用2个泡泡来合成1颗钻石。
熔炼¶
要定义一个熔炼配方,我们只需要用1行代码即可。 新建一个SmeltingRecipes类,并在里面写上:
public SmeltingRecipes() {
GameRegistry.addSmelting(ItemsRegister.BUBBLE, new ItemStack(ItemsRegister.BUBBLE, 2), 0.9f);
}
这样当你烧完泡泡的时候,就会出现2个泡泡,并获得0.9点经验。
最后我们只需要在CommonProxy的init方法中new SmeltingRecipes();即可。
ItemStack¶
Minecraft 1.12.2 原版只有4096个物品ID(某些Mod会将其提升至int上限),当我们想写大一些的Mod的话,可不能随便乱用Item声明那个物品。因此,我们要用ItemStack来声明那些物品。
ItemStack可以让多个物品使用同一个ID,例如一些mod的矿石,虽然是多个物品,但是只用了1个ID:ore。
ItemStack使用metadata来区分物品。
声明一个ItemStack¶
我们来试着添加一个ItemStack。该ItemStack含有low_coal, medium_coal, high_coal, super_coal。
Item coals = new Item();
public ItemStack low_coal = new ItemStack(coals);
public ItemStack medium_coal = new ItemStack(coals, 1, 1);
public ItemStack high_coal = new ItemStack(coals, 1, 2);
public ItemStack super_coal = new ItemStack(coals, 1, 3);
实战¶
首先,新建一个类,名叫ItemCoals。并让其继承Item类。然后添加上面说的代码。
接着,声明一个构造函数,里面写上
coals.setHasSubtypes(true);
coals.setRegistryName("coals");
low_coal.getItem().setUnlocalizedName(ExampleMod.MODID+"."+"low_coal").setCreativeTab(CreativeTabs.MISC);
medium_coal.getItem().setUnlocalizedName(ExampleMod.MODID+"."+"medium_coal").setCreativeTab(CreativeTabs.MISC);
high_coal.getItem().setUnlocalizedName(ExampleMod.MODID+"."+"high_coal").setCreativeTab(CreativeTabs.MISC);
super_coal.getItem().setUnlocalizedName(ExampleMod.MODID+"."+"super_coal").setCreativeTab(CreativeTabs.MISC);
使用getItem();可以把ItemStack转为Item。
再注册物品和物品贴图就好了。
这样,当我们输入/give @p examplemod:coals 1 1时就会获得medium_coal。
此页待完善。¶
创造模式物品栏¶
创建CreativeTab类,在里面写上:
public static final CreativeTabs EXAMPLE_CREATIVE_TAB = new CreativeTabs("example") {
@SideOnly(Side.CLIENT)
@Override
public ItemStack getTabIconItem() {
return new ItemStack(ItemsRegister.BUBBLE);
}
};
其中ItemsRegister.BUBBLE是创造模式物品栏的图标。
完成后把物品改一下:
this.setCreativeTab(CreativeTab.EXAMPLE_CREATIVE_TAB);
再启动游戏就能看到物品了。
方块¶
这一章介绍了如何添加方块。
添加方块¶
是时候添加方块了。新建common.block包,并新建ExampleBlock类,然后让ExampleBlock类继承Block类。在里面写上如下代码:
public ExampleBlock() {
super(Material.GROUND, MapColor.DIRT);
String name = "example_block";
this.setRegistryName(name).setUnlocalizedName(ExampleMod.MODID+"."+name).setCreativeTab(CreativeTabs.MISC).setHardness(0.5f).setResistance(10);
}
注册方块¶
我们在register包下新建BlocksRegister类,在里面写上如下代码:
@Mod.EventBusSubscriber
public class BlocksRegister {
public static final Block EXAMPLE_BLOCK = new ExampleBlock();
public BlocksRegister() {
MinecraftForge.EVENT_BUS.register(this);
}
private static Block[] blocks = {
EXAMPLE_BLOCK
};
@SubscribeEvent
public static void registerBlocks(RegistryEvent.Register<Block> event) {
for (Block block : blocks) {
ModelLoader.setCustomModelResourceLocation(Item.getItemFromBlock(block), 0, new ModelResourceLocation(block.getRegistryName(), "inventory"));
event.getRegistry().register(block);
}
}
@SubscribeEvent
public static void registerItemBlocks(RegistryEvent.Register<Item> event) {
for (Block block : blocks) {
Item itemBlock = new ItemBlock(block).setRegistryName(block.getRegistryName());
ModelLoader.setCustomModelResourceLocation(itemBlock, 0, new ModelResourceLocation(block.getRegistryName(), "inventory"));
event.getRegistry().register(itemBlock);
}
}
}
方块就注册完成了。
添加方块模型¶
Blockstate: src/main/resources/assets/examplemod/blockstates/example_block.json
Block Model: src/main/resources/assets/examplemod/models/block/example_block.json
Item Model: src/main/resources/assets/examplemod/models/item/example_block.json
Block Texture: src/main/resources/assets/examplemod/textures/block/example_block.png
blockstates/example_block.json
{
"variants": {
"": { "model": "examplemod:block/example_block" }
}
}
models/block/example_block.json
{
"parent": "block/cube_all",
"textures": {
"all": "examplemod:block/example_block"
}
}
models/item/example_block.json
{
"parent": "examplemod:block/example_block"
}
textures/block/example_block.png
掉落物¶
在ExampleBlock中添加:
@Override
public Item getItemDropped(IBlockState state, Random rand, int fortune) {
return Item.getItemFromBlock(this);
}