本篇教程的视频
本篇教程的源代码
GitHub地址:
本篇教程目标
- 用
RotatedPillarBlock 做原木,搞懂 axis 这个属性
- 分清原木和木头的区别
- 学会 Fabric 的两套注册:削皮、可燃
- 学会原木的模型生成,包括那份容易漏的「横放」模型
介绍
后面的教程要做树,还要做树的世界生成
但树得先有木头,所以这一期先把木头这套东西铺好
我们一共做五种
原木、木头、去皮原木、去皮木头、木板
树叶这次先不做,那东西要配树苗一起讲,留到树那一期
原木和木头有什么区别
原木(log)是竖着长的,它的顶面和底面是年轮,侧面是树皮
木头(wood)是把原木合成当建材用,它六个面都是树皮
所以原木需要两张贴图(侧面一张、顶面一张),木头只需要一张
查看源代码
RotatedPillarBlock
原木、木头、柱子这类方块,用的都是同一个类
1 2
| public class RotatedPillarBlock extends Block { public static final EnumProperty<Axis> AXIS = BlockStateProperties.AXIS;
|
AXIS 就是「轴向」,只有三个值:X、Y、Z
放下去默认是 Y,也就是竖着立起来;你横着放,游戏会自动把它设成 X 或者 Z
这个类还把 rotate 和 mirror 写好了,结构方块转房子的时候轴向会跟着转,我们不用管
原木的方块状态
原版的橡木原木长这样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| { "variants": { "axis=x": { "model": "minecraft:block/oak_log_horizontal", "x": 90, "y": 90 }, "axis=y": { "model": "minecraft:block/oak_log" }, "axis=z": { "model": "minecraft:block/oak_log_horizontal", "x": 90 } } }
|
注意 axis=x 和 axis=z 用的是另一份模型 oak_log_horizontal,不是把竖着的模型转 90 度
因为原木侧面的树皮纹理是有方向的,直接转 90 度,纹理就躺倒了
所以原版给它专门做了一份「横放」的模型,纹理按横放重新排一遍
而木头(wood)就没这个问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| { "variants": { "axis=x": { "model": "minecraft:block/oak_wood", "x": 90, "y": 90 }, "axis=y": { "model": "minecraft:block/oak_wood" }, "axis=z": { "model": "minecraft:block/oak_wood", "x": 90 } } }
|
削皮和可燃,原版是写死的
用斧头右键原木能削皮,这个映射表在 AxeItem 里,是个静态 Map,而且类加载的时候就建好了
方块会不会烧起来,原版是把数据塞在 FireBlock 里的一堆 setFlammable 调用,那个方法是 private 的
也就是说,这两样我们自己的方块默认都没有,得自己接
NeoForge 的做法是在方块类里重写 isFlammable、getToolModifiedState 这一堆方法
Fabric 的做法不一样,它给了两个注册表,直接在初始化的时候「登记」一下就行
FlammableBlockRegistry 管可燃,StrippableBlockRegistry 管削皮
动手写
注册五个方块
1 2 3 4 5 6 7 8 9 10
| public static final Block ICE_ETHER_LOG = register("ice_ether_log", RotatedPillarBlock::new, BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_LOG), true); public static final Block ICE_ETHER_WOOD = register("ice_ether_wood", RotatedPillarBlock::new, BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_WOOD), true); public static final Block STRIPPED_ICE_ETHER_LOG = register("stripped_ice_ether_log", RotatedPillarBlock::new, BlockBehaviour.Properties.ofFullCopy(Blocks.STRIPPED_OAK_LOG), true); public static final Block STRIPPED_ICE_ETHER_WOOD = register("stripped_ice_ether_wood", RotatedPillarBlock::new, BlockBehaviour.Properties.ofFullCopy(Blocks.STRIPPED_OAK_WOOD), true); public static final Block ICE_ETHER_PLANKS = register("ice_ether_planks", Block::new, BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_PLANKS), true);
|
四个木头全用 RotatedPillarBlock,属性直接抄对应的橡木
木板是普通方块,抄橡木木板
都记得加物品栏
1 2 3 4 5
| output.accept(ModItems.ICE_ETHER_LOG); output.accept(ModItems.ICE_ETHER_WOOD); output.accept(ModItems.STRIPPED_ICE_ETHER_LOG); output.accept(ModItems.STRIPPED_ICE_ETHER_WOOD); output.accept(ModItems.ICE_ETHER_PLANKS);
|
削皮
我们要到模组主类里面去添加以下内容
1 2
| StrippableBlockRegistry.registerCopyState(ModBlocks.ICE_ETHER_LOG, ModBlocks.STRIPPED_ICE_ETHER_LOG); StrippableBlockRegistry.registerCopyState(ModBlocks.ICE_ETHER_WOOD, ModBlocks.STRIPPED_ICE_ETHER_WOOD);
|
第一个参数是原来的方块,第二个是削完变成的方块
可燃
同样在主类里添加
1 2 3 4 5 6
| FlammableBlockRegistry flammable = FlammableBlockRegistry.getDefaultInstance(); flammable.add(ModBlocks.ICE_ETHER_LOG, 5, 5); flammable.add(ModBlocks.ICE_ETHER_WOOD, 5, 5); flammable.add(ModBlocks.STRIPPED_ICE_ETHER_LOG, 5, 5); flammable.add(ModBlocks.STRIPPED_ICE_ETHER_WOOD, 5, 5); flammable.add(ModBlocks.ICE_ETHER_PLANKS, 5, 20);
|
后两个数字是「点燃几率」和「烧毁几率」
原版橡木原木是 5, 5,橡木木板是 5, 20
这些数值都可以参考FireBlock里面原版的数值
数据生成
原木和木头:一条链写完
BlockModelGenerators里有个专门给木头准备的woodProvider,原版那十几种木头全靠它生成
我们直接用它
1 2 3 4 5 6
| blockModelGenerators.woodProvider(ModBlocks.ICE_ETHER_LOG) .logWithHorizontal(ModBlocks.ICE_ETHER_LOG) .wood(ModBlocks.ICE_ETHER_WOOD); blockModelGenerators.woodProvider(ModBlocks.STRIPPED_ICE_ETHER_LOG) .logWithHorizontal(ModBlocks.STRIPPED_ICE_ETHER_LOG) .wood(ModBlocks.STRIPPED_ICE_ETHER_WOOD);
|
一行链式,原木和木头都有了,方块状态、模型、物品模型全齐
返回的对象能接着链式调,三个方法对应三种原木
我们用logWithHorizontal,因为树皮纹理有方向,硬转 90 度会躺倒
.wood(block) 生成的是木头,它把映射里的END槽换成SIDE的内容,于是六面都是树皮
木头六面一样,所以它内部用的是直接旋转那套,不整 _horizontal
木板
1
| blockModelGenerators.createTrivialCube(ModBlocks.ICE_ETHER_PLANKS);
|
六面同一张图的标准方块,一行搞定
标签
方块标签,三个
1 2 3 4 5 6 7 8 9 10 11 12
| valueLookupBuilder(BlockTags.LOGS) .add(ModBlocks.ICE_ETHER_LOG) .add(ModBlocks.ICE_ETHER_WOOD) .add(ModBlocks.STRIPPED_ICE_ETHER_LOG) .add(ModBlocks.STRIPPED_ICE_ETHER_WOOD); valueLookupBuilder(BlockTags.LOGS_THAT_BURN) .add(ModBlocks.ICE_ETHER_LOG) .add(ModBlocks.ICE_ETHER_WOOD) .add(ModBlocks.STRIPPED_ICE_ETHER_LOG) .add(ModBlocks.STRIPPED_ICE_ETHER_WOOD); valueLookupBuilder(BlockTags.PLANKS) .add(ModBlocks.ICE_ETHER_PLANKS);
|
物品标签,同样三个
1 2 3 4 5 6 7 8 9 10 11 12
| valueLookupBuilder(ItemTags.LOGS) .add(ModBlocks.ICE_ETHER_LOG.asItem()) .add(ModBlocks.ICE_ETHER_WOOD.asItem()) .add(ModBlocks.STRIPPED_ICE_ETHER_LOG.asItem()) .add(ModBlocks.STRIPPED_ICE_ETHER_WOOD.asItem()); valueLookupBuilder(ItemTags.LOGS_THAT_BURN.asItem()) .add(ModBlocks.ICE_ETHER_LOG.asItem()) .add(ModBlocks.ICE_ETHER_WOOD.asItem()) .add(ModBlocks.STRIPPED_ICE_ETHER_LOG.asItem()) .add(ModBlocks.STRIPPED_ICE_ETHER_WOOD.asItem()); valueLookupBuilder(ItemTags.PLANKS) .add(ModBlocks.ICE_ETHER_PLANKS.asItem());
|
注意方块和物品的标签是两套,名字一样但路径不同,两个都得写
LOGS_THAT_BURN 这个名字看着不起眼,其实是个「分类」标签
#minecraft:logs 是总标签,底下挂着 #logs_that_burn 和绯红菌柄那两套
会烧的木头进 logs_that_burn,绯红菌柄不进,这样配方里要「木头」的时候,用总标签就能把所有木头一网打尽,需要「会烧的木头」时单独查子标签
燃料判定则是看 #minecraft:logs 这个物品标签
所以我们把木头加进 ItemTags.LOGS,它就能当燃料烧;木板进 ItemTags.PLANKS,也是一样
这里再强调一次:方块标签和物品标签是两套,BlockTags.LOGS 和 ItemTags.LOGS 名字一样但完全是两个文件
战利品
1 2 3 4 5
| dropSelf(ModBlocks.ICE_ETHER_LOG); dropSelf(ModBlocks.ICE_ETHER_WOOD); dropSelf(ModBlocks.STRIPPED_ICE_ETHER_LOG); dropSelf(ModBlocks.STRIPPED_ICE_ETHER_WOOD); dropSelf(ModBlocks.ICE_ETHER_PLANKS);
|
语言文件
1 2 3 4 5 6 7 8 9 10 11
| translationBuilder.add(ModBlocks.ICE_ETHER_LOG, "Ice Ether Log"); translationBuilder.add(ModBlocks.ICE_ETHER_WOOD, "Ice Ether Wood"); translationBuilder.add(ModBlocks.STRIPPED_ICE_ETHER_LOG, "Stripped Ice Ether Log"); translationBuilder.add(ModBlocks.STRIPPED_ICE_ETHER_WOOD, "Stripped Ice Ether Wood"); translationBuilder.add(ModBlocks.ICE_ETHER_PLANKS, "Ice Ether Planks");
translationBuilder.add(ModBlocks.ICE_ETHER_LOG, "冰以太原木"); translationBuilder.add(ModBlocks.ICE_ETHER_WOOD, "冰以太木头"); translationBuilder.add(ModBlocks.STRIPPED_ICE_ETHER_LOG, "去皮冰以太原木"); translationBuilder.add(ModBlocks.STRIPPED_ICE_ETHER_WOOD, "去皮冰以太木头"); translationBuilder.add(ModBlocks.ICE_ETHER_PLANKS, "冰以太木板");
|
测试
跑好数据生成之后,我们就可以启动游戏进行测试了
常见问题(大肥鱼总结版)
- 原木侧面是紫黑方块:模型里侧面贴图变成了
xxx_side,用的是 TextureMapping.column,原木得用 logColumn(woodProvider 已经帮你选好了)
- 横放原木的树皮纹理躺倒了:用了
.log(),换成 .logWithHorizontal(),它会多生成一份 _horizontal 模型
- 木头的顶面和侧面不一样:没走
.wood(),自己拼映射时忘了把 END 也填成侧面那张贴图
- 削皮之后轴向乱了:用了
StrippableBlockRegistry.register,改成 registerCopyState
- 斧头削不了皮:
StrippableBlockRegistry 那两行没写,或者写在方块注册之前了
- 木头烧不起来:
FlammableBlockRegistry 没登记,或者数字写反了(第一个是点燃几率,第二个是烧毁几率)
- 原木不能当燃料:物品标签
ItemTags.LOGS 漏了(不是 LOGS_THAT_BURN,燃料查的是 LOGS)
- 方块标签写了但配方不认:只写了方块标签,物品标签也得写一份
- 物品栏里没有木头:注册方块那行最后一个参数给的
false
- 数据生成报
Duplicate key:同一个方块注册了两次模型,检查是不是既有 registerSimpleItemModel 又有自动生成