本篇教程的视频

本篇教程的源代码

GitHub地址:

查看源代码

虽然说本期教程并不是完全按照原版在栅栏方块来写自己的栅栏方块

我们将按照之前的可连接方块的写法,来编写自定义的栅栏方块

不过我们还是来看一下原版的栅栏方块是怎么写的,这里我们来看FenceBlock

它是继承CrossCollisionBlock这个类的,所以我们不妨先来看看这个类

1
2
3
4
public static final BooleanProperty NORTH = PipeBlock.NORTH;
public static final BooleanProperty EAST = PipeBlock.EAST;
public static final BooleanProperty SOUTH = PipeBlock.SOUTH;
public static final BooleanProperty WEST = PipeBlock.WEST;

开头有4个方块属性,它们都是BooleanProperty类型的,这4个方块属性就是我们本次重点研究的东西

方块状态文件

顺便,我们就来看看之前我们写的栅栏的方块状态文件

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
{
"multipart": [
{
"apply": {
"model": "tutorial_mod:block/ice_ether_fence_post"
}
},
{
"apply": {
"model": "tutorial_mod:block/ice_ether_fence_side",
"uvlock": true
},
"when": {
"north": "true"
}
},
{
"apply": {
"model": "tutorial_mod:block/ice_ether_fence_side",
"uvlock": true,
"y": 180
},
"when": {
"south": "true"
}
},
{
"apply": {
"model": "tutorial_mod:block/ice_ether_fence_side",
"uvlock": true,
"y": 270
},
"when": {
"west": "true"
}
},
{
"apply": {
"model": "tutorial_mod:block/ice_ether_fence_side",
"uvlock": true,
"y": 90
},
"when": {
"east": "true"
}
}
]
}

可以看到,最开头的方块状态类型是multipart,而我们之前写的那些方块状态都是variants

这两个有什么区别呢?multipart是组合,variants是变体

还是有点抽象,我们挑上面文件的其中一部分来看看

1
2
3
4
5
{
"apply": {
"model": "tutorial_mod:block/ice_ether_fence_post"
}
}

这个是基础模型,也就是当栅栏的那个竖着的柱子

1
2
3
4
5
6
7
8
9
{
"apply": {
"model": "tutorial_mod:block/ice_ether_fence_side",
"uvlock": true
},
"when": {
"north": "true"
}
}

这个是栅栏的北面,当north属性为true时,就会显示这个模型,这个是栅栏方块横着的那些杆子

这里的north就是我们在CrossCollisionBlock中看到的NORTH

所以综合起来看,multipart就是根据某些方块属性,决定是否加载某一部分的模型

variants则是将所有情况都罗列出来,这个东西放在栅栏方块中就不太合适了

因为栅栏要罗列的情况太多太多了,因为你要考虑4个方向,你分别要考虑0、1、234个面连接的情况,
这玩意排列组合一下就有一堆状态要写,远比multipart复杂

所以本期教程的重点就在这里,得消化一下multipart这个方块状态类型

源代码

回过头来我们再来看看源代码

1
2
3
4
5
6
this.registerDefaultState(this.stateDefinition.any()
.setValue(NORTH, Boolean.valueOf(false))
.setValue(EAST, Boolean.valueOf(false))
.setValue(SOUTH, Boolean.valueOf(false))
.setValue(WEST, Boolean.valueOf(false))
.setValue(WATERLOGGED, Boolean.valueOf(false)));

这个就是FenceBlock的构造函数中,方块状态的初始化,默认都是false

WATERLOGGED是方块的含水特性,未来的教程也会有,方块需要实现SimpleWaterloggedBlock接口

1
super(2.0F, 2.0F, 16.0F, 16.0F, 24.0F, p_53302_);

FenceBlock的构造函数中,调用了父类CrossCollisionBlock的构造函数

其中的24.0F是栅栏方块的碰撞箱高度,16.0F在外轮廓线的高度,所以栅栏方块是无法越过的

1
2
3
4
5
6
7
8
9
10
public boolean connectsTo(BlockState pState, boolean pIsSideSolid, Direction pDirection) {
Block block = pState.getBlock();
boolean flag = this.isSameFence(pState);
boolean flag1 = block instanceof FenceGateBlock && FenceGateBlock.connectsToDirection(pState, pDirection);
return !isExceptionForConnection(pState) && pIsSideSolid || flag || flag1;
}

private boolean isSameFence(BlockState pState) {
return pState.is(BlockTags.FENCES) && pState.is(BlockTags.WOODEN_FENCES) == this.defaultBlockState().is(BlockTags.WOODEN_FENCES);
}

这个是栅栏方块是否可以连接的方法,看到这里的Tag了吧,是不是有点熟悉

这也就是为什么我们之前写的栅栏方块也得加入方块标签中,不然栅栏是无法连接的

1
2
3
4
5
6
7
8
public InteractionResult use(BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, InteractionHand pHand, BlockHitResult pHit) {
if (pLevel.isClientSide) {
ItemStack itemstack = pPlayer.getItemInHand(pHand);
return itemstack.is(Items.LEAD) ? InteractionResult.SUCCESS : InteractionResult.PASS;
} else {
return LeadItem.bindPlayerMobs(pPlayer, pLevel, pPos);
}
}

这个是右键栅栏方块时的方法,实现拴绳的逻辑,LEAD就是拴绳,你可以将拴绳绑在这个栅栏的杆子上

那么其他的方法我们就不看了,具体实现可连接的方法我们还用之前的方法来写

编写栅栏类方块

这里我们创建ModFenceBlock类,继承Block

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

方块状态

这里我们引入那4个方块状态属性

1
2
3
4
public static final BooleanProperty NORTH = BlockStateProperties.NORTH;
public static final BooleanProperty EAST = BlockStateProperties.EAST;
public static final BooleanProperty SOUTH = BlockStateProperties.SOUTH;
public static final BooleanProperty WEST = BlockStateProperties.WEST;

这4个属性也是在BlockStateProperties类中定义的,我们直接引入即可

然后在构造函数中初始化

1
2
3
this.registerDefaultState(this.defaultBlockState()
.setValue(NORTH, false).setValue(EAST, false)
.setValue(SOUTH, false).setValue(WEST, false));

另外也不要忘记在appendProperties方法中添加这4个方块属性

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

重写getStateForNeighborUpdate

同样我们还是来重写getStateForNeighborUpdate方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public BlockState updateShape(BlockState pState, Direction pDirection, BlockState pNeighborState, LevelAccessor pLevel, BlockPos pPos, BlockPos pNeighborPos) {
BlockPos north = pPos.north();
BlockPos south = pPos.south();
BlockPos east = pPos.east();
BlockPos west = pPos.west();

BlockState northState = pLevel.getBlockState(north);
BlockState southState = pLevel.getBlockState(south);
BlockState eastState = pLevel.getBlockState(east);
BlockState westState = pLevel.getBlockState(west);

return this.defaultBlockState()
.setValue(NORTH, northState.is(this))
.setValue(SOUTH, southState.is(this))
.setValue(EAST, eastState.is(this))
.setValue(WEST, westState.is(this));
}

根据当前方块位置来判断东西南北四个方位是否是一样的方块,然后根据实际情况来改变方块状态

当然这里的判断方法只是判断是否为同类方块,如果想像原版那样不同材质的栅栏之间进行连接,那么可以用Tag

方块注册

注册

1
2
3
public static final RegistryObject<Block> FENCE = 
registerBlocks("fence",
() -> new ModFenceBlock(BlockBehaviour.Properties.of().strength(2.0f, 2.0f).noOcclusion()));

实例化ModFenceBlock,顺便再加上一个noOcclusion方法

加入物品栏

1
pOutput.accept(ModBlocks.FENCE.get());

数据文件

战利品列表

1
dropSelf(ModBlocks.FENCE.get());

语言文件

1
add(ModBlocks.FENCE.get(), "Fence");

模型文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
customFence(ModBlocks.FENCE, "fence");

private <T extends Block> void customFence(RegistryObject<T> block, String name) {
ModelFile modelPost = models().getExistingFile(modLoc("block/" + name + "_post"));
ModelFile modelSide = models().getExistingFile(modLoc("block/" + name + "_side"));

MultiPartBlockStateBuilder builder = getMultipartBuilder(block.get())
.part().modelFile(modelPost).addModel().end();

PipeBlock.PROPERTY_BY_DIRECTION.entrySet().forEach(e -> {
Direction dir = e.getKey();
if (dir.getAxis().isHorizontal()) {
builder.part().modelFile(modelSide).rotationY((((int) dir.toYRot()) + 180) % 360).addModel()
.condition(e.getValue(), true);
}
});

simpleBlockItem(block.get(), models().getExistingFile(modLoc("block/" + name)));
}

这里我们需要自定义一个方法来生成我们的栅栏的方块状态文件

customFence方法中,我们使用getMultipartBuilder来创建multipart方块状态

condition方法就是设置当某一个属性为某一个值时成立

另外,注意一下,在Blockbench制作模型的时候,竖杆就光秃秃的一根,而横杆注意放置的位置

img

横杆(这说法可能不太对,应该是除了中央竖杆之外的部分),你得南北向放置,而且得放置在北面,
就像上图所示,不然在游戏中会出问题

测试

最后我们就可以加入游戏进行测试了,当然,因为没有设置更为准确的碰撞箱,所以环境光遮蔽会有点问题