本篇教程的视频
本篇教程的源代码
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
| public InteractionResult use(BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, InteractionHand pHand, BlockHitResult pHit) { if (pLevel.isClientSide) { return InteractionResult.CONSUME; } else { if (pState.getValue(PART) != BedPart.HEAD) { pPos = pPos.relative(pState.getValue(FACING)); pState = pLevel.getBlockState(pPos); if (!pState.is(this)) { return InteractionResult.CONSUME; } }
if (!canSetSpawn(pLevel)) { pLevel.removeBlock(pPos, false); BlockPos blockpos = pPos.relative(pState.getValue(FACING).getOpposite()); if (pLevel.getBlockState(blockpos).is(this)) { pLevel.removeBlock(blockpos, false); }
Vec3 vec3 = pPos.getCenter(); pLevel.explode((Entity)null, pLevel.damageSources().badRespawnPointExplosion(vec3), (ExplosionDamageCalculator)null, vec3, 5.0F, true, Level.ExplosionInteraction.BLOCK); return InteractionResult.SUCCESS; } else if (pState.getValue(OCCUPIED)) { if (!this.kickVillagerOutOfBed(pLevel, pPos)) { pPlayer.displayClientMessage(Component.translatable("block.minecraft.bed.occupied"), true); }
return InteractionResult.SUCCESS; } else { pPlayer.startSleepInBed(pPos).ifLeft((p_49477_) -> { if (p_49477_.getMessage() != null) { pPlayer.displayClientMessage(p_49477_.getMessage(), true); }
}); return InteractionResult.SUCCESS; } } }
|
这个是床的交互,也就是当玩家右键床的时候执行的逻辑
首先判断是否是床头,如果不是床头,就获取床头的位置,
然后判断床头是否存在,如果不存在,就返回
然后通过canSetSpawn判断床是否可以在当前维度下正常运行,如果不在运行,就移除床,并爆炸
1 2 3
| public static boolean canSetSpawn(Level pLevel) { return pLevel.dimensionType().bedWorks(); }
|
在下界和末地,床都是会爆炸的
然后判断床是否被占领,如果床是被村民占领的,就唤醒村民,但如果床不是被村民占领的,
比如你在多人游戏中,床被另外一个玩家占领,那么你右键床的时候,就会输出提示信息
1 2 3 4 5 6 7 8 9
| private boolean kickVillagerOutOfBed(Level pLevel, BlockPos pPos) { List<Villager> list = pLevel.getEntitiesOfClass(Villager.class, new AABB(pPos), LivingEntity::isSleeping); if (list.isEmpty()) { return false; } else { list.get(0).stopSleeping(); return true; } }
|
最后,如果床没有被占领,那么玩家就可以尝试睡眠
当然还会进行判断,当不符合条件时,也会提醒玩家,比如离床太远、周围有怪物在游荡,你不能休息等等
至于重置玩家重生点的方法是在ServerPlayerEntity的trySleep方法中,有单独的方法来判定是否重置玩家重生点
1 2 3
| public void fallOn(Level pLevel, BlockState pState, BlockPos pPos, Entity pEntity, float pFallDistance) { super.fallOn(pLevel, pState, pPos, pEntity, pFallDistance * 0.5F); }
|
这个方法是当实体坠落在床上时,会被弹起一段距离(黏液块中也有类似的方法)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public void updateEntityAfterFallOn(BlockGetter pLevel, Entity pEntity) { if (pEntity.isSuppressingBounce()) { super.updateEntityAfterFallOn(pLevel, pEntity); } else { this.bounceUp(pEntity); }
}
private void bounceUp(Entity pEntity) { Vec3 vec3 = pEntity.getDeltaMovement(); if (vec3.y < 0.0D) { double d0 = pEntity instanceof LivingEntity ? 1.0D : 0.8D; pEntity.setDeltaMovement(vec3.x, -vec3.y * (double)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 6
| public class ModBedBlock extends BedBlock {
public ModBedBlock(DyeColor pColor, Properties pProperties) { super(pColor, pProperties); } }
|
这里我们创建ModBedBlock,并创建构造函数
然后我们写个碰撞箱
1 2 3 4 5 6
| public static final VoxelShape SHAPE = Block.box(0, 0, 0, 16, 8, 16);
@Override public VoxelShape getShape(BlockState pState, BlockGetter pLevel, BlockPos pPos, CollisionContext pContext) { return SHAPE; }
|
这个大小是一个半砖的大小,与原版床的高度一样,当然你要更加细致就参考原版的床来好了
另外,我们再重写getRenderType
1 2 3 4 5 6 7 8
| @Override public RenderShape getRenderShape(BlockState pState) { if (pState.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
| pOutput.accept(ModBlocks.BED.get());
|
数据文件
战利品列表
1 2
| add(ModBlocks.BED.get(), block -> createSinglePropConditionTable(block, BedBlock.PART, BedPart.HEAD));
|
战利品列表的写法有些特殊,因为床是两个方块,而我们只希望它只会有一个掉落物,所以这里的战利品列表要给它加上一个条件
只有方块类型是BedPart.HEAD,才会有掉落物
语言文件
1
| add(ModBlocks.BED.get(), "Bed");
|
模型文件
1
| customHorizontalBlock(ModBlocks.BED);
|
床的话,它是带有FACING参数的,所以这里我们使用customHorizontalBlock(之前的自定义方法)来生成方块状态文件
另外的方块模型我们依旧拿Blockbench制作
测试
做完一切之后我们就可以进入游戏进行测试了