本篇教程的视频

本篇教程的源代码

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;
}
}

最后,如果床没有被占领,那么玩家就可以尝试睡眠

当然还会进行判断,当不符合条件时,也会提醒玩家,比如离床太远周围有怪物在游荡,你不能休息等等

至于重置玩家重生点的方法是在ServerPlayerEntitytrySleep方法中,有单独的方法来判定是否重置玩家重生点

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制作

测试

做完一切之后我们就可以进入游戏进行测试了