由于最近太忙了,所以无力一口气把所有教程更新完.
所以打算现在这里放出预览版然后慢慢更新,全部写完后再正式发到论坛.
注意由于是预览版,所以随时都会有更新和修正.
基础篇
第一章:MCP,Forge和Eclipse的配置
http://www.hakugyokurou.net/wordpress/?p=134
第二章:建立一个基于Forge的Mod
http://www.hakugyokurou.net/wordpress/?p=144
第三章:创建新的砖块,物品和冶炼
http://www.hakugyokurou.net/wordpress/?p=163
第四篇:实体
http://www.hakugyokurou.net/wordpress/?p=340
Extra篇
第一篇:Forge的事件系统
http://www.hakugyokurou.net/wordpress/?p=225
第二篇:在Eclipse下编译和调试(从1.7开始就不用考虑这个问题了)
http://www.hakugyokurou.net/wordpress/?p=257
第三篇:Coremod的制作
http://www.hakugyokurou.net/wordpress/?p=333
配套:Java字节码(Bytecode)与ASM简单说明
http://www.hakugyokurou.net/wordpress/?p=409
第四篇:Gui
http://www.hakugyokurou.net/wordpress/?p=333
常见问题
http://blog.hakugyokurou.net/?p=1298
Plus篇(同样未更新并且严重过时...)
什么是Plus篇?Plus篇倾向于讲那些原理和底层中的东西,或许对大部分人来说,是没有什么作用的.
http://www.hakugyokurou.net/wordpress/?p=284
ASMShooterMappingData的下载(供用来做Coremod的人使用,介绍看Extra编第三篇.)
http://sdrv.ms/1cv32le
另外,基础篇可能以后我不会更新了...换句话说旧教程的TileEntity和地形生成不会再被移植到新教程上,对于这几篇教程的空白,你可以参考别人的教程:
Manageryzy编写的综合索引站,包括所有中文教程的索引:https://mcdev-wiki.org
Manageryzy的教程:http://www.261day.com/minecraft-forge教程/
Darkyoooooo的教程:http://darkyoooooo.minestudio.org/minecraft-forge-开发实例/
非官方Forge文档:http://mcforge-cn.readthedocs.org/zh/latest/ (有点慢,可能需要翻墙)
如果你是位教程作者的话,可以叫我在这里加上你的教程的链接.
更新:
12.12.9 更新一点点...
12.12.12 更新了一点物品的部分
12.12.23 更新到Forge6.5.0.471
13.1.1 过年啦过年啦...旧坑未填又来新坑哟,这次是Forge的事件系统.同时,代码高亮插件修复,看起来挺不错.
13.1.2 紫妈大暴走(?),第二篇Extra教程出炉了!顺便对第一篇基础教程稍微调整了一下.
13.1.28 放出了Plus篇.
13.2.2 更新了基础教程(3)的一部分.
13.2.10 稍微修正了一点小细节(真的?)
13.2.26 Plus篇更新了一部分.为基础篇和Extra篇的更新做准备.
13.3.17 Extra第三篇和其配套教程发布.
13.6.24 Plus篇更新了"AABB盒与Vec3"
13.8.4 基础篇第四篇发布
13.9.1 修正了Extra第三篇的问题
15.1.6 更新了基础篇的1~3篇
15.2.5 更新了Extra第一篇
15.2.6 更新了Extra第三篇和ASM教程
15.2.22 加上了其他作者的教程的链接
16.2.22 时隔一年,在1.9发布前夕,教程开始向1.8更新
sz, 现在有空了吗?
之前问你如何在画面中保持显示字串这部分我OK了, 但是现在又延伸出一个问题, 我同样在显示字串的判断区里加入了显示图片, 图片是能够显示出来, 但是在画面上却像是拼贴的样子一直延伸出去, 我使用的代码如下:
GL11.glPushMatrix();
GL11.glScalef(1.0f, 1.0f, 1.0f);
GL11.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
mc.renderEngine.bindTexture("/gui/test001.png");
drawTexturedModalRect(0, 0, 0, 0, 339, 456);
GL11.glPopMatrix();
我的图片画得很大, 所以我使用 1024 x 1024 的尺寸, 但在图中目前只用到 339x456那块区域而已.
我查了很多国外讨论板的讨论文, 他们最后都没下文了, 也不知道他们是怎么解决这问题的.
麻烦你帮我看一下代码里是缺了哪条指令.
这是因为Minecraft假设所有纹理(Texture)都是256x256尺寸而造成的,OpenGL在很多地方采用比例参数,就以GL11.glColor4f举例,如果它的4个参数都是实际数值的话,那麽妳画出来的应该是壹个几乎看不见的黑色方框,但实际上不是,这是因为它采用的是比例,它将全红,全绿,全蓝,完全不透明定为1.0,然后以此为参照按比例来设置,0.5就是半红,半绿,半蓝或半透明.
这么做是因为很多东西并非只有壹个标准,采用RGB24的话,壹种颜色有256个深度,但若采用RGB555的话,壹种颜色就只有32个深度了.
同理,不同纹理有不同的大小,不同屏幕有不同的尺寸,因此OpenGL在分割纹理时采用比例参数,Minecraft对此进行了壹次封装,通过壹个系数(具体值为0.00390625,即1/256)对输入值进行换算(实际值=输入值*系数)
Minecraft假设所有纹理都是256x256,所以这个系数就固定为1/256了,于是在换算时就有了各种各样的悲剧...
下面这个纹理绘制函数可以适应各种尺寸的纹理,它相比原来的函数多了2个参数:纹理的宽(texWidth)和纹理的高(texHeight).
public void drawTexturedModalRectWisely(int texWidth,int texHeight,int x, int y, int u, int v, int areaWidth, int areaHeight)
{
float f = 1 / (float)texWidth;
float f1 = 1 / (float)texHeight;
Tessellator tessellator = Tessellator.instance;
tessellator.startDrawingQuads();
tessellator.addVertexWithUV((double)(x + 0), (double)(y + areaHeight), (double)this.zLevel, (double)((float)(u + 0) * f), (double)((float)(v + areaHeight) * f1));
tessellator.addVertexWithUV((double)(x + areaWidth), (double)(y + areaHeight), (double)this.zLevel, (double)((float)(u + areaWidth) * f), (double)((float)(v + areaHeight) * f1));
tessellator.addVertexWithUV((double)(x + areaWidth), (double)(y + 0), (double)this.zLevel, (double)((float)(u + areaWidth) * f), (double)((float)(v + 0) * f1));
tessellator.addVertexWithUV((double)(x + 0), (double)(y + 0), (double)this.zLevel, (double)((float)(u + 0) * f), (double)((float)(v + 0) * f1));
tessellator.draw();
}
感恩, 一语惊醒梦中人~
原来0.00390625 是这么来的, 我一直看不懂在Gui.java档里的那数值到底是怎么来的, 这下子我明白了.
说实在的, 绘图使用的代码运作内容我没一个了解的, 我只知道贴文字或贴图片该用哪个代码而已, 讲起来实在汗颜啊XD
你贴的这个自制函式drawTexturedModalRectWisel我有实验了, 依然行不通...
我把原来的 drawTexturedModalRect(0, 0, 0, 0, 339, 456); 代码 换成drawTexturedModalRectWisel(1024, 1024, 0, 0, 0, 0, 339, 456); 后, 图片显示被放大了, 而且是很大很大, 在Debug模式运行时, 当我把 1024 的值逐渐缩小时可以看到图片逐渐跟着缩小, 但是缩到 339的时候, 依然会看到拼贴的样子, 跟原本我提问时的那样子一样. 我一直在思考绘图座标这问题, 有没有跟学习VisualBasic那时一样呢?
后来, 我随便乱搞一通, 使用原来的代码, 但改了两个数值变成 drawTexturedModalRect(0, 0, 0, 0, 256, 256); 居然画出来的图就正常了, 1比1的尺寸, 而且没有拼贴的情况了.
但是, 这下子我更搞糊涂了, 代码我下成这样子后, 那我原本还去得知图片内部尺寸的 (339, 456) 这数值要做什么呢, 不就变成脱裤子放屁多此一举了 =.=
让我来假设一下你看我说的对不对, 因为Minecraft内定以256x256的绘图模式去画图, 如果我使用的图片总尺寸也是用 256x256 的尺寸, 只要我在这尺寸范围里画我想要的图, 那么, 我去使用 drawTexturedModalRect 这代码时才有需要去明确指定图片里的小图正确的宽和高, 这样子想法对不对呢?
我又测试了另一数据, 我的图片一样是1024x1024尺寸使用了 drawTexturedModalRect(0, 0, 0, 0, 256, 256); 和 drawTexturedModalRect(0, 0, 0, 0, 128, 128); 这两个显示出来的图片都正常, 也都没有拼贴的情况.
另外测试一个方式, 把我的图片从1024x1024尺寸转换为 512x512尺寸, 然后用drawTexturedModalRect(0, 0, 0, 0, 256, 256);的代码, 结果就显示不正确了, 图片变成放大很多.
因为我不想用256x256的规格来画我想要的图片, 不够细致.
我被它这个贴图代码搞得头昏脑胀, 根本不知道要从何依据了.
上次居然玩砸了,What a shame...
首先先要说明两件事
1.如果绘制一个纹理并且绘制区域大小大于纹理大小的话(比如用一个64x64的纹理绘制一个256x256的矩形),OpenGL有多种处理方式,其中一个(应该是默认的)就是重复绘制,换句话说就是你所说的"像是拼贴的样子一直延伸出去"
2.[删去了大段的话,发现最近自己变笨了无法像以前那样讲原理了,很显然最近是撸多了],Minecraft对纹理截取的方式是通过比例数值确定4个点然后取样,左上那个点的比例坐标是(u * x系数,v * y系数),右下那个点的坐标是((u + width) * x系数,(v + height) * y系数)
3.游戏的GUI Scale默认为Auto,实际上(至少是在我的机器上)除了Small以外,任何设置下游戏都会对绘制到屏幕上的GUI进行缩放,256x256的纹理相当于一个背包栏,正常显示的339x456应该会撑满整个屏幕吧
4.我赌⑨毛你将你的纹理不用的部分涂为了透明
5.好像不止两件事了
如果你看懂了头3件事那你应该就能明白后面我要说的了,首先,你的纹理太大了...在我们能找到规避无情的GUI Scale之前,先以256x256为参照物来设计纹理.另外,你说drawTexturedModalRect(0, 0, 0, 0, 256, 256)能正常显示,是因为按照正常系数,这刚好是绘制一整张纹理,不存在重复绘制的问题...你说你在缩小drawTexturedModalRectWisel(1024, 1024, 0, 0, 0, 0, 339, 456)中的1024时遇到了图片缩小和拼贴的情况,首先,这是个数字游戏,系数的算法是1/宽度或1/长度,取样点的算法之一是((u + width) * x系数,(v + height) * y系数),随着宽度与长度的缩小,系数会越来越大,从纹理上截取到的区域也越大,但要绘制的矩形却不变,因此游戏便缩小了纹理,你那图片也越来越小了.至于纹理重复,是因为你所要绘制的矩形的长宽不相同的问题造成的...不用说也是能明白的.
至于那个缩小尺寸反而图片变大...你确定你用的是缩放而不是截取吗...那个我想了半天也没想出来是怎么回事.按理说,只要你有内容的区域在纹理中占的面积比例不变,那绘制出来的就应该是相同的.
请原谅我资质驽钝, 某些部分我理解能力还不足.
第1点, 我大约了解了, 如果游戏内定是用拼贴的方式来展现纹理, 那么我就得去仔细算一下我要使用的小图片尺寸了.
第2点, 纹理截取的座标方式理解了.
第3点, 如果GUI的Scale 是以Auto的方式来进行, 这也表示我必须在Scale代码进行设定才会展现出符合我想要的画面, 对吧.
第4点, 是的, 通常我都会把不用的区域涂成透明色.
经由你的提醒后, 我把重点放在 Scale 这代码上, 我稍微的测试了一下, 图片都我自己绘制, 在一张256x256尺寸的图片里, 我画了 140x13 尺寸的小图, 使用GL11.glScalef(0.25f, 0.25f, 0.25f) 它才显示出1:1的尺寸. 当我在另外一张 512x512尺寸的图片里画一个 339x456尺寸的小图, 使用GL11.glScalef(0.5f, 0.5f, 0.5f) 它才显示出1:1的尺寸, 虽然这解决了我的显示图片问题, 但我仍然不了解 drawTexturedModalRect 这代码的运作到底准不准确.
Forge的更新两三天就来一次, 我也不知道它在绘图这方面有没有修正什么Bug, 我目前只能用得上就用, 用不上就另外找方法来实现自己想要的功能了.
谢谢你的指导, 我想我该让你知道,我有做出小小的模组发布在巴哈姆特, 也顺便帮你推广一下.
文章位址: http://forum.gamer.com.tw/C.php?bsn=18673&snA=77253&tnum=1
一起加油吧~
再来跟你请教一下NBT的问题, 我在使用TileEntity的NBT上没有问题, 它能随时存读.
但是, 我用Entity的NBT就有很大的问题了, 我试过在write和read的NBT那里放 system.out.println 来观察我设定的变数存读情形, 结果系统都很久才存读一次, 而且在存读方面也有问题, 一开始是存读正确的值, 在接着下去的值都是 0了.
例如以下我使用的代码:
@Override
public void readEntityFromNBT(NBTTagCompound nbt) {
super.readEntityFromNBT(nbt);
this.safeMode = nbt.getBoolean("NBT_Attacking");
system.out.println("读目前值="+this.safe.Mode);
}
@Override
public void writeEntityToNBT(NBTTagCompound nbt) {
super.writeEntityToNBT(nbt);
nbt.setBoolean("NBT_Attacking", this.safeMode);
system.out.println("存目前值="+this.safe.Mode);
}
我的主要疑问是为何Entity的NBT不是随时在存读, 而 TileEntity的NBT就时常在存读?
可否请你在指导一下 "Entity" 的NBT正确用法.
NBT的作用之一是将数据写入硬盘上的存档,以及将存档中的数据读入游戏,并非所有NBT都适合存储实时需要的数据,Entity的readEntityFromNBT和writeEntityToNBT并不能保证实时存读数据(另外TileEntity真的是实时存读吗...我感觉它在没有增删时每900Tick才存储一次...).
另外Entity在读取NBT时,有些地方采用这种写法:
if (par1NBTTagCompound.hasKey("xxx"))
this.xxxx = par1NBTTagCompound.getXX("xxx");
换句话说是先判断有没有已存储的数据,然后再进行读取,不过我也不明白为什么要这样...也许有时存在没存的时候就先读的情况?
既然NBT不灵了,那麽我们就得用别的办法来存读数据,比如DataWatcher.
DataWatcher除了存读数据以外,它还有保证客户端与服务器端之间数据同步的功能.DataWatcher有0至31共计32个可用的数据值(其实我喜欢把它叫"通道"或"频道"),每个数据值可以存储一个数据,通过底层实现,DataWatcher会尽量保证一个数据值在客户端和服务器端都是相同的.
DataWatcher的使用非常酷似C#的访问器,即通过setXXX来修改XXX变量,getXXX来获取XXX变量的值.在Minecraft中你能看到类似的写法.
使用DataWatcher前,先要在实体的DataWatcher中注册数据值,注册数据值通常在entityInit中进行,以EntityCreeper为例,0~15号数据值被基类使用了,16号数据值被用来储存它爆炸前蓄力时间,17号用来存储它是否被闪电充能过.因此它的entityInit是:
protected void entityInit()
{
super.entityInit();
this.dataWatcher.addObject(16, Byte.valueOf((byte) - 1));
this.dataWatcher.addObject(17, Byte.valueOf((byte)0));
}
addObject是向DataWatcher中注册一个数据值(万不可用它来修改值),数据类型只能为Byte,Short,Int,Float,String,ItemStack和ChunkCoordinates中的一种.
修改数据值通过updateObject来进行.获取数据值通过getWatchableObjectXXX来进行,XXX为类型名.
一个数据值在被注册时以及被修改后会被自动设为待更新状态,DataWatcher会尽快完成同步并撤销状态,你也可以通过setObjectWatched来手动将一个数据值设为待更新状态.
另外,有人之前和我聊过这个问题,我想可能你也会问,就是DataWatcher怎么和Entity中的各个字段(或者叫变量吧,虽然很不准确.我不知道Field在台湾翻译为什么.)保持关联的,事实上,它们并没有关联,开发者可以手动将DataWatcher中的数据刷新入字段,也可以完全不管字段,直接从DataWatcher中读取值来用.通常来说是采用直接从DataWatcher中读取值来用的方式.
总结来说,NBT用来将数据保存在硬盘当中,DataWatcher用于游戏中实际使用.
感谢精辟的解说, 看来, 我又得去研读DataWatcher的详细资料了.
非常谢谢你提供此资讯让我了解, 不然, 我老是在NBT那里打转XD
请问一下
如果说我想了解类别下的所有属性的话
我可以怎么做呢?
例如哪个网站有资料?
亦或如何查询?
看名字,看注释~
不过名字也有起错或含糊不清的时候(比如很早以前GuiPlayerInfo就是个被MCP组起错的名字,它和Gui毫无关系,现在应该已经修正了),注释也有写错的时候(比如MovingObjectPosition的sideHit的注释,现在似乎还没更正呢).这时就要靠Eclipse的神器:References和Hierarchy了.
References是查找一个东西(可以是Field,可以是Method,也可以是Class)在哪里被引用到了,它在右键菜单里,默认快捷键Ctrl+Shift+G是在整个Workspace中查找引用.
Hierarchy分为Type Hierarchy和Call Hierarchy,前者是查找一个类的所有派生类和基类以及它们之间的继承关系,后者类似于References,只不过它能一口气查到底,并给出调用关系.它们同样在右键菜单里.
我主要通过这些东西来分析一个Class或Field或Method的功能...至于文档和网站...文档已经被自动融合进代码里了,网站...似乎真缺乏这类网站,日本Wiki上有很少的一些解释资料(而且大部分已经过期了)
感谢你的回复!
也谢谢你的文章造福大众!
继上次的DataWatcher研究时, 额外碰到 setEntityState 的问题, 我在许多怪物和动物的Entity代码里发现许多地方有用到这指令, 而且影响蛮大的, 我也找了很多关于 setEntityState 的解答, 但老外那边似乎没有人在提这东西, 他们只知道要用这指令, 但不知道指令里的参数为何要这样子用.
我想请教你, setEntityState 这指令是World类里的, 我点进去看, 查了它相关的参照, 没有一个地方会显示他的参数"代表意义".
我举例 EntityIronGolem 代码里用的参数是 this.worldObj.setEntityState(this, (byte)4), 而 EntityWolf 代码里用的参数是 this.worldObj.setEntityState(this, (byte)7) 和 this.worldObj.setEntityState(this, (byte)6)
我很想知道为什么有的数值用4, 有的用6 或 7 , 我上网想查那些数值代表意义, 但都没有任何资料可查, 可否帮我解惑一下?
setEntityState的作用是从服务器端发一个信号给客户端,该信号代表某Entity的某个状态发生变化,换句话说这是个轻量级的数据传输方案~只不过是单向的(其实DataWatcher应该也是单向的吧...)
setEntityState的第二个参数代表信号类型.任何一个继承了Entity类的类都可以通过重写handleHealthUpdate方法来处理信号(别忘了将不能处理的信号通过super.handleHealthUpdate传给基类去处理).只有2(代表遭受攻击)和3(代表被杀死)这两个信号是"公用"的,是所有EntityLivingBase类的派生类都能识别的,其它的则是由接受的类来实现.
举例,当服务器端判断某Entity受到攻击时,会调用world.setEntityState(this,(byte)2) (假设world是其所处世界,this指该Entity).服务器会将信息广播给有机会与此Entity互动的玩家,玩家的客户端收到信息后会调用entity.handleHealthUpdate((byte)2) (假设entity为此Entity).效果是让它做出被伤害的特效.
对于各个信号值的含义,只要知道2和3分别代表遭受攻击和被杀死就可以了,因为除此之外的信号值是不通用的,尽管Minecraft为每种信号值只规定了一种含义...不过我还是整理了一下部分信号值的含义
2 遭受攻击/受到伤害
3 死亡
4 IronGolem发起攻击
6 驯服失败
7 被驯服
8 狼甩掉身上的水
9 物品使用完毕
10 一个"小动作"(吃草)
11 IronGolem拿到玫瑰
13 村民愤怒
14 村民高兴
15 Witch身旁产生粒子特效
16 僵尸被转化为村民
18 被驯养的动物开始繁殖
感谢说明, 这样子至少我有个底了.
不过, 我很好奇, 你是如何得知这些信号是做为哪些作用的?
例如信号4和信号11, 它固定都是IronGolem在用的吗?
因为, 我目前卡在类似IronGolem的手摆动描绘问题, 我必须借由setEntityState才能使我自制的其它动作在客户端上看得到, 因为我是仿照IronGolem的手摆动设计方式来写我自己想要的动作, 如果不依靠setEntityState这指令, 在客户端就看不到动作的改变.
请问, 你有什么好建议可以提供参考一下?
看它们的handleHealthUpdate来判断他们是怎么解析信号值,或者看它们怎么调用setEntityState.只有IronGolem的handleHealthUpdate能解析4和11,所以4和11两个信号值可以说是在原版Minecraft中IronGolem专用的.然而对开发者而言不必死守"一个信号值只对应一个实体的一种动作"这个规定...
除了setEntityState以外,还有其他一些从服务器向客户端发送信息的方式,比如自定义封包等...教程可以看这个http://www.minecraftforge.net/wiki/Packet_Handling,不过就是太麻烦了.
谢谢说明, 我再去研读一下.
我想知道蛋糕这些半格高的东西会讲解吗?还有像蛋糕的功能能否也讲解下呢?