床 26.1 Fabric
本篇教程的视频
本篇教程的源代码
GitHub地址:
本篇教程目标
- 搞清「双格方块」是怎么回事
- 学会继承原版
BedBlock做自己的床 - 学会用
getRenderShape让一份两格长的模型只画一次 - 学会给双格方块写战利品表,只掉一个
介绍
前面几期我们做的方块都只占一格
沙发虽然能连成一排,但每一段自己占一格
这一期做床,它一个方块就占两格
这类方块在游戏里不少:门、床、高花、双箱子,都是「放下去变成两个方块」的东西
原版的床是两个方块拼起来的,脚一半、头一半,各自有一份模型
我们的床是 Blockbench 做的一整张,所以只能换一个思路:让其中一半负责画模型,另一半什么都不画
为什么必须继承 BedBlock
床不只是个模型,它还牵扯到睡觉、重生点、下界爆炸这些逻辑
而游戏里判断「这是不是床」的方式很粗暴:blockState.getBlock() instanceof BedBlock(在LivingEntity的startSleeping方法)
你要是不继承它,玩家右键就睡不上去,除非写 Mixin 去改原版的判断
所以这期的做法是:继承 BedBlock,把模型相关的部分改成自己的
查看源代码
两个属性
1 | public static final EnumProperty<BedPart> PART = BlockStateProperties.BED_PART; |
PART 是「脚」还是「头」,只有 foot 和 head 两个值
OCCUPIED 是这张床有没有被占用,主要是村民晚上会自己爬上去睡
放下去为什么变成两格
1 |
|
这一段是BedBlock已有的逻辑
逻辑是:你放下的那一格是脚,然后在 facing 方向上再放一格头
所以facing指的是「从脚指向头」的方向
顺带一提,getStateForPlacement 里也判断了「头那一格有没有位置」,没位置就返回 null,床就放不下去
两半怎么绑在一起
1 |
|
这个是更新方块的逻辑,它保证了床如果有一部分被移除,另一部分也会跟着消失
右键床
1 |
|
这个方法原版写得挺长,我们不用改,但值得看一眼它做了什么
先是判断当前点的是不是头,不是的话换成头那一格
然后判断这个维度能不能睡床(下界和末地会直接炸)
再判断床有没有被占,被村民占了就把村民踹下去,被别的玩家占了就提示「这张床已被占用」
最后才是 player.startSleepInBed(pos),睡觉、设重生点都在里面
在床上蹦
1 |
|
摔在床上一半伤害,而且会弹起来
方块实体
26.1 的 BedBlock 还实现了一个 EntityBlock,newBlockEntity 里返回的是 BedBlockEntity,里面只存了一个 DyeColor
这个东西不是摆设,它暴露了 26.1 原版床的渲染方式:方块状态文件是空的
1 | { |
而 minecraft:block/bed 这个模型本身也是空的,只剩一个 particle
真正那张床是 BedRenderer(方块实体渲染器)画出来的,它从 BedBlockEntity 里读颜色,决定画哪一张床
所以原版床压根不需要 part、facing 的方块状态,朝向和形状都是渲染器自己算的
我们的思路反过来:不搞渲染器,就用方块模型
那就得把 newBlockEntity 重写成 null,让原版那套渲染器没有东西可读,再用 getRenderShape 把模型画出来
NeoForge 1.21.1 那期的教程说要「注册方块实体」,其实就是被这套东西绊了一下;26.1 里返回 null 更省事
动手写
ModBedBlock
1 | public class ModBedBlock extends BedBlock { |
构造函数要传一个 DyeColor,这是 BedBlock 规定的
随便给一个就行,因为我们不用原版那套按颜色换模型的逻辑,给 ORANGE 意思一下
碰撞箱给 8 像素高,和原版床一样高
只画一半
1 |
|
getRenderShape 决定「这个方块要不要画模型」
MODEL 是按方块模型正常渲染,INVISIBLE 是不画
所以头那一格画整张床,脚那一格什么都不画,看起来就是一张完整的床
注意这一步是必须的:原版床的几何是 BedRenderer 画的,我们不给它方块实体,就没人画了,只能靠方块模型顶上
这里有个前提:模型必须是「床头在原点、往 +Z 方向长两格」
我们这个模型正好是这样:床头板在 z 从 0 到 2,枕头在 2 到 7,床尾在 30 到 32
因为 facing=north 的时候,脚那一格正好在头的南边(+Z),模型不用转就能盖住两格
你自己建模型的时候如果反过来了,把床头放在 z=32 那一端,那就得把下面数据生成里的 NOP 和 Y_ROT_180 换个位置
不要方块实体
1 |
|
直接返回 null,游戏就不会给它创建方块实体
注册
1 | public static final Block BED = register("bed", p -> new ModBedBlock(DyeColor.ORANGE, p), |
noOcclusion 别忘,床不是满方块
顺手加物品栏和语言
1 | output.accept(ModBlocks.BED); |
数据生成
战利品表
床是两格,如果两格都掉,拆一张床能捡到两个,那就亏了
所以要加条件,只让头那一格掉
1 | add(ModBlocks.BED, createSinglePropConditionTable(ModBlocks.BED, BedBlock.PART, BedPart.HEAD)); |
createSinglePropConditionTable 是原版数据生成里的现成方法,意思是「只有某个属性等于某个值时,才掉这个东西」
它其实就是给战利品表塞一个 block_state_property 条件,和门的写法一样
方块状态
床的方块状态有两个属性:part 和 facing,一共 8 种
虽然我们只有一份模型,但两个 part 在方块状态文件里都得列出,不然缺状态
现成的 createBed 用不了
BlockModelGenerators 里其实有个 createBed,名字看着就是给床准备的
1 | public final void createBed(final Block bed, final Block itemParticle, final DyeColor dyeColor) { |
原版那 16 张床就是用它生成的,但我们用不了,三个原因
decorateBlockModelLocation("bed")返回的就是minecraft:block/bed,它是个加了@Deprecated的老方法,固定用minecraft命名空间,没法传自己的模型createSimpleBlock(bed, blockModel)生成的就是{"variants": {"": {...}}},既没有part也没有facing的分发- 物品模型走的是
BedSpecialRenderer,它按DyeColor画原版床,手里没有颜色就画不出来,我们的模型插不进去
所以这个方法只适合「就是原版那张床、只是换个颜色」的情况
我们要把自己 Blockbench 做的模型塞进去,还是得自己拼 MultiVariantGenerator
1 | private void generateBedBlock(BlockModelGenerators blockModelGenerators, Block block) { |
和沙发那期一个套路,两层 with
第一层按 PART 选模型,两半都指向同一份
第二层按 FACING 套旋转
模型和贴图
1 | assets/tutorial/models/block/bed.json |
模型是两格长的,注意别只做一格
测试
跑完数据生成就可以在游戏里测试了
常见问题(大肥鱼总结版)
- 一张床显示成两格重复的模型:
getRenderShape没写 - 床显示出来了,但错开一格:模型的床头没放在
z=0那一端,或者方块状态的y旋转对不上 - 只显示一半床,另一半是空的:
getRenderShape里HEAD和FOOT写反了 - 拆一半另一半还在:
updateShape被你自己覆盖掉了,记得保留原版的判断 - 拆一张床掉两个:战利品表没加
part=head条件 - 睡不上去,右键没反应:没有继承
BedBlock,或者getStateForPlacement那一格没空间 - 下界放床就炸:这是原版逻辑,不是 bug
- 日志里刷「block entity not valid」:
newBlockEntity没返回null,游戏创建了原版的BedBlockEntity - 床的碰撞箱太高:
SHAPE给的是 8 像素,别按整格写 - 物品栏里的床是拉长的一坨:模型
display里的gui没调,Blockbench里重新导一次 - 想用
createBed一行搞定,结果模型变成原版床:这个方法把模型路径写死成minecraft:block/bed,自定义模型用不上,还是得自己拼







