本篇教程的视频
本篇教程的源代码
本篇教程目标
- 理解原版
栅栏
方块的编写
- 学会编写栅栏类方块及其
方块状态
文件
查看源代码
虽然说本期教程并不是完全按照原版在栅栏方块来写自己的栅栏方块
我们将按照之前的可连接方块的写法,来编写自定义的栅栏方块
不过我们还是来看一下原版的栅栏方块是怎么写的,这里我们来看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、2
、3
、4
个面连接的情况,
这玩意排列组合一下就有一堆状态要写,远比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));
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
制作模型的时候,竖杆就光秃秃的一根,而横杆注意放置的位置

横杆(这说法可能不太对,应该是除了中央竖杆之外的部分),你得南北向放置,而且得放置在北面,
就像上图所示,不然在游戏中会出问题
测试
最后我们就可以加入游戏进行测试了,当然,因为没有设置更为准确的碰撞箱,所以环境光遮蔽
会有点问题