方块朝向 26.1 Fabric
本篇教程的视频
本篇教程的源代码
GitHub地址:
本篇教程目标
- 搞懂
facing这个方块状态到底是什么 - 学会给方块加
HORIZONTAL_FACING属性 - 学会
getStateForPlacement、rotate、mirror、createBlockStateDefinition这四个方法 - 学会用
Blockbench导出四个方向的碰撞箱 - 学会用数据生成一次输出四个朝向的方块状态
介绍
上一篇我们添加了一个Blockbench的模型,但它是「死」的:放下去永远朝同一个方向
这一期我们做一个真正有朝向的方块,一个挂墙上的橙色时钟
从这期开始进入方块状态小系列,这篇讲朝向,下一篇讲可连接方块
什么叫朝向
游戏里除了六个面材质一样的方块,大部分都是有朝向的
比如熔炉,它开口的那一面,我们姑且叫特征面
你把熔炉放下去的时候,不管你面朝哪边(上下除外),特征面永远朝着你
楼梯、讲台、床、门,都是这个逻辑
方块朝向在底层就是一个叫 facing 的方块状态
方块状态是什么
方块状态是方块独有的一层属性,用来描述同一个方块的不同形态
我们在代码里给方块声明了哪些属性,游戏就会把所有组合穷举出来
而方块状态文件就是一张「状态 → 模型」的对照表,游戏照着它来决定这个方块该用什么模型、转多少度
两种写法
穷举型,variants,一种状态一条。原版的熔炉就是这样
1 | { |
lit=true省略了,本质上是一样的
组合型,multipart,按条件往方块上叠模型,栅栏就是这种
穷举型的缺点很明显,属性一多就爆炸,原版按钮 face、facing、powered 三个属性一组合就是 24 条
我们这期只有 facing 一个,四条就够,用 variants 最省事
查看源代码
FACING 属性
先看熔炉,AbstractFurnaceBlock 里
1 | public static final EnumProperty<Direction> FACING = HorizontalDirectionalBlock.FACING; |
它引用的是 HorizontalDirectionalBlock.FACING,而那边又是
1 | public static final EnumProperty<Direction> FACING = BlockStateProperties.HORIZONTAL_FACING; |
绕了两层,本质还是 BlockStateProperties 里的那个
HORIZONTAL_FACING 的定义
1 | public static final EnumProperty<Direction> HORIZONTAL_FACING = EnumProperty.create("facing", Direction.class, Direction.Plane.HORIZONTAL); |
三个参数看一遍
facing 是方块状态文件里那个键名,必须和它一致,不然游戏找不到这个属性
Direction.class 说明这个属性存的是方向枚举
Direction.Plane.HORIZONTAL 限定只取水平四个方向:NORTH、EAST、SOUTH、WEST
同一层里还有个 FACING,那个多带了 UP 和 DOWN,原版只有木桶在用
构造函数里初始化
1 | protected AbstractFurnaceBlock(final BlockBehaviour.Properties properties) { |
registerDefaultState 把默认朝向定成北,LIT 那个我们以后讲发光再说
四个方法
放置时决定朝向
1 |
|
getHorizontalDirection() 是玩家面朝的方向,getOpposite() 取反,所以正面才朝着玩家
旋转和镜像
1 |
|
这两个不是给玩家用的,是给结构方块、/rotate 命令用的
不写的话,你用结构方块把房子转 90 度,别的方块都跟着转,我们的方块还杵在原地
最后把属性注册进状态表
1 |
|
这个漏了直接崩,报的是属性没注册
动手写
方块类
新建 OrangeClock,继承 Block
1 | public class OrangeClock extends Block { |
原版的 HorizontalDirectionalBlock 其实也可以用,但它是抽象类,子类还得写一个 codec,为了省事我们直接继承 Block,自己把属性加上
碰撞箱
这一步比上一篇麻烦,因为朝四个方向,碰撞箱也得有四份
装插件
Blockbench 本身导不出 VoxelShape,得装个插件
在 文件 -> 插件 里搜 Mod Utils,装上

装好之后,文件 -> 导出 里就会多出一项 Export VoxelShape(1.14+ Modded Minecraft)
整理体块
这个插件有个前提:所有体块必须放在一个叫 VoxelShapes 的组里,名字一个字母都不能错,大小写也要对

整理好之后点导出,会弹一个窗口让你选映射

这里要选你工程用的那套:我们这个 26.1 模板是 Mojang 官方映射,就选 Mojang;写 Yarn 的工程选 Yarn(上图是 1.21.1 那期的截图,所以显示的是 Yarn)
斜着的体块会跑掉
导出北面这份之后,还得有东西南三份
正常想法是把整个模型转 90 度再导一次,对吧?
在26.1之前的版本我会说不行
因为在这之前,Java版的方块模型只支持单轴旋转,你把一个带有斜着体块的模型整体旋转,那些斜着的体块就会跑偏,导出来的碰撞箱跟模型对不上

当然,现在的版本你大可以直接旋转然后导出碰撞箱,但是我还是要讲讲曾经的方法
也就是别管原来的体块了,直接拉一个能盖住整个时钟的大体块,把其他体块全删掉(或者挪走),拿它去导出四个方向的碰撞箱

旋转的时候记得把枢轴点调到方块正中心,也就是把 X 和 Z 都改成 8,Y 不用管
这种方法简单粗暴,也算是一种优化的手段,如果你制作了一些复杂的模型,实际上也只需要拉一个能覆盖整个模型的体块当碰撞箱就行了,因为本来模型里面犄角旮旯的地方玩家也进不去
四个碰撞箱
1 | public static final VoxelShape SHAPE_N = Block.box(3, 3, 15, 13, 13, 16); |
按朝向返回碰撞箱
1 |
|
Direction 一共六个值,UP 和 DOWN 在这个方块上不可能出现,所以用 default 兜住北
注册方块
1 | public static final Block ORANGE_CLOCK = register("orange_clock", OrangeClock::new, |
加进物品栏
1 | output.accept(ModBlocks.ORANGE_CLOCK); |
数据生成
语言和战利品
1 | translationBuilder.add(ModBlocks.ORANGE_CLOCK, "Orange Clock"); |
上面两条分别是英文和中文那个文件里的,别写一个文件里去
新方块记得补战利品表,不然挖了不掉
1 | dropSelf(ModBlocks.ORANGE_CLOCK); |
方块状态
上一篇那个 createNonTemplateModelBlock 现在不能用了,因为我们多了一个facing的属性,所以这里我们自己来写一个方法
1 | private void generateHorizontalFacingBlock(BlockModelGenerators blockModelGenerators, Block block) { |
这里我们直接调用 createNonTemplateHorizontalBlock,它会帮我们生成四个朝向的方块状态文件,另外同样需要调用registerSimpleItemModel来生成对应的物品模型
之后就是把我们的方块加进去
1 | generateHorizontalFacingBlock(blockModelGenerators, ModBlocks.ORANGE_CLOCK); |
测试
跑完数据生成,进游戏进行测试
常见问题(大肥鱼总结版)
- 启动就崩,报属性没注册:
createBlockStateDefinition里漏了builder.add(FACING) - 放下来永远是同一个方向:没写
getStateForPlacement - 模型不显示:方块状态还是那条空变体,数据生成没换成带
facing的版本 - 四个方向都不转:
.with(...)那段没写,或者select少了一个方向 - 钟面朝反了:模型建的时候钟面朝南了,把
NOP和Y_ROT_180换个位置 - 转 90 度后碰撞箱错位:导出碰撞箱时没把枢轴点调到
X、Z都是8 - 碰撞箱跟模型差一截:模型里有斜着的体块,整体旋转导出的结果不准,老老实实用一个覆盖体块代替
- 结构方块转了朝向不变:
rotate/mirror没写 - 物品栏里的模型是紫黑块:
registerSimpleItemModel漏了







