又谈工作台类型的设计方法(1.12.2FML)

再次强烈谴责专栏没法贴代码块的功能
前往TIS论坛,感受更好的 Markdown 渲染体验:https://forum.tis.world/topic/137/又谈工作台类型的设计方法-1-12-2FML
启
最近也有需求,需要设计一个类似原版工作台的方块“注魔台”,支持在消耗一些素材的情况下合成一些物品。在参考了 异形龙虾 的专栏 CV3031327 和他的一些代码了以后,表示完全搞不懂。后来转而在铁砧的代码里寻得了解决方案。

这篇文章虽然较长,实际信息量应该对了解 Java 的人来说并不大;对深谙 Forge 的各种操作的 Modder 来说更是小菜一碟。部分代码自己还未完全吃透,发在这里权当抛砖引玉了。

因为工作台合成这个东西太庞杂了,不知道怎么看起,决定对需求重新抽象:
方块本身不保存界面中的东西,退出界面后剩余物品自动返回背包
输入物品,输出栏位显示另一个物品,但只有把输出的物品拿走的时候,才会消耗输入的物品
有一定的配方 根据这些抽象结果,铁砧比工作台看起来更加贴近要求。那么接下来对铁砧的代码进行研究,抽取有用的部分用在自己的模组里。
铁砧主要由三个类组成:
BlockAnvil
负责方块本体各种属性(如透明底,碰撞箱)的配置,ContainerRepair
是负责对界面中物品的储存和逻辑判断,GuiRepair
负责对客户端的 GUI 进行渲染。
首先从方块类看起。BlockAnvil
重写了onBlockActivated
方法,对玩家右键的操作进行响应:
/**
* Called when the block is right clicked by a player.
*/
public boolean onBlockActivated(World worldIn, BlockPos pos, IBlockState state, EntityPlayer playerIn, EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ) {
if (!worldIn.isRemote) {
playerIn.displayGui(new BlockAnvil.Anvil(worldIn, pos));
}
return true;
}
这里使用了一个额外的类Anvil
来显示 GUI ,但是在实际的 mod 编写中,应该使用 forge 给我们提供的方法openGui()
而不是displayGui()
,另外,GUI 应该被注册好了,具体注册方法别处都有,恕不赘述。
方块部分就是这样,很简单,不涉及任何 TileEntity(方块实体)。接下来看逻辑的重中之重ContainerRepair
。在类的初始化方法中,可以看到初始化了两个栏位:一个二格的输入和一个一格的输出,对应铁砧的输入输出:
this.outputSlot = new InventoryCraftResult();
this.inputSlots = new InventoryBasic("Repair", true, 2) {
public void markDirty() {
super.markDirty();
ContainerRepair.this.onCraftMatrixChanged(this);
}
};
这里使用匿名类的方法,重写了输入栏位的markDirty()
,让它调用onCraftMatrixChanged()
方法。markDirty()
方法会在栏位发生任何变化的时候调用,提醒游戏保存一下。
那么接着看onCraftMatrixChanged()
这个方法,它也被重写了。如果输入栏位被改变,就调用一个更新输出栏位的方法:
public void onCraftMatrixChanged(IInventory inventoryIn) {
super.onCraftMatrixChanged(inventoryIn);
if (inventoryIn == this.inputSlots) {
this.updateRepairOutput();
}
}
简而言之,通过重写方法,所有输入栏位的变化都会更新输出栏位

这样一来具体的实现就变得很简单了,另外写一个类,用HashMap
统一管理配方,要获得输出就直接从里面查询。记得用ItemStack.areItemsEqual()
而不是双等于进行比较。获取到的产物要用copy()
来防止引用问题,修改到不该修改的东西。
将栏位加入容器的过程,Minecraft原本的是:
this.addSlotToContainer(new Slot(this.inputSlots, 0, 27, 47));
this.addSlotToContainer(new Slot(this.inputSlots, 1, 76, 47));
this.addSlotToContainer(new Slot(this.outputSlot, 2, 134, 47){/**一些重写代码**/});
但是实际情况下,设置成0,1,2会有下标越界错误,不太清楚,但是都改成0就好了:
this.addSlotToContainer(new Slot(this.powderSlot, 0, 80, 53) {/**一些重写代码**/});
this.addSlotToContainer(new Slot(this.inputSlot, 0, 48, 26));
this.addSlotToContainer(new Slot(this.outputSlot, 0, 111, 26){/**一些重写代码**/});
接下来别忘了把玩家背包的栏位也添加进去,这部分代码直接抄就好:
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 9; ++j) {
this.addSlotToContainer(new Slot(playerInventory, j + i * 9 + 9, 8 + j * 18, 84 + i * 18));
}
}
for (int k = 0; k < 9; ++k) {
this.addSlotToContainer(new Slot(playerInventory, k, 8 + k * 18, 142));
}
然后在一个统一的方法进行输出栏位的更新,然后搞一个 GUI 的渲染。

接下来处理一些细节,首先是输出栏位不能放入东西,只能在创造模式或者等级够的时候拿出输出栏位的物品,拿出输出栏位物品会减少输入栏位的物品。这些逻辑在添加栏位的时候重写已有的方法就好,还是拿铁砧举例:
this.addSlotToContainer(new Slot(this.outputSlot, 2, 134, 47) {
public boolean isItemValid(ItemStack stack) {
return false;
}
public boolean canTakeStack(EntityPlayer playerIn) {
return (playerIn.capabilities.isCreativeMode || playerIn.experienceLevel >= ContainerRepair.this.maximumCost) && ContainerRepair.this.maximumCost > 0 && this.getHasStack();
}
public ItemStack onTake(EntityPlayer thePlayer, ItemStack stack){
if (!thePlayer.capabilities.isCreativeMode) {
thePlayer.addExperienceLevel(-ContainerRepair.this.maximumCost);
}
//减少输入物品可以用setInventorySlotContents()或者decrStackSize()做到
//一堆复杂的东西,具体就是用玩家的rng计算铁砧会不会坏,还有播放音效之类的。各位rng大法师可以试试操控
return stack;
}
});
如果要控制输入栏位只能塞入特定的物品,也是重写isItemValid
方法。
在退出界面时自动把栏位的物品弹到玩家身上或地上:
public void onContainerClosed(EntityPlayer playerIn) {
super.onContainerClosed(playerIn);
if (!this.world.isRemote) {
this.clearContainer(playerIn, this.world, this.inputSlots);
}
}
玩家用SHIFT点击的情况需要特殊处理,不过这里全部照抄铁砧的transferStackInSlot()
就好
成品展示
感谢 XeKr 做的材质和模型,XKnb!。

我给注魔台上面的蛋糕模型做了一个旋转+起伏的效果,这里是需要 TileEntity 和 TileEntitySpecialRenderer 的


结
从这篇文章可以看出,大部分玩家想要做出的功能,都可以在原版中找到对应。只要了解 Java,能阅读代码,就能做出不少东西。如果要做背包,那么可以参考末影箱(数据较多,要单独存放)或者潜影盒(数据不多,可以存放在背包的NBT里)。如果要做 GUI 里面的按钮,可以参考附魔台或者信标。要对周围方块实时监测,信标或者附魔台也是不错的例子……