本篇教程的视频

本篇教程的源代码

GitHub地址:

查看源代码

那么在我们的游戏中,除了六个面都是相同材质的方块之外,大多数方块都是有朝向的

比如熔炉,我暂且将它开口的那个面称之为特征面

当我们放下熔炉方块时,不论你面朝何方(除了上下),熔炉的特征面永远是面向玩家的

同理,楼梯讲台这种异形方块也都是有朝向的

FACING属性

这里我们就来看看熔炉方块的源代码,重点找到它的方块状态,这里找到AbstractFurnaceBlock

1
public static final DirectionProperty FACING = HorizontalDirectionalBlock.FACING;

这里定义了一个FACING属性,从它的名字来看就是我们要找的方块朝向

这个是一个DirectionProperty类型的变量,是Minecraft中用于表示方位的属性

1
public static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING;

虽然说熔炉方块的FACING引用的是HorizontalDirectionalBlock中的,不过它本质上引用的是BlockStateProperties.HORIZONTAL_FACING

BlockStateProperties类中,我们可以找到它的定义

1
public static final DirectionProperty HORIZONTAL_FACING = DirectionProperty.create("facing", Direction.Plane.HORIZONTAL);

另外我们也可以看到另一个名为FACING这个属性的定义

1
2
3
public static final DirectionProperty FACING = DirectionProperty.create(
"facing", Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.UP, Direction.DOWN
);

原版就只有木桶方块引用的是这个FACING,相比较而言,HORIZONTAL_FACING这个属性只有东西南北四个方向,而FACING则多了上下

哎?那么楼梯的上下是怎么区分的?好问题

楼梯方块有其他的方块状态共同决定其形状,它的方法也和其他方块不一样

具体的感兴趣的同学可以自己研究,未来我们或许会讲讲楼梯方块的具体实现

状态初始化

另外在熔炉方块的构造函数中,我们可以看到对方块状态初始化

1
2
3
4
protected AbstractFurnaceBlock(BlockBehaviour.Properties properties) {
super(properties);
this.registerDefaultState(this.stateDefinition.any().setValue(FACING, Direction.NORTH).setValue(LIT, Boolean.valueOf(false)));
}

这里我们看到了FACING,并且初始化为Direction.NORTH,也就是朝

另外一个LIT属性我们在未来的教程中也会讲,这个是控制方块发光的属性,因为熔炉在工作时,它会照亮周围

一些方法

然后,我们再看看引用FACING的一些方法

1
2
3
4
@Override
public BlockState getStateForPlacement(BlockPlaceContext context) {
return this.defaultBlockState().setValue(FACING, context.getHorizontalDirection().getOpposite());
}

这里我们看到了getStateForPlacement这个方法,这个方法用于获取方块放置时的状态

getHorizontalDirection这个方法用于获取玩家放置方块时的朝向

getOpposite方法用于获取方位的反方向,比如玩家面朝南,那么熔炉就会朝北

1
2
3
4
@Override
protected BlockState rotate(BlockState state, Rotation rotation) {
return state.setValue(FACING, rotation.rotate(state.getValue(FACING)));
}

rotate这个方法用于旋转方块,这里我们直接将FACING属性旋转即可

1
2
3
4
@Override
protected BlockState mirror(BlockState state, Mirror mirror) {
return state.rotate(mirror.getRotation(state.getValue(FACING)));
}

mirror这个方法用于镜像方块,这里我们直接将FACING属性旋转即可

当然这两个方法具体我也不太清楚是用来干什么的,带有方块朝向的方块,一般都会重写这两个方法

1
2
3
4
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(FACING, LIT);
}

最后还要将方块属性加入到方块中,不然方块还是没有这些属性的

添加朝向

这期教程我们也是用Blockbench制作的方块

导出方块碰撞箱

那么在导出方块之前,我们再安装一个插件,用于导出方块的碰撞箱

在文件->插件中搜索并安装Mod Utils这个插件,然后我们在导出这就会多了一个选项,Export VoxelShape(1.14+ Moded Minecraft)

图片

但在导出之前,还得将我们方块(组成方块的小体块)放到一个名为VoxelShapes这个组下面,这样才能被这个插件识别(感觉这插件要求还挺多)

图片

然后再点击Export VoxelShape(1.14+ Modded Minecraft),就会弹出一个窗口,然后就可以选择映射,这里我们要选择Yarn,导出当前方块的碰撞箱

图片

但这还没完,因为我们需要4个不同面向的方块碰撞箱,那么现在你想到,把方块整体转一下呗

哎,在你的方块中没有斜着的体块时可以这么做,但是,本期教程的案例是我从方舟模组中拿过来的一个时钟(见视频教程),
这里面有很多斜着的小体块,当我们整体旋转时,斜着的小体块就跑掉了

图片

因为由于Blockbench的限制,Java版的方块只支持单轴旋转

那么怎么办呢?这个倒是简单,直接做一个能够覆盖整个方块的体块,然后将其他的体块删了,再导出方块的碰撞箱,
如此导出其他3个碰撞箱

图片

另外注意,在旋转方块时,将枢轴点调整到一个完整方块的中心,即XZ调到8Y轴可以不管

SimpleOrangeClock

这里我们新建一个自定义的方块类,SimpleOrangeClock,继承Block

1
2
3
4
5
public class SimpleOrangeClock extends Block {
public SimpleOrangeClock(Properties properties) {
super(properties);
}
}

方块状态

接下来我们来设置这个方块的方块状态

首先引入FACING这个参数

1
public static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING;

这里我们可以直接引用BlockStateProperties.HORIZONTAL_FACING

碰撞箱

随后我们将导出的方块碰撞箱放到方块中来

1
2
3
4
private static final VoxelShape SHAPE_N = Block.box(3, 3, 15, 13, 13, 16);
private static final VoxelShape SHAPE_W = Block.box(15, 3, 3, 16, 13, 13);
private static final VoxelShape SHAPE_S = Block.box(3, 3, 0, 13, 13, 1);
private static final VoxelShape SHAPE_E = Block.box(0, 3, 3, 1, 13, 13);

一共是4个方位的不同碰撞箱

方块状态初始化

构造函数中的方块状态初始化写一下

1
this.registerDefaultState(this.defaultBlockState().setValue(FACING, Direction.NORTH));

和原版一样,初始化为朝北

后面我们就要重写一些方法了

getShape

1
2
3
4
5
6
7
8
9
@Override
protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return switch (state.getValue(FACING)) {
case SOUTH -> SHAPE_S;
case EAST -> SHAPE_E;
case WEST -> SHAPE_W;
default -> SHAPE_N;
};
}

重写getShape这个方法,这个方法用于获取方块的碰撞箱

因为FACING的实际属性是一个枚举类型的(你一路挖下去就能发现),所以这里可以直接用switch来判断当前方块面向并返回对应的碰撞箱

哎,这里就有人要问了,FACING不是DirectionProperty吗,怎么就成枚举了?

其实原版大多数的方块状态都是一个注册id再带上一个枚举类型的变量,后面我们自己来定义方块状态属性时,你可能就能理解了

这里的FACING属性,其实就是一个facing的注册id,带上一个有东西南北这4个不同面向的方块属性

createBlockStateDefinition

1
2
3
4
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> pBuilder) {
pBuilder.add(FACING);
}

别忘了将FACING属性加入到方块中

其他的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public BlockState rotate(BlockState pState, Rotation pRot) {
return pState.setValue(FACING, pRot.rotate(pState.getValue(FACING)));
}

@Override
public BlockState mirror(BlockState pState, Mirror pMirror) {
return pState.rotate(pMirror.getRotation(pState.getValue(FACING)));
}

@Override
public @Nullable BlockState getStateForPlacement(BlockPlaceContext pContext) {
return this.defaultBlockState().setValue(FACING, pContext.getHorizontalDirection().getOpposite());
}

另外的三个我们就不再赘述了,按照原版来写就好了

注册方块

注册方块

1
2
3
public static final DeferredBlock<SimpleOrangeClock> SIMPLE_ORANGE_CLOCK =
registerBlock("simple_orange_clock",
() -> new SimpleOrangeClock(Block.Properties.of().strength(3.0F, 3.0F).noOcclusion()));

实例化SimpleOrangeClock

添加物品栏

1
output.accept(ModBlocks.SIMPLE_ORANGE_CLOCK);

不要忘了物品栏

数据生成

语言文件

1
add(ModBlocks.SIMPLE_ORANGE_CLOCK.get(), "Simple Orange Clock");

模型文件

1
2
3
4
5
6
7
customHorizontalBlock(ModBlocks.SIMPLE_ORANGE_CLOCK);

private <T extends Block> void customHorizontalBlock(DeferredBlock<T> block) {
ResourceLocation model = modLoc("block/" + block.getId().getPath());
horizontalBlock(block.get(), models().getExistingFile(model));
simpleBlockItem(block.get(), models().getExistingFile(model));
}

方块状态文件,这里我们用到了customHorizontalBlock自定义的方法来生成默认面向北的方块状态文件

当然,这个并不会生成我们的方块模型文件,我们使用Blockbench制作的模型文件

测试

那么最后,我们就可以进入游戏进行测试了