分享

MCP的Mod制作教程(3)

 gljin_cn 2015-02-25
MCPMod制作教程(3)
创建新的砖块,物品和冶炼
作者:szszss

索引贴地址:
http://www./thread-18949-1-2.html

注:本文最初基于MCP5.6和ModLoader1.1.0编写的.
MCP6.2和ModLoader1.2.4更新了大量方法的名字,导致我的教程几乎报废一半...不管怎么说,我用了一个晚上的时间还是修正了教程的文字部分,使其和最新版的MCP与ModLoader接轨.但图片部分我实在是无力修改了...大家将就着看吧.
旧版和新版的ModLoader的最大区别在于,旧版的方法全部是首字母大写,而新版的方法全部是首字母小写.自行脑补替换图片内容.



上一章我们创建了一个基于ModLoadermod,现在我们要来为它添加功能.
本章我们要进行:
创建一个新的矿物(砖块):Diracium
创建一个新的矿锭(物品):Diracium Ingot
创建一个新的物品:Dirac Wand


创建一个新的砖块

知识点:创建一个新砖块的流程
///////////////////////////////////////////////////////////////////////////////////////////////////////////
Minecraft中,每一种砖块都拥有一个类,这个类是用来描述这种砖块的属性,它继承自Block类.
我们可以通过创建一个新的类来创建一种新的砖块,然而此时我们还无法直接使用它,因为它没有被注册入游戏.
你可以这样理解:你作为一个人(你不是Daedalus或Icarus吧...)必然拥有一些特征(包括名字),然而当你要加入一个组织时,你必须先得到那个组织的认可,很可能还要被分配一个新的编号甚至代号.今后组织内的人也将只称呼你的编号.
同理,Minecraft中一个砖块类包括了它自身的特征,比如耐久度,纹理等...除此之外你还需要用将它注册入游戏才能使用.

以下是创建一个叫My New Block的新砖块的流程:
创建一个继承自Block的新类myBlock,最简单的myBlock类是这样的:


  1. public class myBlock extends Block{
  2.          public myBlock(int id, int textureIndex, Material mat){
  3.                   super(id, textureIndex, mat);
  4.                   super.setHardness(1.0f);
  5.                   super.setBlockName("myblock");
  6.                   super.setStepSound(soundStoneFootstep);
  7.          }
  8. }
复制代码
它只有一个构造函数,它会调用它的基类(Block)的构造函数并传递相关参数(id为砖块编号,textureIndex为纹理编号,mat为砖块材质).同时还会配置自身的砖块硬度(调用基类的setHardness方法),里名字(调用基类的setBlockName方法,里名字是游戏内部用于识别的名字,它主要在数据储存时使用.)和玩家走在上面的走路声(调用基类的setStepSound方法)

之后我们需要在游戏中注册这个砖块.
mod主文件的类内添加这个代码

  1. public static Block NewBlock;
复制代码

在你的mod主文件的load方法内添加这些代码

  1. int textureIndex = ModLoader.addOverride("/terrain.png", "/newblock.png");
  2. int BlockId = 176;
  3. NewBlock = new myBlock(BlockId, textureIndex, Material.rock);
  4. ModLoader.registerBlock(NewBlock);
  5. ModLoader.addName(NewBlock, "My New Block");
复制代码
textureIndex记录ModLoader分配来的纹理ID,ModLoader使用addOverride方法可以向指定纹理文件中寻找空位插入新的纹理,并自动算出它的ID.
BlockId是砖块ID,砖块ID不能重复.砖块和物品的最大差异(在文艺青年眼中):砖块的ID范围是0~255(截止1.2.4版时,0~124已被使用)  物品的ID范围是256~32000(10000以后就随便使吧)
现在我们需要开始初始化新砖块了,NewBlock即代表这个新砖块,NewBlock = new myBlock(BlockId, textureIndex, Material.rock);即初始化myBlock类,并将初始化的结果赋给NewBlock,从此NewBlock即等同于新砖块.
之后,便是要将已初始化的新砖块注册入游戏,registerBlock是让ModLoader注册一个新的砖块,砖块只有被注册后才能使用.
addName是为已注册的砖块添加一个表名字(在游戏中显示的名字,即玩家能看到的名字.)

前三行可以被简化压缩,它的等效最简形式是:
  1. NewBlock = new myBlock(176, ModLoader.addOverride("/terrain.png", "/newblock.png"), Material.stone);
复制代码
///////////////////////////////////////////////////////////////////////////////////////////////////////////

首先我们为砖块创建一个新的类,MCP对砖块类的建议命名规范是BlockXXX,我习惯为一个mod的所有文件()都加入一个缩写前缀.因此我将类命名为dcBlockDiracOre

之后编写类的内容

  1. package net.minecraft.src;

  2.     public class dcBlockDiracOre extends Block {

  3.         public dcBlockDiracOre(int id, int textureIndex, Material mat){
  4.             super(id, textureIndex, mat);
  5.             super.setHardness(3.0f);
  6.             super.setBlockName("oreDiracium");
  7.             super.setStepSound(soundStoneFootstep);
  8.     }

  9. }
复制代码

接下来我们要制作砖块的纹理,所有原版砖块的纹理全部在Minecraft.jar内的terrain.png,ModLoader给我们提供了一种不用修改原纹理就能添加纹理的办法.所以我们现在只需制作一个单独的新的纹理文件即可,一个砖块的纹理大小是16x16.
我将原版铁矿涂上紫色充当Diracium矿的纹理.并起名叫diracore.png,将它存到MCP文件夹下的lib文件夹中.
之后我们要在mod_Diracon中注册它,在类中加入

  1. public static Block BlockDiracium;
复制代码

load方法内加入

  1. BlockDiracium = new dcBlockDiracOre(176, ModLoader.addOverride("/terrain.png", "/diracore.png"), Material.rock);
  2. ModLoader.registerBlock(BlockDiracium);
  3. ModLoader.addName(BlockDiracium, "Diracium Ore");
复制代码

现在我们制作完新的砖块了,但在游戏里却无法调用它!它既不能合成,也不会被地形生成器生成.为了测试它,我们决定临时添加一个合成,4个土块和1个沙子来合成1Diracium.

创建一个新的合成

知识点:创建一个新合成的流程
///////////////////////////////////////////////////////////////////////////////////////////////////////////
MC有一个专门的类:CraftingManager用于管理物品的合成.Java没有真正意义上的静态类,CraftingManager可以当做静态类来使用,CraftingManager类下的getInstance方法可以返回一个可用的CraftingManager.你可以用这个办法来直接获取一个CraftingManager并为它添加合成表(使用addRecipe方法),但为了保险起见,我们习惯使用ModLoader提供的addRecipe方法.

ModLoaderaddRecipe方法可以直接添加一个合成表.
addRecipe的参数是(ItemStack itemstack, Object aobj[])
ItemStack是一个类,中文名为"物品栈" 它代表一组任意数量的物品,比如"1个矿车" "32块铁矿" "16个雪球" 等等.某些情况下它可以不写数量,但这里必须写上.
Object aobj[]是一个Object数组,这意味着你可以按照一定规范来随意的写.然而想要仅凭文字来解释它的使用规范实在太难了.所以我以牌子的合成为例来解释.

  1. addRecipe(new ItemStack(Item.sign, 1), new Object[]
  2.                 {
  3.                     "###", "###", " X ", Character.valueOf('#'), Block.planks, Character.valueOf('X'), Item.stick
  4.                 });
复制代码

addRecipe - 调用addRecipe方法
new ItemStack(Item.sign, 1) - 创建一个新的物品栈类,物品类型为牌子,数量为1
new Object[] - 创建一个新的Object数组
{ - 开始声明数组内的内容(按标准术语说应该是变量或参数...)
"###", "###", " X " - 绘制合成蓝图,牌子的合成有三行,所以需要绘制三次,对于没有物品的格子就用空格代替.如果行数不到三行可以不绘制无物品的行,比如天窗的合成"###", "###".
Character.valueOf('#'), Block.planks - 解释蓝图,Character.valueOf('#')引号内的东西是解释的对象,Block.planks(木材砖块)是解释的结果.
///////////////////////////////////////////////////////////////////////////////////////////////////////////
load方法内加入

  1. ModLoader.addRecipe(new ItemStack(BlockDiracium, 1), new Object[]
  2. {
  3.          " D ",
  4.          "DSD",
  5.          " D ",
  6.          Character.valueOf('D'), Block.dirt,
  7.          Character.valueOf('S'), Block.sand
  8. });
复制代码

目前的程序代码应该是这样的.

现在可以开始测试了!
运行MCP文件夹下的recompile.bat开始编译.编译完后就可以运行startclient.bat开始测试了!

知识点:recompile.bat的编译方式
///////////////////////////////////////////////////////////////////////////////////////////////////////////
虽然不是很重要,但我还是打算写写
这些文件会作为编译所需的库载入:
lib文件夹下的一切东西
jars\bin\minecraft.jar
jars\bin\jinput.jar
jars\bin\lwjgl.jar
jars\bin\lwjgl_util.jar
源文件地址会设为:
src\minecraft
编译后文件会储存在:
bin\minecraft
编译器会编译这些源文件:
src\minecraft\net\minecraft\client目录的任何java文件
src\minecraft\net\minecraft\isom目录的任何java文件
src\minecraft\net\minecraft\src目录的任何java文件
(备注:子目录下的应该也编译吧?)
(备注2:这是针对MCP5.6写的,MCP6.0以后新增的3个目录应该也会编译吧...)
conf\patches\ga.java
conf\patches\Start.java
///////////////////////////////////////////////////////////////////////////////////////////////////////////

知识点:startclient.bat的启动方式
///////////////////////////////////////////////////////////////////////////////////////////////////////////
曾有几时,我在纠结它到底从哪里启动的游戏...
后来研究了下日志文件,才明白它的启动方式.
它会按顺序载入这些文件
bin\minecraft
lib下的一切文件(class会作为资源文件载入)
jars\bin\minecraft.jar
jars\bin\jinput.jar
jars\bin\lwjgl.jar
jars\bin\lwjgl_util.jar
之后它会调用这个文件夹下的文件作为链接库:
jars\bin\natives
///////////////////////////////////////////////////////////////////////////////////////////////////////////

如果你之前的步骤没有错的话,你能顺利地进入游戏.
你可以尝试徒手拍碎它,但无法掉落任何矿物,因为它的材质类型是石头(Rock),你至少要拿个木镐才能调教它.

创建一个新的物品

接下来我们要为这个矿创建一个冶炼产物:Diracium Ingot.

知识点:创建一个新物品的流程
///////////////////////////////////////////////////////////////////////////////////////////////////////////
制作物品的流程与砖块稍有不同,因为ModLoader没有提供一个"注册"物品的方法.
以下是制作一个物品的流程:
创建一个继承自Item的新类myItem
最简单的myItem类是这样:

  1. package net.minecraft.src;

  2. public class myItem extends Item {
  3.    protected myItem(int i) {
  4.        super(i);
  5.        maxStackSize = 64;
  6.    }
  7. }
复制代码
首先它调用基类的构造函数.maxStackSize = 64是将最大堆叠数量设为64.

mod主文件的类内添加这个代码

  1. public static Item newItem;
复制代码

在你的mod主文件的load方法内添加这些代码

  1. newItem = new myItem (10086).setItemName("myitem");
  2. newItem.iconIndex = ModLoader.addOverride("/gui/items.png", "newitem.png");
  3. ModLoader.addName(newItem, "My Item");
复制代码

newItem便代表你的新物品. 它的构造函数的参数是物品ID,这里为10086.
setItemName方法是设置物品的里名字.
iconIndex方法是设置图标纹理ID,类似砖块,它的图标纹理是由addOverride方法传回的.
ModLoaderAddName方法是为物品添加一个表名字.
(表名字和里名字的定义参见砖块部分)
///////////////////////////////////////////////////////////////////////////////////////////////////////////

创建一个类,名字为dcItemDiracIngot,让它继承Item类.
之后输入它的构造函数.
  1. protected dcItemDiracIngot(int i) {
  2.   super(i);
  3.   maxStackSize = 64;
  4. }
复制代码

在mod_Diracon类中加入
  1. public static Item DiracIngot;
复制代码

在类的load方法中加入:
  1. DiracIngot = new dcItemDiracIngot (10086).setItemName("diracingot");
  2. DiracIngot.iconIndex = ModLoader.addOverride("/gui/items.png", "/gui/diracingot.png");
  3. ModLoader.addName(DiracIngot, "Dirac Ingot");
复制代码

此时的代码应该是这样的.


之后为砖块绘制一个图标纹理,图标纹理的大小也是16x16.
绘制好后拷贝到MCP下的lib\gui文件夹内(肯定没有这个文件夹..自行新建).
之后我们就制作好新的物品了...但是却没有办法在游戏中见到它!接下来我们要添加冶炼公式,使炉子可以冶炼原矿.

ModLoader添加一个新的冶炼

知识点:ModLoader添加一个冶炼公式
///////////////////////////////////////////////////////////////////////////////////////////////////////////
就像CraftingManager,MC专门提供了一个类来管理冶炼公式:FurnaceRecipes,它也是个伪静态类,如果你有自信,你可以直接跳过ModLoader,CraftingManager直接操作,你可以调用CraftingManagersmelting方法来获取它.

ModLoader提供了一个方法AddSmelting来向普通炉子添加冶炼公式,无论是ModLoaderAddSmelting还是FurnaceRecipesAddSmelting.它们的参数都是相同的:
AddSmelting(int i, ItemStack itemstack,float f) (注意:MC1.3.1以前,参数只有前两个,即没有那个float f)
i为被冶炼矿物的物品/砖块ID.需要注意的是,无论哪个AddSmelting.如果你知道被冶炼砖块的ID的话,你可以直接输入数字,否则你需要引用那个砖块的blockID变量.而对于物品,你必须引用那个物品的shiftedIndex变量作为ID.因为物品的ID计算和砖块有些不同.
f是MC1.3.1新增了"冶炼矿物也能获得经验"这个设定后新增的参数,已知烧沙子是0.1,烤猪肉是0.3,冶炼铁是0.7,冶炼金是1.0

众所周知,0~255ID是储存砖块,256~32256(这个数字是我估算出来的,不要太相信...)是用来储存物品.MC对于砖块和物品ID的初始化方式采用了遗祸千年的一[两制制度,在初始化时两种东西的ID都是从0算起,但砖块初始化完毕后这个ID便作为它的真实ID使用,而物品会悄悄地将初始的ID(比如我在上一个知识点范例中写的10086)进行换算,换算出可用的真实ID(10086换算完后是10342)后再储存入shiftedIndex变量.你可以在Item类中找到相关的运算.
这里就随便甩2个例子
addSmelting(Block.sand.blockID, new ItemStack(Block.glass));  //沙子冶炼玻璃
addSmelting(Item.porkRaw.shiftedIndex, new ItemStack(Item.porkCooked)); //烤猪肉
///////////////////////////////////////////////////////////////////////////////////////////////////////////
继续在load方法中添加代码:

  1. ModLoader.addSmelting(BlockDiracium.blockID, new ItemStack(newItem),1.0f);
复制代码

代码应该是这样的.
<<这个是旧版的代码,缺了一个1.0f...

于是保存,编译,测试.

好吧,由于我的失误我忘了考虑纹理的Alpha通道,所以多了个白边,不过不算太大的Bug -w-
虽然扔到地上后感觉很213...

Entity(实体)的操作

我们已经创建过一个新砖块和一个物品了,现在我们要开始制作Dirac Wand.
先要说一下Dirac是什么,Dirac的中文写作迪拉克,读作xi jian(.......),本教程中的Diracium(迪拉克元素)是一种能从无穷无尽的迪拉克之海(反物质世界/纯能量世界)中吸取能量,凭借这些能量来创造各种在我们的宇宙中因为能量守恒定律而无法做到的事情!
首先创建一个物品类dcItemDiracWand,我们让他继承自ItemSword类,这样我们就能拿它痛快地打人了.让Eclipse自动补上它的构造函数.
之后我们来为它设计一个功能,被它打的怪物会向远方飞出去...
但问题来了,怎么控制被他的怪物?答案是控制Entity(实体)

知识点:什么是实体
///////////////////////////////////////////////////////////////////////////////////////////////////////////
Entity是续Block,Item后第三个MC世界的重要组成部分,在游戏中,地上跑的动物,洞穴里潜行的炸弹魔,怪物死后掉落的经验球,水上漂浮的舟,它们都是不同的Entity.
一个Entity除了具有XYZ坐标以外还具有一些特殊的参数,比如高度,宽度(某些摸不着的Entity没有这些参数)生命值(无敌的Entity无需考虑)Yaw,Pitch,速度...
什么是YawPitch?按照立体几何的定义,在右手坐标系(MC使用的就是右手坐标系),Yaw是物体围绕Y(即高度轴)旋转的参数,MC中他通常用来控制有形实体的模型角度.Pitch是物体围绕X轴旋转的参数,即向左倒向右倒.没错,MC没有Roll(前倒后倒),因此你无法看到哪个僵尸被你钻石剑切死后是直挺挺地向前扑倒的.任何怪物被砍死后都是侧着倒下(即改变Pitch).
然而我们很少直接使用Entity,我们会根据需要从Entity派生出新的类,比如EntityBoat(已放置的船)就是从Entity中派生出来的.
然而说到Entity就不得不提NBT.
///////////////////////////////////////////////////////////////////////////////////////////////////////////

知识点:NBT初解
///////////////////////////////////////////////////////////////////////////////////////////////////////////
NBTMC的数据储存格式,它是Notch设计的一种树形数据储存格式.它与地图存档密不可分,在存档时,NBT会将Entity的数据写入NBT文件,读档时NBT会读取NBT文件并将数据还原给实体.
NBT是个很复杂的东西,幸运的是,在这一章中,我们还无需操作NBT.
///////////////////////////////////////////////////////////////////////////////////////////////////////////

了解了什么是实体后我们就可以开始动工了,查看ItemSword类的源码后可以了解到,用剑砍中一个野怪会引发hitEntity方法,因此我们要在dcItemDiracWand中添加这个方法的重写(Override).
dcItemDiracWand类中加入这个方法:

  1. public boolean hitEntity(ItemStack itemstack, EntityLiving entityliving, EntityLiving entityliving1)
  2. {
  3.     //从下开始为第一行
  4.         itemstack.damageItem(1, entityliving1);
  5.         float Angle = ((entityliving1.rotationYaw + 90f)/ 180F) * 3.141593F;
  6.         float x = 3f * MathHelper.cos(Angle);
  7.         float y = 1f;
  8.         float z = 3f * MathHelper.sin(Angle);
  9.         entityliving.setVelocity(x, y, z);
  10.         return true;
  11. }
复制代码

首先,他重写了旧的hitEntity方法.
hitEntity方法的主要目的是在击中实体后降低装备的耐久度,因此第一行的代码是用来降低装备的耐久.
第二行:单精度浮点数Angle是记录玩家朝向的弧度制角度,entityliving1这个实体就是玩家,rotationYaw可以视为玩家朝向的角度.但要注意的是rotationYaw返回的是角度制(0~360) 而我们待会运算时需要弧度制(0~2π),因此需要进行换算.数学中的换算公式是:弧度制=(角度制/180)*π  然而这里的换算公式是弧度制=((角度制+90)/180)*π
为什么这个换算公式和正规的数学公式不同?正规数学的角度坐标系是正右方为0,正上方为90(或者是弧度制的二分之π) 然而MCYaw使用的是另一套角度坐标系,正上方为0,正左方为90.MathHelper类使用的是正规数学的角度坐标系,因此我们使用了一套特殊的换算方法.
第三行:x是利用正交分解来算出被殴打的对象在x轴上的飞行速度.(不知道何为正交分解?好吧你高中是怎么上的...) 注意MathHelper类的几种三角函数方法都要求输入弧度制参数.
第四行:y是物体在y轴上的飞行速度.
第五行:z是利用正交分解算出物体在z轴上的飞行速度.
第六行:entityliving是被殴打的物体,setVelocity是设置速度向量,即为物体设置XYZ三个轴上的分速度.(不知道何为向量和分速度...请自行学习高一物理课程)
motionX,motionY,motionZ这三个double变量是实体的分速度,它们的访问级是Public,也就是说你其实能直接修改它们.
第七行是返回true,即代表这个方法调用成功.
事实上还有更深层的含义,它影响到系统对它的判定比如能否使出武器挥动动作等...
之后设计一个叫diracwand.png的纹理,注意Alpha通道...(背景透明)
依然是保存在lib/gui
之后为mod添加物品,mod_Diracon类中添加
  1. public static Item DiracWand;
复制代码

load方法内添加

  1. DiracWand = new dcItemDiracWand (10087,EnumToolMaterial.EMERALD).setItemName("diracwand");
  2. DiracWand.iconIndex = ModLoader.addOverride("/gui/items.png", "/gui/diracwand.png");
  3. ModLoader.addName(DiracWand, "Dirac Wand");
  4. ModLoader.addRecipe(new ItemStack(DiracWand, 1), new Object[]
  5. {
  6.          "D",
  7.          "S",
  8.          "S",
  9.          Character.valueOf('D'), DiracIngot,
  10.          Character.valueOf('S'), Item.stick
  11.   });
复制代码

这样我们就添加了一个新物品,它的合成是上面一个Dirac,下面2个木棍.(和铲子一样,但可以当剑使)
保存,编译,测试.
用它打一个怪,怪会直挺挺地飞出去.

如果你完成了上面几章的内容,那么恭喜你,你现在已经能制作最基本的mod!你可以设计自己的砖块,制作自己的物品,你可以添加自己的合成!
然而我们发现仍有许多我们无法做到的地方,我们不能添加一个新的NPC,我们无法生成新的地形或自定义矿物.如果你希望学习更多的关于mod制作的内容,请继续读下一篇教程,在下一篇教程中,我们会详细讨论如何创建一个新的Entity,如何制作新的功能,以及稍微学习一些关于TileEntityNBT的知识.
如果你发现了本教程有什么不足(语言病句,或者内容错误,或者讲的不清楚)可以告诉我,我会尽快修正!

下一篇:创建新的实体,NPC,添加特殊功能
http://www./thread-18942-1-1.html


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多