本篇教程的视频
本篇教程的源代码
GitHub地址:
查看源代码
本期教程我们来写一个使用自己方块的床类方块
当然,我们先看看原版的床是怎么写的,这里我们来看BedBlock
我们这一次写这个案例是直接继承BedBlock的,但它里面的有些东西我们还是得了解一下
1 2
| public static final EnumProperty<BedPart> PART = BlockStateProperties.BED_PART; public static final BooleanProperty OCCUPIED = BlockStateProperties.OCCUPIED;
|
两个方块的属性,一个是分前和后,一个是这个床是否被占领
后面是一堆碰撞箱,这里就略过
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| @Override protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) { if (level.isClientSide) { return InteractionResult.CONSUME; } else { if (state.getValue(PART) != BedPart.HEAD) { pos = pos.relative(state.getValue(FACING)); state = level.getBlockState(pos); if (!state.is(this)) { return InteractionResult.CONSUME; } }
if (!canSetSpawn(level)) { level.removeBlock(pos, false); BlockPos blockpos = pos.relative(state.getValue(FACING).getOpposite()); if (level.getBlockState(blockpos).is(this)) { level.removeBlock(blockpos, false); }
Vec3 vec3 = pos.getCenter(); level.explode(null, level.damageSources().badRespawnPointExplosion(vec3), null, vec3, 5.0F, true, Level.ExplosionInteraction.BLOCK); return InteractionResult.SUCCESS; } else if (state.getValue(OCCUPIED)) { if (!this.kickVillagerOutOfBed(level, pos)) { player.displayClientMessage(Component.translatable("block.minecraft.bed.occupied"), true); }
return InteractionResult.SUCCESS; } else { player.startSleepInBed(pos).ifLeft(p_49477_ -> { if (p_49477_.getMessage() != null) { player.displayClientMessage(p_49477_.getMessage(), true); } }); return InteractionResult.SUCCESS; } } }
|
这个是床的交互,也就是当玩家右键床的时候执行的逻辑
首先判断是否是床头,如果不是床头,就获取床头的位置,
然后判断床头是否存在,如果不存在,就返回
然后通过canSetSpawn判断床是否可以在当前维度下正常运行,如果不在运行,就移除床,并爆炸
1 2 3
| public static boolean canSetSpawn(Level level) { return level.dimensionType().bedWorks(); }
|
在下界和末地,床都是会爆炸的
然后判断床是否被占领,如果床是被村民占领的,就唤醒村民,但如果床不是被村民占领的,
比如你在多人游戏中,床被另外一个玩家占领,那么你右键床的时候,就会输出提示信息
1 2 3 4 5 6 7 8 9
| private boolean kickVillagerOutOfBed(Level level, BlockPos pos) { List<Villager> list = level.getEntitiesOfClass(Villager.class, new AABB(pos), LivingEntity::isSleeping); if (list.isEmpty()) { return false; } else { list.get(0).stopSleeping(); return true; } }
|
最后,如果床没有被占领,那么玩家就可以尝试睡眠
当然还会进行判断,当不符合条件时,也会提醒玩家,比如离床太远、周围有怪物在游荡,你不能休息等等
至于重置玩家重生点的方法是在ServerPlayerEntity的trySleep方法中,有单独的方法来判定是否重置玩家重生点
1 2 3 4
| @Override public void fallOn(Level level, BlockState state, BlockPos pos, Entity entity, float fallDistance) { super.fallOn(level, state, pos, entity, fallDistance * 0.5F); }
|
这个方法是当实体坠落在床上时,会被弹起一段距离(黏液块中也有类似的方法)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Override public void updateEntityAfterFallOn(BlockGetter level, Entity entity) { if (entity.isSuppressingBounce()) { super.updateEntityAfterFallOn(level, entity); } else { this.bounceUp(entity); } }
private void bounceUp(Entity entity) { Vec3 vec3 = entity.getDeltaMovement(); if (vec3.y < 0.0) { double d0 = entity instanceof LivingEntity ? 1.0 : 0.8; entity.setDeltaMovement(vec3.x, -vec3.y * 0.66F * d0, vec3.z); } }
|
这两个方法让你可以在床上蹦跶,会弹起来
1 2 3 4 5 6 7 8 9 10 11
| public static Optional<Vec3> findStandUpPosition(EntityType<?> pEntityType, CollisionGetter pCollisionGetter, BlockPos pPos, Direction pDirection, float pYRot) { ... }
private static Optional<Vec3> findBunkBedStandUpPosition(EntityType<?> pEntityType, CollisionGetter pCollisionGetter, BlockPos pPos, Direction pStateFacing, Direction pEntityFacing) { ... }
private static Optional<Vec3> findStandUpPositionAtOffset(EntityType<?> pEntityType, CollisionGetter pCollisionGetter, BlockPos pPos, int[][] pOffsets, boolean pSimulate) { ... }
|
这些方法显然易见,是寻找玩家起床后,用来重置玩家的位置
其他的方法的话,我们就不在这里讲了,另外的是关于这个床方块放置的方法,毕竟这个床也是分了两个部分的,
与门类似
未来我们的教程也会涉及放下一个方块后生成另外部分方块的教程,到那个时候我们再具体来讲好了
创建自定义床类
这里我们还是要继承BedBlock来写我们的床类方块,因为实际上trySleep方法是会判断这个方块是否是BedBlock的实例
所以除非你使用Mixin,不然就只能继承BedBlock来写床类方块
当然,这个Mixin也不是很难写
1 2 3 4 5
| public class ModBedBlock extends BedBlock { public ModBedBlock(DyeColor color, Properties properties) { super(color, properties); } }
|
这里我们创建ModBedBlock,并创建构造函数
然后我们写个碰撞箱
1 2 3 4 5 6
| public static final VoxelShape SHAPE = Block.box(0, 0, 0, 16, 8, 16);
@Override protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { return SHAPE; }
|
这个大小是一个半砖的大小,与原版床的高度一样,当然你要更加细致就参考原版的床来好了
另外,我们再重写getRenderType
1 2 3 4 5 6 7 8
| @Override protected RenderShape getRenderShape(BlockState state) { if (state.getValue(PART) == BedPart.HEAD) { return RenderShape.MODEL; } else { return RenderShape.INVISIBLE; } }
|
因为原版的床是两个部分,但我们的床是拿Blockbench制作的一个完整的模型,所以得隐藏其中一个部分
当然,我这种实际上属于偷懒的做法,你也可以参考原版床的放置逻辑
注册方块
注册
1 2 3
| public static final RegistryObject<Block> BED = registerBlocks("bed", () -> new ModBedBlock(DyeColor.BLACK, BlockBehaviour.Properties.of().strength(2.0f, 2.0f).noOcclusion()));
|
那么接下来我们就注册这个床,这里要填一个DyeColor,随便写一个就可以了
物品栏
不要忘记物品栏
1
| output.accept(ModBlocks.BED);
|
注册方块实体
因为1.21.1 改了一些底层逻辑,所以如果直接用之前 1.20.1 的方法添加床会出问题
这里我们还需要注册方块实体
数据文件
战利品列表
1
| add(ModBlocks.BED.get(), createSinglePropConditionTable(ModBlocks.BED.get(), BedBlock.PART, BedPart.HEAD));
|
战利品列表的写法有些特殊,因为床是两个方块,而我们只希望它只会有一个掉落物,所以这里的战利品列表要给它加上一个条件
只有方块类型是BedPart.HEAD,才会有掉落物
语言文件
1
| add(ModBlocks.BED.get(), "Bed");
|
模型文件
1
| customHorizontalBlock(ModBlocks.BED);
|
床的话,它是带有FACING参数的,所以这里我们使用customHorizontalBlock(之前的自定义方法)来生成方块状态文件
另外的方块模型我们依旧拿Blockbench制作
测试
做完一切之后我们就可以进入游戏进行测试了