利益相关:minecraft modder
“怎么设计的”和“设计成什么样的”是两个不同的问题,“内部的逻辑,一些奇特的机制”都是“设计成什么样的问题。当然这两个问题是紧密相关的。
从软件工程的角度,minecraft的代码其实写的不那么漂亮,因为它是一个典型的快速开发不断迭代的项目,看代码就很容易能看出来,minecraft很多部分都明显是先有一个方便的原型, 然后部分重构,再部分重构,这么拖着改到今天这么大的。所以里面有很多不规范、临时的用法在里面残留着。今天我就着重说一说MC写的烂的地方。 再顺便黑一黑java
举几个例子
1、GUI
MC的GUI是lwjgl从头写的,它写的难看到了什么地步呢,就是随便一个稍微上一点档次的mod,都要重新造一遍轮子。MC GuiScreen里面的一个鼠标事件是这么写的:
protected void mouseClicked(int x, int y, int enable)
if (enable == 0)
for (int l = 0; l < this.buttonList.size(); ++l)
GuiButton guibutton = (GuiButton)this.buttonList.get(l);
if (guibutton.mousePressed(this.mc, x, y))
this.selectedButton = guibutton;
guibutton.playsound(this.mc.getSoundHandler());
this.actionPerformed(guibutton);
GuiButton.mousePressed()长这样:
public boolean mousePressed(Minecraft p_146116_1_, int p_146116_2_, int p_146116_3_)
return this.enabled && this.visible && p_146116_2_ >= this.xPosition && p_146116_3_ >= this.yPosition && p_146116_2_ < this.xPosition + this.width && p_146116_3_ < this.yPosition + this.height;
对,根本没有事件,也没有回调,逻辑必须在主窗口类的actionPerformed里处理,这是上个世纪90年代的写法。所以说稍微有点档次的mod都要重写GUI,因为MC本来写的真是太难看了。
(顺便一提,forge用ASM在这里生插进去了一个事件,虽然这个事件会把别人的按钮事件也发送给你。所以还算有事件可用,但是这就不算MC本身写的了。)
2、注册
我又回头看了看,注册这个问题很大,所以我决定不止讲那个硬编码的部分,展开说。
首先就是一段硬编码的注册代码。这是Block类里的:
public static void registerBlocks()
blockRegistry.addObject(0, "air", (new BlockAir()).setBlockName("air"));
blockRegistry.addObject(1, "stone", (new BlockStone()).setHardness(1.5F).setResistance(10.0F).setStepSound(soundTypePiston).setBlockName("stone").setBlockTextureName("stone"));
blockRegistry.addObject(2, "grass", (new BlockGrass()).setHardness(0.6F).setStepSound(soundTypeGrass).setBlockName("grass").setBlockTextureName("grass"));
blockRegistry.addObject(3, "dirt", (new BlockDirt()).setHardness(0.5F).setStepSound(soundTypeGravel).setBlockName("dirt").setBlockTextureName("dirt"));
Block block = (new Block(Material.rock)).setHardness(2.0F).setResistance(10.0F).setStepSound(soundTypePiston).setBlockName("stonebrick").setCreativeTab(CreativeTabs.tabBlock).setBlockTextureName("cobblestone");
blockRegistry.addObject(4, "cobblestone", block);
Block block1 = (new BlockWood()).setHardness(2.0F).setResistance(5.0F).setStepSound(soundTypeWood).setBlockName("wood").setBlockTextureName("planks");
blockRegistry.addObject(5, "planks", block1);
blockRegistry.addObject(6, "sapling", (new BlockSapling()).setHardness(0.0F).setStepSound(soundTypeGrass).setBlockName("sapling").setBlockTextureName("sapling"));
blockRegistry.addObject(7, "bedrock", (new Block(Material.rock)).setBlockUnbreakable().setResistance(6000000.0F).setStepSound(soundTypePiston).setBlockName("bedrock").disableStats().setCreativeTab(CreativeTabs.tabBlock).setBlockTextureName("bedrock"));
blockRegistry.addObject(8, "flowing_water", (new BlockDynamicLiquid(Material.water)).setHardness(100.0F).setLightOpacity(3).setBlockName("water").disableStats().setBlockTextureName("water_flow"));
blockRegistry.addObject(9, "water", (new BlockStaticLiquid(Material.water)).setHardness(100.0F).setLightOpacity(3).setBlockName("water").disableStats().setBlockTextureName("water_still"));
blockRegistry.addObject(10, "flowing_lava", (new BlockDynamicLiquid(Material.lava)).setHardness(100.0F).setLightLevel(1.0F).setBlockName("lava").disableStats().setBlockTextureName("lava_flow"));
blockRegistry.addObject(11, "lava", (new BlockStaticLiquid(Material.lava)).setHardness(100.0F).setLightLevel(1.0F).setBlockName("lava").disableStats().setBlockTextureName("lava_still"));
blockRegistry.addObject(12, "sand", (new BlockSand()).setHardness(0.5F).setStepSound(soundTypeSand).setBlockName("sand").setBlockTextureName("sand"));
blockRegistry.addObject(13, "gravel", (new BlockGravel()).setHardness(0.6F).setStepSound(soundTypeGravel).setBlockName("gravel").setBlockTextureName("gravel"));
blockRegistry.addObject(14, "gold_ore", (new BlockOre()).setHardness(3.0F).setResistance(5.0F).setStepSound(soundTypePiston).setBlockName("oreGold").setBlockTextureName("gold_ore"));
后面不往下写了,都是这样,直到把所有方块注册完。这是flash小游戏的写法吧。
不过硬编码和硬编码也不一样。你应该注意一下细节,blockRegistry.addObject()的第一个参数是一个int,它是方块的序号。在RegistryNamespaced里,这个序号作map的key值。序号硬编码会造成什么恶果呢?对,序号冲突。自己改序号不好改(所以你看到MC从来没有删除过物品或者方块,也没有在中间添加过,因为会导致序号改变),别人加物品更头痛。所以1.6以及之前,mod一直有一个序号冲突的问题。那么在1.7是否解决了序号问题呢?实际上,没有。1.7的方块问题最大的改动是加进了一个UnlocalizedName,意思就是说你以后用这个名字来找方块,但是实际上代码内部还是用int编号硬编码。而1.8最大的改动是让你在游戏内命令里不能按照序号give方块了,而代码内部还是跟上面没什么区别,用int硬编码。
于是在1.7,fml加进了自动分配序号的功能:
package cpw.mods.fml.common.registry;
private int registerBlock(Block block, String name, int idHint)
// handle ItemBlock-before-Block registrations
ItemBlock itemBlock = null;
for (Item item : iItemRegistry.typeSafeIterable()) // find matching ItemBlock
if (item instanceof ItemBlock && ((ItemBlock) item).field_150939_a == block)
itemBlock = (ItemBlock) item;
break;
if (itemBlock != null) // has ItemBlock, adjust id and clear the slot already occupied by the corresponding item
idHint = iItemRegistry.getId(itemBlock);
FMLLog.fine("Found matching ItemBlock %s for Block %s at id %d", itemBlock, block, idHint);
freeSlot(idHint, block); // temporarily free the slot occupied by the Item for the block registration
// add
int blockId = iBlockRegistry.add(idHint, name, block, availabilityMap);
if (itemBlock != null) // verify
if (blockId != idHint) throw new IllegalStateException(String.format("Block at itemblock id %d insertion failed, got id %d.", idHint, blockId));
verifyItemBlockName(itemBlock);
useSlot(blockId);
((RegistryDelegate.Delegate<Block>) block.delegate).setName(name);
return blockId;
用的namespace都不是原来的RegistryNamespaced,是fml自己的FMLControlledNamespacedRegistry<I>,也就是说原来那个已经没法用了。对,你看fml还加泛型,可以限定这个命名空间给方块用或者给物品用,这就是人的写法。
看中间那个if,就是在判断命名空间里有没有;如果没有,重新分配序号。
这个就是有fml,帮我们解决了MC的代码问题。如果fml没解决呢?
public class Potion
/** The array of potion types. */
public static final Potion[] potionTypes = new Potion[32];
public static final Potion field_76423_b = null;
public static final Potion moveSpeed = (new Potion(1, false, 8171462)).setPotionName("potion.moveSpeed").setIconIndex(0, 0).func_111184_a(SharedMonsterAttributes.movementSpeed, "91AEAA56-376B-4498-935B-2F7F68070635", 0.20000000298023224D, 2);
public static final Potion moveSlowdown = (new Potion(2, true, 5926017)).setPotionName("potion.moveSlowdown").setIconIndex(1, 0).func_111184_a(SharedMonsterAttributes.movementSpeed, "7107DE5E-7CE8-4030-940E-514C1F160890", -0.15000000596046448D, 2);
public static final Potion digSpeed = (new Potion(3, false, 14270531)).setPotionName("potion.digSpeed").setIconIndex(2, 0).setEffectiveness(1.5D);
public static final Potion digSlowdown = (new Potion(4, true, 4866583)).setPotionName("potion.digSlowDown").setIconIndex(3, 0);
public static final Potion damageBoost = (new PotionAttackDamage(5, false, 9643043)).setPotionName("potion.damageBoost").setIconIndex(4, 0).func_111184_a(SharedMonsterAttributes.attackDamage, "648D7064-6A60-4F59-8ABE-C2C23A6DD7A9", 3.0D, 2);
public static final Potion heal = (new PotionHealth(6, false, 16262179)).setPotionName("potion.heal");
public static final Potion harm = (new PotionHealth(7, true, 4393481)).setPotionName("potion.harm");
public static final Potion jump = (new Potion(8, false, 7889559)).setPotionName("potion.jump").setIconIndex(2, 1);
public static final Potion confusion = (new Potion(9, true, 5578058)).setPotionName("potion.confusion").setIconIndex(3, 1).setEffectiveness(0.25D);
/** The regeneration Potion object. */
public static final Potion regeneration = (new Potion(10, false, 13458603)).setPotionName("potion.regeneration").setIconIndex(7, 0).setEffectiveness(0.25D);
public static final Potion resistance = (new Potion(11, false, 10044730)).setPotionName("potion.resistance").setIconIndex(6, 1);
/** The fire resistance Potion object. */
public static final Potion fireResistance = (new Potion(12, false, 14981690)).setPotionName("potion.fireResistance").setIconIndex(7, 1);
/** The water breathing Potion object. */
public static final Potion waterBreathing = (new Potion(13, false, 3035801)).setPotionName("potion.waterBreathing").setIconIndex(0, 2);
/** The invisibility Potion object. */
public static final Potion invisibility = (new Potion(14, false, 8356754)).setPotionName("potion.invisibility").setIconIndex(0, 1);
/** The blindness Potion object. */
public static final Potion blindness = (new Potion(15, true, 2039587)).setPotionName("potion.blindness").setIconIndex(5, 1).setEffectiveness(0.25D);
/** The night vision Potion object. */
public static final Potion nightVision = (new Potion(16, false, 2039713)).setPotionName("potion.nightVision").setIconIndex(4, 1);
/** The hunger Potion object. */
public static final Potion hunger = (new Potion(17, true, 5797459)).setPotionName("potion.hunger").setIconIndex(1, 1);
/** The weakness Potion object. */
public static final Potion weakness = (new PotionAttackDamage(18, true, 4738376)).setPotionName("potion.weakness").setIconIndex(5, 0).func_111184_a(SharedMonsterAttributes.attackDamage, "22653B89-116E-49DC-9B6B-9971489B5BE5", 2.0D, 0);
/** The poison Potion object. */
public static final Potion poison = (new Potion(19, true, 5149489)).setPotionName("potion.poison").setIconIndex(6, 0).setEffectiveness(0.25D);
/** The wither Potion object. */
public static final Potion wither = (new Potion(20, true, 3484199)).setPotionName("potion.wither").setIconIndex(1, 2).setEffectiveness(0.25D);
public static final Potion field_76434_w = (new PotionHealthBoost(21, false, 16284963)).setPotionName("potion.healthBoost").setIconIndex(2, 2).func_111184_a(SharedMonsterAttributes.maxHealth, "5D6F0BA2-1186-46AC-B896-C61C5CEE99CC", 4.0D, 0);
public static final Potion field_76444_x = (new PotionAbsoption(22, false, 2445989)).setPotionName("potion.absorption").setIconIndex(2, 2);
public static final Potion field_76443_y = (new PotionHealth(23, false, 16262179)).setPotionName("potion.saturation");
public static final Potion field_76442_z = null;
public static final Potion field_76409_A = null;
public static final Potion field_76410_B = null;
public static final Potion field_76411_C = null;
public static final Potion field_76405_D = null;
public static final Potion field_76406_E = null;
public static final Potion field_76407_F = null;
public static final Potion field_76408_G = null;
没错,这就是药水类的注册。也不用什么register了,直接写static了。
好吧,你是怎么写的我也不想管了,我就想加几种自己定义的药水效果。然后我们就看到了一行代码:
public static final Potion[] potionTypes = new Potion[32];
尼玛。数组,而且还是定长数组。
怎么办?老实说,没什么好办法,因为fml没帮你解决这个问题。固然你可以换掉这个数组给它扩容,但是这必然会导致mod和mod不兼容。反正,现在版本依然有大量的药水效果冲突的问题存在。比如你可以在1.7.10装个IC再装个环境,看饥渴效果是怎么变成辐射效果把你秒了的。TC的药水数量已经自己都在那几个空里装不下了,他自己实现了一个potion数组,但是他不是fml,我们也没法都转移过去,所以这个问题也没什么好办法。
public abstract class Enchantment
public static final Enchantment[] enchantmentsList = new Enchantment[256];
/** The list of enchantments applicable by the anvil from a book */
public static final Enchantment[] enchantmentsBookList;
/** Converts environmental damage to armour damage */
public static final Enchantment protection = new EnchantmentProtection(0, 10, 0);
/** Protection against fire */
public static final Enchantment fireProtection = new EnchantmentProtection(1, 5, 1);
/** Less fall damage */
public static final Enchantment featherFalling = new EnchantmentProtection(2, 5, 2);
/** Protection against explosions */
public static final Enchantment blastProtection = new EnchantmentProtection(3, 2, 3);
/** Protection against projectile entities (e.g. arrows) */
public static final Enchantment projectileProtection = new EnchantmentProtection(4, 5, 4);
/** Decreases the rate of air loss underwater; increases time between damage while suffocating */
public static final Enchantment respiration = new EnchantmentOxygen(5, 2);
/** Increases underwater mining rate */
public static final Enchantment aquaAffinity = new EnchantmentWaterWorker(6, 2);
public static final Enchantment thorns = new EnchantmentThorns(7, 1);
/** Extra damage to mobs */
public static final Enchantment sharpness = new EnchantmentDamage(16, 10, 0);
/** Extra damage to zombies, zombie pigmen and skeletons */
public static final Enchantment smite = new EnchantmentDamage(17, 5, 1);
/** Extra damage to spiders, cave spiders and silverfish */
public static final Enchantment baneOfArthropods = new EnchantmentDamage(18, 5, 2);
/** Knocks mob and players backwards upon hit */
public static final Enchantment knockback = new EnchantmentKnockback(19, 5);
/** Lights mobs on fire */
public static final Enchantment fireAspect = new EnchantmentFireAspect(20, 2);
/** Mobs have a chance to drop more loot */
public static final Enchantment looting = new EnchantmentLootBonus(21, 2, EnumEnchantmentType.weapon);
/** Faster resource gathering while in use */
public static final Enchantment efficiency = new EnchantmentDigging(32, 10);
* Blocks mined will drop themselves, even if it should drop something else (e.g. stone will drop stone, not
* cobblestone)
public static final Enchantment silkTouch = new EnchantmentUntouching(33, 1);
/** Sometimes, the tool's durability will not be spent when the tool is used */
public static final Enchantment unbreaking = new EnchantmentDurability(34, 5);
/** Can multiply the drop rate of items from blocks */
public static final Enchantment fortune = new EnchantmentLootBonus(35, 2, EnumEnchantmentType.digger);
/** Power enchantment for bows, add's extra damage to arrows. */
public static final Enchantment power = new EnchantmentArrowDamage(48, 10);
/** Knockback enchantments for bows, the arrows will knockback the target when hit. */
public static final Enchantment punch = new EnchantmentArrowKnockback(49, 2);
/** Flame enchantment for bows. Arrows fired by the bow will be on fire. Any target hit will also set on fire. */
public static final Enchantment flame = new EnchantmentArrowFire(50, 2);
* Infinity enchantment for bows. The bow will not consume arrows anymore, but will still required at least one
* arrow on inventory use the bow.
public static final Enchantment infinity = new EnchantmentArrowInfinite(51, 1);
public static final Enchantment field_151370_z = new EnchantmentLootBonus(61, 2, EnumEnchantmentType.fishing_rod);
public static final Enchantment field_151369_A = new EnchantmentFishingSpeed(62, 2, EnumEnchantmentType.fishing_rod);
附魔的问题好一些,好在什么地方呢?他的数组长。有时候想想挺可笑的,写mod遇到这么大麻烦,就是因为mojang有个数写的不够大。
3、性能
性能这一块我要分两部分,先黑java,再黑mojang。
没错,java性能就是差,你来打我啊。
打完了没有,我要回家了。
好吧,让我们认真来说,java性能就是差。当然mc也跟所有的java程序一样,里面充满了new new new new,耗内存比耗CPU还多,这么个小游戏居然没个2G内存带不起来。画图性能也不咋地,当然最新的lwjgl3.0有了一定的好转,不过稍早的mc用的还是2.4,简直惨,就这么个根本没有几个多边形的游戏,看着不同的地方能差十几帧。
算法倒没什么可黑的,mc主要吃CPU的算法都写的中规中矩 网上抄的 ,按照java规范来,快不到哪去但也没法写的再快了。比如PathNavigate和WorldGen系列,算法比较复杂,详细的就不贴了。
好,黑java差不多,该黑mojang了。先给你们看看1.8特性列表里的一栏。
渲染 & 图像优化
- 显著的提高 FPS 和性能
- 每个世界(主世界、下界、末路之地) 现在各自独立运作
- 区块渲染和区块重建现在使用多线程- 超快速的区块渲染!
- 重写了区块序列
- Better visibility culling code
- 生物寻路现在是多线程的
- 重写储物系统
- 矿物生成现在较以前快了2倍
- 现在只有透明方块才可以被渲染为透明的(移除X光材质包的使用)
- Dropped items now face the player in all three directions on fast graphics
- 重写方块是如何被渲染的
- 重写方块数据是如何被处理的
- 改善游戏并使"cooler things" 得以被实现
也就是说,直到1.8之前,区块重建、矿物生成、多世界、生物寻路都是 单线程 的。你要知道,这些可都是性能大头,这些部分是单线程的,意味着mc服务器基本上就是单线程的……
坑爹啊!
还是给你们看一看mc的线程状况吧。以下以1.7.10版本为准。
怎么知道mc哪里有线程呢?搜索Thread和synchronized关键字。
好嘛,有OpenGL用的,有OpenAL用的,有twitch流用的,有Crash的时候打印错误信息用的,有网关IO用的,就是没有算法用的……
再看看synchronized
跟上面差不多,有文件IO的,有网络IO的,有图像IO的,有视频IO的……
不过world类里那两个是真的,Chunk和Gen确实有多线程,这个等黑完了我再夸一夸。
好吧,有没有可能我们漏掉了什么地方,实际上mc多数代码早已悄悄躲进几个大线程里,普通代码根本不用加锁呢?我们来调试一下看看:
什么乱七八糟线程都有,就是看不见算法分线程。实际上MC代码就全都运行在就那一个高亮的Server Thread里面。
1.8进步了,不过有限。如果照样搜一遍的话,我们会看到ChunkRender里有了Thread和synchronized,对应上面的方块渲染。
珍爱服务器,远离1.7。希望微软赶紧用C++把mc重写。 然后把学不会C++的都赶走,这样我就是领军人物了。如果能招我进微软就更好了。
4、API
现在想了想,API的问题应该放到第一去讲的。但是实际上上面三个都是我们modder的眼中钉肉中刺,相比之下API的问题都已经习惯了,所以我现在才想起来这个问题。
那么我们就讲讲mc里的API怎么写的。
问:MC里的API怎么写的?
答:MC根本没有API。
我们刚刚提过,MC是从一个小游戏慢慢变大的,所以里面有很多历史遗留写法。细节上的历史遗留讲的很多了,那么架构上最大的遗留问题是什么呢?那就是API。
MC的架构,根本没有给写API留下什么空间。
仍以一个方块为例,MC里采用一种类似享元模式的设计模式处理方块,每一种方块都是一个对象。为了创造具有特殊行为的方块,需要写一个新类继承Block类,并且Override相应的函数以实现自己的行为。那么,Block类大概有多大呢?
2631行,大概200个可以被重载的函数,其中大部分都在某个方块类里被重载了。
举个特定方块类的例子,比如矿物方块。
public class BlockOre extends Block
public BlockOre()
super(Material.rock);
this.setCreativeTab(CreativeTabs.tabBlock);
public Item getItemDropped(int p_149650_1_, Random p_149650_2_, int p_149650_3_)
return this == Blocks.coal_ore ? Items.coal : (this == Blocks.diamond_ore ? Items.diamond : (this == Blocks.lapis_ore ? Items.dye : (this == Blocks.emerald_ore ? Items.emerald : (this == Blocks.quartz_ore ? Items.quartz : Item.getItemFromBlock(this)))));
public int quantityDropped(Random p_149745_1_)
return this == Blocks.lapis_ore ? 4 + p_149745_1_.nextInt(5) : 1;
public int quantityDroppedWithBonus(int p_149679_1_, Random p_149679_2_)
if (p_149679_1_ > 0 && Item.getItemFromBlock(this) != this.getItemDropped(0, p_149679_2_, p_149679_1_))
int j = p_149679_2_.nextInt(p_149679_1_ + 2) - 1;
if (j < 0)
j = 0;
return this.quantityDropped(p_149679_2_) * (j + 1);
return this.quantityDropped(p_149679_2_);
public void dropBlockAsItemWithChance(World p_149690_1_, int p_149690_2_, int p_149690_3_, int p_149690_4_, int p_149690_5_, float p_149690_6_, int p_149690_7_)
super.dropBlockAsItemWithChance(p_149690_1_, p_149690_2_, p_149690_3_, p_149690_4_, p_149690_5_, p_149690_6_, p_149690_7_);
private Random rand = new Random();
@Override
public int getExpDrop(IBlockAccess p_149690_1_, int p_149690_5_, int p_149690_7_)
if (this.getItemDropped(p_149690_5_, rand, p_149690_7_) != Item.getItemFromBlock(this))
int j1 = 0;
if (this == Blocks.coal_ore)
j1 = MathHelper.getRandomIntegerInRange(rand, 0, 2);
else if (this == Blocks.diamond_ore)
j1 = MathHelper.getRandomIntegerInRange(rand, 3, 7);
else if (this == Blocks.emerald_ore)
j1 = MathHelper.getRandomIntegerInRange(rand, 3, 7);
else if (this == Blocks.lapis_ore)
j1 = MathHelper.getRandomIntegerInRange(rand, 2, 5);
else if (this == Blocks.quartz_ore)
j1 = MathHelper.getRandomIntegerInRange(rand, 2, 5);
return j1;
return 0;
public int damageDropped(int p_149692_1_)
return this == Blocks.lapis_ore ? 4 : 0;
看,新方块类就这么写,继承一下Block,然后就开始胡改了。不管是被按照Block调用还是调用别的函数,都没有中间层抽象,都是直接调用。耦合成这个样子,怎么写API?无怪mojang 1.6的时候就说要写API了,都这么久了还一点消息没有,这怎么能写出来API。
现在所有的modder写mod,都必须拿着反编译反混淆过的源码,大量地直接使用MC里原有的基类和调用函数,把自己的代码完全嵌入MC的架构里,否则没法写。
反编译是否违法呢?当然违法,人家可是商业软件。但是有什么办法,我还想有成熟的API可用呢,mojang自己不争气写不出来API,又不敢关门撵人,所以就变成了现在这个样子,mojang默许整个MC社区使用MC的反编译代码(这个工程叫MCP)来写mod。所以说mod社区对微软收购这件事这么敏感,因为本来就名不正言不顺。
如果MC有了API,大概会是什么样子呢?虽然目前还没有可以整个用来写mod的API,不过一个子集已经有了,叫bukkit。bukkit很能说明一个有了API的开发,是一种什么感受。bukkit大概总共就这么长:
org.bukkit
More generalized classes in the API.
org.bukkit.block
Classes used to manipulate the voxels in a world, including special states.
org.bukkit.block.banner
org.bukkit.command
Classes relating to handling specialized non-chat player input.
org.bukkit.command.defaults
Commands for emulating the Minecraft commands and other necessary ones for use by a Bukkit implementation.
org.bukkit.configuration
Classes dedicated to handling a plugin's runtime configuration.
org.bukkit.configuration.file
Classes dedicated facilitating configurations to be read and stored on the filesystem.
org.bukkit.configuration.serialization
Classes dedicated to being able to perform serialization specialized for the Bukkit configuration implementation.
org.bukkit.conversations
Classes dedicated to facilitate direct player-to-plugin communication.
org.bukkit.enchantments
Classes relating to the specialized enhancements to item stacks, as part of the meta data.
org.bukkit.entity
Interfaces for non-voxel objects that can exist in a world, including all players, monsters, projectiles, etc.
org.bukkit.entity.minecart
Interfaces for various Minecart types.
org.bukkit.event
Classes dedicated to handling triggered code executions.
org.bukkit.event.block
Events relating to when a block is changed or interacts with the world.
org.bukkit.event.enchantment
Events triggered from an enchantment table.
org.bukkit.event.entity
Events relating to entities, excluding some directly referencing some more specific entity types.
org.bukkit.event.hanging
Events relating to entities that hang.
org.bukkit.event.inventory
Events relating to inventory manipulation.
org.bukkit.event.painting
Events relating to paintings, but deprecated for more general hanging events.
org.bukkit.event.player
Events relating to players.
org.bukkit.event.server
Events relating to programmatic state changes on the server.
org.bukkit.event.vehicle
Events relating to vehicular entities.
org.bukkit.event.weather
Events relating to weather.
org.bukkit.event.world
Events triggered by various world states or changes.
org.bukkit.generator
Classes to facilitate world generation implementation.
org.bukkit.help
Classes used to manipulate the default command and topic assistance system.
org.bukkit.inventory
Classes involved in manipulating player inventories and item interactions.
org.bukkit.inventory.meta
The interfaces used when manipulating extra data can can be stored inside item stacks.
org.bukkit.map
Classes to facilitate plugin handling of map displays.
org.bukkit.material
Classes that represents various voxel types and states.
org.bukkit.metadata
Classes dedicated to providing a layer of plugin specified data on various Minecraft concepts.
org.bukkit.permissions
Classes dedicated to providing binary state properties to players.
org.bukkit.plugin
Classes specifically relating to loading software modules at runtime.
org.bukkit.plugin.java
Classes for handling plugins written in java.
org.bukkit.plugin.messaging
Classes dedicated to specialized plugin to client protocols.
org.bukkit.potion
Classes to represent various potion properties and manipulation.
org.bukkit.projectiles
Classes to represent the source of a projectile
org.bukkit.scheduler
Classes dedicated to letting plugins run code at specific time intervals, including thread safety.
org.bukkit.scoreboard
Interfaces used to manage the client side score display system.
org.bukkit.util
Multi and single purpose classes to facilitate various programmatic concepts.
org.bukkit.util.io
Classes used to facilitate stream processing for specific Bukkit concepts.
org.bukkit.util.noise
Classes dedicated to facilitating deterministic noise.
org.bukkit.util.permissions
Static methods for miscellaneous permission functionality.
相比之下MC包的数量大概是他的三倍,类的数量……我不想算了,因为每个物品\方块都是类。
你要知道,这些部分全都是解耦的。bukkit这些接口是真正的接口,bukkit并不会规定其具体实现,每一版craftbukkit都会有所不同。而且bukkit向下兼容,其内容简洁易懂,符合软件工程原则,使用舒适度远超读MC那些诘屈聱牙的大段代码。这就是一个真正的API该有的样子。
forge是不是API呢?实际上,不是。在写mod的时候,只有两个部分要跟forge交互:
1.forge.event。原版MC是没有事件系统的,这个样子连调用自己的代码都办不到。forge加进了一些事件,包括MC初始化事件,让你有机会处理事务。
2.register。这基本上是一些便利类,能简化你注册方块、物品、翻译文件一类的流程,可以写一个函数搞定。
这些不是API,实际上它们没有抽象任何事情,你真正需要解决的问题还是得到源码里搞定,他只是帮你解决不该由你解决的问题而已。
5、网络
相信眼尖的同学在线程里已经看见了,MC的网络连接用的是netty。netty设计的大致还是没什么问题的,有问题这里也不提了。我们就看看netty IO的上下游,MC发包和解包怎么写的。
先看一下类,有Packet类,每种Packet有个类,有INetHandler,有NetHandler,写的挺好嘛,一个一个看。
public abstract class Packet
public static Packet generatePacket(BiMap p_148839_0_, int p_148839_1_)
Class oclass = (Class)p_148839_0_.get(Integer.valueOf(p_148839_1_));
return oclass == null ? null : (Packet)oclass.newInstance();
catch (Exception exception)
logger.error("Couldn\'t create packet " + p_148839_1_, exception);
return null;
public abstract void readPacketData(PacketBuffer p_148837_1_) throws IOException;
public abstract void writePacketData(PacketBuffer p_148840_1_) throws IOException;
public abstract void processPacket(INetHandler p_148833_1_);
}
不错,有抽象基类,还自带一个小工厂。找个类看看。
public class C09PacketHeldItemChange extends Packet
public C09PacketHeldItemChange(int slot)
this.hold = slot;
public void readPacketData(PacketBuffer buffer) throws IOException
this.bit= buffer.readShort();
public void writePacketData(PacketBuffer buffer) throws IOException
buffer.writeShort(this.bit);
public void processPacket(INetHandlerPlayServer nethandler)
nethandler.processHeldItemChange(this);
public void processPacket(INetHandler nethandler)
this.processPacket((INetHandlerPlayServer)nethandler);
}
……等等,解包之后的实现哪去了?
public class NetworkManager extends SimpleChannelInboundHandler
public void processReceivedPackets()
if (this.netHandler != null)
for (int i = 1000; !this.receivedPacketsQueue.isEmpty() && i >= 0; --i)
Packet packet = (Packet)this.receivedPacketsQueue.poll();
packet.processPacket(this.netHandler);
this.netHandler.onNetworkTick();
this.channel.flush();
这……
public class NetHandlerPlayServer implements INetHandlerPlayServer
public void processAnimation(C0APacketAnimation p_147350_1_)
this.playerEntity.func_143004_u();
if (p_147350_1_.func_149421_d() == 1)
this.playerEntity.swingItem();
public void processEntityAction(C0BPacketEntityAction p_147357_1_)
this.playerEntity.func_143004_u();
if (p_147357_1_.func_149513_d() == 1)
this.playerEntity.setSneaking(true);
else if (p_147357_1_.func_149513_d() == 2)
this.playerEntity.setSneaking(false);
else if (p_147357_1_.func_149513_d() == 4)
this.playerEntity.setSprinting(true);
else if (p_147357_1_.func_149513_d() == 5)
this.playerEntity.setSprinting(false);
else if (p_147357_1_.func_149513_d() == 3)
this.playerEntity.wakeUpPlayer(false, true, true);
this.hasMoved = false;
else if (p_147357_1_.func_149513_d() == 6)
if (this.playerEntity.ridingEntity != null && this.playerEntity.ridingEntity instanceof EntityHorse)
((EntityHorse)this.playerEntity.ridingEntity).setJumpPower(p_147357_1_.func_149512_e());
else if (p_147357_1_.func_149513_d() == 7 && this.playerEntity.ridingEntity != null && this.playerEntity.ridingEntity instanceof EntityHorse)
((EntityHorse)this.playerEntity.ridingEntity).openGUI(this.playerEntity);
public void processHeldItemChange(C09PacketHeldItemChange p_147355_1_)
if (p_147355_1_.func_149614_c() >= 0 && p_147355_1_.func_149614_c() < InventoryPlayer.getHotbarSize())
this.playerEntity.inventory.currentItem = p_147355_1_.func_149614_c();
this.playerEntity.func_143004_u();
logger.warn(this.playerEntity.getCommandSenderName() + " tried to set an invalid carried item");
(...)
}
……尼玛。
这解包的具体实现是写在Handler类里的,把每个被Handle的类的实现都写在Handler里
,比硬编码还硬编码。注册那些硬编码至少还是数据,只是难看点,不妨碍你继续往数组里加数据;这硬编码的是函数,根本不可能加。
于是我们很奇怪,接口呢?我们不是还有一个接口,可以用来实现具体解包过程吗?
public interface INetHandlerPlayServer extends INetHandler
void processAnimation(C0APacketAnimation p_147350_1_);
void processChatMessage(C01PacketChatMessage p_147354_1_);
void processPlayerDigging(C07PacketPlayerDigging p_147345_1_);