本篇教程的视频

本篇教程的源代码

GitHub地址:

本篇教程目标

  • 把 Blockbench 导出的模型接进自己的模组
  • 分清 方块状态、模型、贴图 这三层各管什么
  • 学会用数据生成输出一个「指向已有模型」的方块状态
  • 认识 26.1 里碰撞箱那几个改了名的 API

介绍

到现在为止,我们用的都是原版的模型

cube_all、stairs、slab 这些是原版写好的模板,套一张贴图就能用

但真做模组的时候,家具、机器、装饰方块这些东西,原版模型根本表达不出来

这时候就得上 Blockbench 了

Blockbench 是一个轻量的开源三维建模软件,专门给 Minecraft 用的

上手非常简单,拖体块、缩放、移动、旋转,拼一拼就是一个模型

它要精通也有得学,比如 UV 的调整、贴图的绘制,但我们现阶段够用就行

下载地址在这里:下载

建模的过程我这里就不讲了,哔哩哔哩上一搜一大把

这篇教程的重点在后半段:模型建好之后,怎么让它在我们自己的模组里正常显示

这期我们做两个东西,一个物品 棒球棍,一个方块 橙色床头柜

注意事项

一定要选 Java 版

新建模型的时候,模板必须选 Java版方块/物品

不要选基岩版,基岩版导不出 json 格式的模型文件

obj 模型游戏原生渲染器也不认,Fabric 也没有官方的 obj 加载器,这个以后再说

另外,我们还需要注意,新建项目时会有一个弹窗让你选择版本,我们这里是26.1,所以我们格式版本选择1.21.11-26.2

值得庆幸的事情是,从这个格式版本开始,我们也可以制作一些精度较高的模型了

  • 游戏取消了单轴旋转的限制,现在旋转了某个轴之后,其他两个轴也可以旋转了(之前会归零)
  • 22.5°的旋转倍数也移除了,现在可以把体块旋转到任意角度

还有一个格式版本是26.3+,本质上没什么区别,但多了一个光源方向,是调整它在物品栏里面的模型打光方向的

显示模式

导出之前,记得在右上角的 显示 面板里调一下

GUI、第一人称、第三人称、掉落物,四个都要看

不调的话,物品拿在手上会插进胳膊里,或者挡住半个屏幕

Blockbench 自带预设,物品、方块、工具、武器 都有,挑一个最接近的再微调

动手写

这篇教程本身是我们前面物品和方块的拓展,所以这里的源代码部分我们就略过了

自定义物品

注册物品

1
public static final Item BASEBALL_BAT = registerItem("baseball_bat", Item::new, new Item.Properties());

就一个普通物品,没有任何特殊属性

想让它能打人、有耐久,那就得上自定义物品类或者 sword 那一套,我们前面都讲过

别忘了加进物品栏

1
output.accept(ModItems.BASEBALL_BAT);

模型和贴图

把 Blockbench 导出的东西放进去

1
2
assets/tutorial/models/item/baseball_bat.json
assets/tutorial/textures/item/baseball_bat.png

模型 json 里那个 "textures" 字段的路径,要和贴图实际位置对上

自定义方块

方块类

这期的方块不是完整方块,所以先写个方块类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class OrangeNightstand extends Block {
public static final VoxelShape SHAPE = Shapes.or(
Block.box(0, 15, 0, 16, 16, 16),
Block.box(1, 0, 1, 15, 15, 16)
);

public OrangeNightstand(Properties properties) {
super(properties);
}

@Override
protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return SHAPE;
}
}

为什么要写方块类?因为模型不是完整的方块

不写碰撞箱的话,玩家会站在空气上,挖的时候也按一个整方块算,手感非常怪

这里的 SHAPE 对应模型的两个体块

上面那块是 0,15,0 到 16,16,16 的桌面

下面是 1,0,1 到 15,15,16 的柜体

Shapes.or 就是把两个形状拼一起

碰撞箱其实可以让 Blockbench 帮你算

装一个叫 Mod Utils 的插件,在插件面板里就能下到,装好之后导出菜单里会多一个 Export Voxelshape

不过它有点笨,所有体块必须放在一个叫 VoxelShapes 的组里,名字一个字母都不能错,大小写也得对

注册方块

1
2
public static final Block ORANGE_NIGHTSTAND = register("orange_nightstand", OrangeNightstand::new,
BlockBehaviour.Properties.of().strength(2.0f, 6.0f).noOcclusion(), true);

最后一个true是顺便把方块物品也注册了

这里加一个noOcclusion方法,让相邻方块的面不会被剔除,否则会出现空洞

模型和贴图

1
2
assets/tutorial/models/block/orange_nightstand.json
assets/tutorial/textures/block/orange_nightstand.png

数据生成

方块状态和方块物品

1
2
3
4
5
6
7
generateCustomBlock(blockModelGenerators, ModBlocks.ORANGE_NIGHTSTAND);

private void generateCustomBlock(BlockModelGenerators blockModelGenerators, Block block) {
Identifier model = ModelLocationUtils.getModelLocation(block);
blockModelGenerators.createNonTemplateModelBlock(block);
blockModelGenerators.registerSimpleItemModel(block, model);
}

两行代码干了三件事

createNonTemplateModelBlock 写方块状态

registerSimpleItemModel 写方块物品的定义,让它指回方块模型,因为上面的方法不会生成物品的模型,我们的自己写一句

物品模型

1
itemModelGenerators.declareCustomModelItem(ModItems.BASEBALL_BAT);

语言文件

1
2
translationBuilder.add(ModItems.BASEBALL_BAT, "Baseball Bat");
translationBuilder.add(ModBlocks.ORANGE_NIGHTSTAND, "Orange Nightstand");

中文那份

1
2
translationBuilder.add(ModItems.BASEBALL_BAT, "棒球棍");
translationBuilder.add(ModBlocks.ORANGE_NIGHTSTAND, "橙色床头柜");

方块物品的翻译键会自动沿用方块那条,这是因为我们注册方块物品的时候加了 useBlockDescriptionPrefix,方块那篇讲过

战利品表

新方块别忘了战利品表,不然挖掉什么也不掉

1
dropSelf(ModBlocks.ORANGE_NIGHTSTAND);

这一行不加,方块能放能挖,就是不掉东西,非常容易忘

测试

跑完数据生成,启动游戏进行测试

常见问题(大肥鱼总结版)

  • 模型是紫黑方块:贴图命名空间不对(tutorial_mod 没改成 tutorial),或者贴图路径写错
  • 方块能放但看不到:blockstates/ 文件名不对,或者 createNonTemplateModelBlock 没调
  • 贴图错位、UV 全乱:模型 json 里的 texture_size 和贴图分辨率对不上,或者改过分辨率没重新导出
  • 贴图糊:分辨率太低,64x64 的基准配 128x128 的图会清楚不少,游戏是认这个的
  • 相邻方块的面被吞了:noOcclusion 没加
  • 能穿过方块、站在空气上:getShape 没写,或者参数写错了
  • 挖掉不掉落:战利品表里漏了 dropSelf
  • 物品在物品栏里是一坨放大模型:display 里的 gui 没调
  • 第一人称挡住半个屏幕:display.firstperson_righthand 的 translation 和 scale 没调
  • 数据生成没出 blockstates:generateBlockStateModels 里漏了调用
  • 名字显示成 item.tutorial.baseball_bat:语言文件漏了翻译