本篇教程的视频
本篇教程的源代码
本篇教程目标
查看源代码
本期教程我们来写一个使用自己方块的床类方块
当然,我们先看看原版的床是怎么写的,这里我们来看BedBlock
我们这一次写这个案例是直接继承BedBlock
的,但它里面的有些东西我们还是得了解一下
1 2
| public static final EnumProperty<BedPart> PART = Properties.BED_PART; public static final BooleanProperty OCCUPIED = Properties.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 public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) { if (world.isClient) { return ActionResult.CONSUME; } else { if (state.get(PART) != BedPart.HEAD) { pos = pos.offset(state.get(FACING)); state = world.getBlockState(pos); if (!state.isOf(this)) { return ActionResult.CONSUME; } }
if (!isBedWorking(world)) { world.removeBlock(pos, false); BlockPos blockPos = pos.offset(((Direction)state.get(FACING)).getOpposite()); if (world.getBlockState(blockPos).isOf(this)) { world.removeBlock(blockPos, false); }
Vec3d vec3d = pos.toCenterPos(); world.createExplosion(null, world.getDamageSources().badRespawnPoint(vec3d), null, vec3d, 5.0F, true, World.ExplosionSourceType.BLOCK); return ActionResult.SUCCESS; } else if ((Boolean)state.get(OCCUPIED)) { if (!this.wakeVillager(world, pos)) { player.sendMessage(Text.translatable("block.minecraft.bed.occupied"), true); }
return ActionResult.SUCCESS; } else { player.trySleep(pos).ifLeft(reason -> { if (reason.getMessage() != null) { player.sendMessage(reason.getMessage(), true); } }); return ActionResult.SUCCESS; } } }
|
这个是床的交互,也就是当玩家右键
床的时候执行的逻辑
首先判断是否是床头
,如果不是床头,就获取床头的位置,
然后判断床头是否存在,如果不存在,就返回
然后判断床是否可以在当前维度
下正常运行,如果不在运行,就移除床,并爆炸
1 2 3
| public static boolean isBedWorking(World world) { return world.getDimension().bedWorks(); }
|
在下界
和末地
,床都是会爆炸的
然后判断床是否被占领
,如果床是被村民
占领的,就唤醒村民
,但如果床不是被村民占领的,
比如你在多人游戏中,床被另外一个玩家
占领,那么你右键床的时候,就会输出提示信息
1 2 3 4 5 6 7 8 9
| private boolean wakeVillager(World world, BlockPos pos) { List<VillagerEntity> list = world.getEntitiesByClass(VillagerEntity.class, new Box(pos), LivingEntity::isSleeping); if (list.isEmpty()) { return false; } else { ((VillagerEntity)list.get(0)).wakeUp(); return true; } }
|
最后,如果床没有被占领,那么玩家就可以尝试睡眠
当然还会进行判断,当不符合条件时,也会提醒玩家,比如离床太远
、周围有怪物在游荡,你不能休息
等等
至于重置玩家重生点的方法是在ServerPlayerEntity
的trySleep
方法中,有单独的方法来判定是否重置玩家重生点
1 2 3 4
| @Override public void onLandedUpon(World world, BlockState state, BlockPos pos, Entity entity, float fallDistance) { super.onLandedUpon(world, 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 onEntityLand(BlockView world, Entity entity) { if (entity.bypassesLandingEffects()) { super.onEntityLand(world, entity); } else { this.bounceEntity(entity); } }
private void bounceEntity(Entity entity) { Vec3d vec3d = entity.getVelocity(); if (vec3d.y < 0.0) { double d = entity instanceof LivingEntity ? 1.0 : 0.8; entity.setVelocity(vec3d.x, -vec3d.y * 0.66F * d, vec3d.z); } }
|
这两个方法让你可以在床上蹦跶
,会弹起来
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| public static Optional<Vec3d> findWakeUpPosition(EntityType<?> type, CollisionView world, BlockPos pos, Direction bedDirection, float spawnAngle) { Direction direction = bedDirection.rotateYClockwise(); Direction direction2 = direction.pointsTo(spawnAngle) ? direction.getOpposite() : direction; if (isBedBelow(world, pos)) { return findWakeUpPosition(type, world, pos, bedDirection, direction2); } else { int[][] is = getAroundAndOnBedOffsets(bedDirection, direction2); Optional<Vec3d> optional = findWakeUpPosition(type, world, pos, is, true); return optional.isPresent() ? optional : findWakeUpPosition(type, world, pos, is, false); } }
private static Optional<Vec3d> findWakeUpPosition(EntityType<?> type, CollisionView world, BlockPos pos, Direction bedDirection, Direction respawnDirection) { int[][] is = getAroundBedOffsets(bedDirection, respawnDirection); Optional<Vec3d> optional = findWakeUpPosition(type, world, pos, is, true); if (optional.isPresent()) { return optional; } else { BlockPos blockPos = pos.down(); Optional<Vec3d> optional2 = findWakeUpPosition(type, world, blockPos, is, true); if (optional2.isPresent()) { return optional2; } else { int[][] js = getOnBedOffsets(bedDirection); Optional<Vec3d> optional3 = findWakeUpPosition(type, world, pos, js, true); if (optional3.isPresent()) { return optional3; } else { Optional<Vec3d> optional4 = findWakeUpPosition(type, world, pos, is, false); if (optional4.isPresent()) { return optional4; } else { Optional<Vec3d> optional5 = findWakeUpPosition(type, world, blockPos, is, false); return optional5.isPresent() ? optional5 : findWakeUpPosition(type, world, pos, js, false); } } } } }
private static Optional<Vec3d> findWakeUpPosition(EntityType<?> type, CollisionView world, BlockPos pos, int[][] possibleOffsets, boolean ignoreInvalidPos) { BlockPos.Mutable mutable = new BlockPos.Mutable();
for (int[] is : possibleOffsets) { mutable.set(pos.getX() + is[0], pos.getY(), pos.getZ() + is[1]); Vec3d vec3d = Dismounting.findRespawnPos(type, world, mutable, ignoreInvalidPos); if (vec3d != null) { return Optional.of(vec3d); } }
return Optional.empty(); }
|
这些方法显然易见,是寻找玩家起床后,用来重置玩家的位置
其他的方法的话,我们就不在这里讲了,另外的是关于这个床方块放置的方法,毕竟这个床也是分了两个部分的,
与门类似
未来我们的教程也会涉及放下一个方块后生成另外部分方块的教程,到那个时候我们再具体来讲好了
创建自定义床类
这里我们还是要继承BedBlock
来写我们的床类方块,因为实际上trySleep
方法是会判断这个方块是否是BedBlock
的实例
所以除非你使用Mixin
,不然就只能继承BedBlock
来写床类方块
当然,这个Mixin
也不是很难写
1 2 3 4 5 6 7
| public class ModBedBlock extends BedBlock {
public ModBedBlock(DyeColor color, Settings settings) { super(color, settings); }
}
|
这里我们创建ModBedBlock
,并创建构造函数
然后我们写个碰撞箱
1 2 3 4 5 6
| public static final VoxelShape SHAPE = Block.createCuboidShape(0, 0, 0, 16, 8, 16);
@Override public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) { return SHAPE; }
|
这个大小是一个半砖
的大小,与原版床的高度一样,当然你要更加细致就参考原版的床来好了
另外,我们再重写getRenderType
1 2 3 4 5 6 7 8
| @Override public BlockRenderType getRenderType(BlockState state) { if (state.get(PART) == BedPart.HEAD) { return BlockRenderType.MODEL; } else { return BlockRenderType.INVISIBLE; } }
|
因为原版的床是两个部分,但我们的床是拿Blockbench
制作的一个完整
的模型,使用得隐藏其中一个部分,
不然两个区域就重叠了
注册方块
注册
1 2
| public static final Block BED = register("bed", new ModBedBlock(DyeColor.BLACK, AbstractBlock.Settings.create().strength(2.0f, 6.0f).nonOpaque()));
|
那么接下来我们就注册这个床,这里要填一个DyeColor
,随便写一个就可以了
物品栏
不要忘记物品栏
1
| entries.add(ModBlocks.BED);
|
数据文件
语言文件
1
| translationBuilder.add(ModBlocks.BED, "Bed");
|
模型文件
1
| blockStateModelGenerator.registerNorthDefaultHorizontalRotation(ModBlocks.BED);
|
床的话,它是带有FACING
参数的,所以这里我们使用registerNorthDefaultHorizontalRotation
来生成方块状态文件
另外的方块模型我们依旧拿Blockbench
制作
测试
做完一切之后我们就可以进入游戏进行测试了