本篇教程的视频

本篇教程的源代码

GitHub地址:TutorialMod-Fence-1.20.1

本篇教程目标

  • 理解原版栅栏方块的编写
  • 学会编写栅栏类方块及其方块状态文件

查看源代码

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

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

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

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

1
2
3
4
public static final BooleanProperty NORTH = ConnectingBlock.NORTH;
public static final BooleanProperty EAST = ConnectingBlock.EAST;
public static final BooleanProperty SOUTH = ConnectingBlock.SOUTH;
public static final BooleanProperty WEST = ConnectingBlock.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
{
"multipart": [
{
"apply": {
"model": "minecraft:block/bamboo_fence_post"
}
},
{
"apply": {
"model": "minecraft:block/bamboo_fence_side_north",
"uvlock": false
},
"when": {
"north": "true"
}
},
{
"apply": {
"model": "minecraft:block/bamboo_fence_side_east",
"uvlock": false
},
"when": {
"east": "true"
}
},
{
"apply": {
"model": "minecraft:block/bamboo_fence_side_south",
"uvlock": false
},
"when": {
"south": "true"
}
},
{
"apply": {
"model": "minecraft:block/bamboo_fence_side_west",
"uvlock": false
},
"when": {
"west": "true"
}
}
]
}

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

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

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

1
2
3
"apply": {
"model": "minecraft:block/bamboo_fence_post"
}

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

1
2
3
4
5
6
7
"apply": {
"model": "minecraft:block/bamboo_fence_side_north",
"uvlock": false
},
"when": {
"north": "true"
}

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

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

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

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

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

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

源代码

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

1
2
3
4
5
6
7
8
9
this.setDefaultState(
this.stateManager
.getDefaultState()
.with(NORTH, Boolean.valueOf(false))
.with(EAST, Boolean.valueOf(false))
.with(SOUTH, Boolean.valueOf(false))
.with(WEST, Boolean.valueOf(false))
.with(WATERLOGGED, Boolean.valueOf(false))
);

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

WATERLOGGED是方块的含水特性,未来的教程也会有,这个是让方块类实现Waterloggable接口

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

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

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

1
2
3
4
5
6
7
8
9
10
public boolean canConnect(BlockState state, boolean neighborIsFullSquare, Direction dir) {
Block block = state.getBlock();
boolean bl = this.canConnectToFence(state);
boolean bl2 = block instanceof FenceGateBlock && FenceGateBlock.canWallConnect(state, dir);
return !cannotConnect(state) && neighborIsFullSquare || bl || bl2;
}

private boolean canConnectToFence(BlockState state) {
return state.isIn(BlockTags.FENCES) && state.isIn(BlockTags.WOODEN_FENCES) == this.getDefaultState().isIn(BlockTags.WOODEN_FENCES);
}

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

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

1
2
3
4
5
6
7
8
9
@Override
public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
if (world.isClient) {
ItemStack itemStack = player.getStackInHand(hand);
return itemStack.isOf(Items.LEAD) ? ActionResult.SUCCESS : ActionResult.PASS;
} else {
return LeadItem.attachHeldMobsToBlock(player, world, pos);
}
}

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

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

编写栅栏类方块

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

1
2
3
4
5
6
public class ModFenceBlock extends Block {

public ModFenceBlock(Settings settings) {
super(settings);
}
}

方块状态

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

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

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

然后在构造函数中初始化

1
2
3
4
5
this.setDefaultState(this.getStateManager().getDefaultState()
.with(NORTH, false)
.with(EAST, false)
.with(SOUTH, false)
.with(WEST, false));

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

1
2
3
4
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
builder.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 getStateForNeighborUpdate(BlockState state, Direction direction, BlockState neighborState, WorldAccess world, BlockPos pos, BlockPos neighborPos) {
BlockPos north = pos.north();
BlockPos south = pos.south();
BlockPos east = pos.east();
BlockPos west = pos.west();

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

return this.getDefaultState()
.with(NORTH, northState.isOf(this))
.with(SOUTH, southState.isOf(this))
.with(EAST, eastState.isOf(this))
.with(WEST, westState.isOf(this));
}

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

方块注册

注册

1
2
public static final Block FENCE = register("fence",
new ModFenceBlock(AbstractBlock.Settings.create().strength(2.0f, 6.0f).nonOpaque()));

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

加入物品栏

1
entries.add(ModBlocks.FENCE);

数据文件

语言文件

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

模型文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Identifier post = new Identifier(TutorialMod.MOD_ID, "block/fence_post");
Identifier side = new Identifier(TutorialMod.MOD_ID, "block/fence_side");
blockStateModelGenerator.blockStateCollector
.accept(createFenceBlockState(ModBlocks.FENCE, post, side));

// 这个方法在generateBlockStateModels方法外
public static BlockStateSupplier createFenceBlockState(Block fenceBlock, Identifier postModelId, Identifier sideModelId) {
return MultipartBlockStateSupplier.create(fenceBlock)
.with(BlockStateVariant.create().put(VariantSettings.MODEL, postModelId))
.with(When.create().set(Properties.NORTH, true), BlockStateVariant.create().put(VariantSettings.MODEL, sideModelId))
.with(
When.create().set(Properties.EAST, true),
BlockStateVariant.create().put(VariantSettings.MODEL, sideModelId).put(VariantSettings.Y, VariantSettings.Rotation.R90)
)
.with(
When.create().set(Properties.SOUTH, true),
BlockStateVariant.create().put(VariantSettings.MODEL, sideModelId).put(VariantSettings.Y, VariantSettings.Rotation.R180)
)
.with(
When.create().set(Properties.WEST, true),
BlockStateVariant.create().put(VariantSettings.MODEL, sideModelId).put(VariantSettings.Y, VariantSettings.Rotation.R270)
);
}

这个写法呢,也是照着原版的栅栏抄的,我们只需要准备两个方块的模型文件即可,一个竖杆一个横杆

createFenceBlockState方法中,我们使用MultipartBlockStateSupplier来创建multipart方块状态

with方法里面写的就是我们前面看到的栅栏方块的那些判断条件,When.create().set()方法就是设置当某一个属性为某一个值时成立
(第一个参数填方块属性,第二个是条件成立的值),put方法就是设置模型文件

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

img

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

测试

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