本篇教程的视频
本篇教程的源代码
本篇教程目标
自定义方块类
本期教程我们来写一个可连接方块——沙发
源代码的话我们这里就先不看了,因为我们现在写的并不是像栅栏这样的方块
未来的另外一期的可连接方块我们再来讲栅栏那样的方块
SofaBlock
接下来我们先创建一个SofaBlock
类,继承自Block
类,并重写它的方法
1 2 3 4 5
| public class SofaBlock extends Block { public SofaBlock(Settings settings) { super(settings); } }
|
连接状态枚举类
原版有的连接状态也就只有栅栏
方块有,但并不是我们需要的
因为沙发总不能像栅栏那样东西南北4
个面都可以连接吧,在这一期教程中,我们暂且定义这个沙发只能左右
连接
那么现在,我们就得自己来创建一个方块状态的属性,首先是创建一个枚举类
,定义各个状态
在SofaBlock类中创建一个嵌套的枚举类Type,实现StringIdentifiable接口
1 2 3 4 5 6 7 8
| public enum Type implements StringIdentifiable { ;
@Override public String asString() { return ""; } }
|
这里我们再定义一个String
类型的变量,用于表示连接状态的名字
1
| private final String name;
|
同时也要创建一个构造函数,用于初始化这个变量
1 2 3
| Type(String name) { this.name = name; }
|
后面的重写方法我们也要改一下
1 2 3 4
| @Override public String asString() { return this.name; }
|
最后我们就可以来定义各个不同的状态了
1 2 3 4
| SINGLE("single"), LEFT("left"), RIGHT("right"), MIDDLE("middle");
|
这里我们一共定义了4
个状态,分别是单
个沙发、左
沙发、右
沙发、中
间沙发
那么同样的,你的方块模型就得准备4
个,当然建模上的事情我们这里就不多讲了
定义方块状态
写完这个Type
类之后,我们接下来就要声明这个类,给我们的方块赋予方块状态了
1 2 3
| public static final DirectionProperty FACING = Properties.HORIZONTAL_FACING;
public static final EnumProperty<Type> TYPE = EnumProperty.of("type", Type.class);
|
这里我们将FACING引入,再定义一共EnumProperty
类型的变量,泛型是我们写的方块状态枚举类
随后使用EnumProperty.of
方法来创建这个变量,第一个参数是名字,第二个参数是枚举类
方块状态初始化
我们在构造函数
中来初始化这些方块状态
1 2 3 4
| public SofaBlock(Settings settings) { super(settings); this.setDefaultState(this.getStateManager().getDefaultState().with(FACING, Direction.NORTH).with(TYPE, Type.SINGLE)); }
|
写法上和我们之前写的差不多,用with
方法来设置默认状态
重写方法
接下来我们重写几个方法
常规方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Override protected void appendProperties(StateManager.Builder<Block, BlockState> builder) { builder.add(FACING, TYPE); }
@Override public BlockState rotate(BlockState state, BlockRotation rotation) { return state.with(FACING, rotation.rotate(state.get(FACING))); }
@Override public BlockState mirror(BlockState state, BlockMirror mirror) { return state.rotate(mirror.getRotation(state.get(FACING))); }
@Override public BlockState getPlacementState(ItemPlacementContext ctx) { return this.getDefaultState().with(FACING, ctx.getHorizontalPlayerFacing().getOpposite()); }
|
这4个方法我们之前都遇到过了,这里就不再赘述
那么随后我们就要再重写一些方法用于改变方块的状态,也就是实现方块能够连接起来的方法
getStateForNeighborUpdate
这里我们要重写getStateForNeighborUpdate
方法,这个名字显然易见,就是当周围方块发生改变时,会调用这个方法
1 2 3 4
| @Override public BlockState getStateForNeighborUpdate(BlockState state, Direction direction, BlockState neighborState, WorldAccess world, BlockPos pos, BlockPos neighborPos) { return this.getRelatedBlockState(state, world, pos, state.get(FACING)); }
|
这里我们调用了getRelatedBlockState
方法,这个方法是我们自定义的方法,用于获取相关的方块状态
1 2 3 4 5 6 7 8 9 10 11 12
| private BlockState getRelatedBlockState(BlockState state, WorldAccess world, BlockPos pos, Direction direction) { boolean left = isRelatedInDirection(world, pos, direction, true); boolean right = isRelatedInDirection(world, pos, direction, false); if (left && right) { return state.with(TYPE, Type.MIDDLE); } else if (left) { return state.with(TYPE, Type.LEFT); } else if (right) { return state.with(TYPE, Type.RIGHT); } return state.with(TYPE, Type.SINGLE); }
|
这个方法就是用来返回不同的方块状态,判断左右
是否同样是沙发方块,再来决定是否连接
isRelatedInDirection
方法也是我们自定义的方法
不过这里注意一下,因为我没有准备充分的问题
在本期教程中,沙发建模时采用的left
、right
(左右
)等参数,是对于玩家而言的
而在沙发是否可连接的判定中,是对于方块本身而言的,所以下面的判断是反过来的
1 2 3 4 5 6 7 8 9 10 11 12 13
| private boolean isRelatedInDirection(WorldAccess world, BlockPos pos, Direction direction, boolean counterClockwise) { Direction rotate = counterClockwise ? direction.rotateYCounterclockwise() : direction.rotateYClockwise(); return this.isRelatedBlock(world, pos, rotate, direction); }
private boolean isRelatedBlock(WorldAccess world, BlockPos pos, Direction rotate, Direction direction) { BlockState state = world.getBlockState(pos.offset(rotate)); if (state.getBlock() == this) { Direction direction1 = state.get(FACING); return direction1.equals(direction); } return false; }
|
其实是还有两个方法的,当时想放一起然后后面又忘记了
isRelatedInDirection
方法就进行了一步方向旋转的操作,这个旋转是根据最后的布尔值来决定是逆时针旋转还是顺时针旋转
rotateYCounterclockwise()
是逆时针旋转,rotateYClockwise()
是顺时针旋转
那么问题又来了,这转的是什么?Direction,也就是方位
要理解这个方法也很简单,现在请你站起来,然后你逆时针
转动90°
,是不是就面向你原来左边
的这个方位了,同理,顺时针
转,就面向右边
的
当然,因为这里的方法判断是对于玩家来说的,所以上面是反着写的
isRelatedBlock
方法就是判断这个方块是否是沙发方块,并且方向是否一致
正常的沙发都是面向同一个方向的吧,总不能一个朝前一个朝后然后还能接起来吧?
当然,你也可以去实现像楼梯那样的
那么现在,我们的方块就已经写好了,接下去就是注册方块
注册方块
注册方块
那么接下来我们就来注册方块
1 2
| public static final Block SOFA = register("sofa", new SofaBlock(AbstractBlock.Settings.create().strength(2.0f, 6.0f).nonOpaque()));
|
那么它实例化的是我们前面写的SofaBlock
,并且设置了一些属性
添加物品栏
不要忘记将物品添加到物品栏
1
| entries.add(ModBlocks.SOFA);
|
数据文件
语言文件
1
| translationBuilder.add(ModBlocks.SOFA, "Sofa");
|
模型文件
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 49
| Identifier left = new Identifier(TutorialMod.MOD_ID, "block/sofa_left"); Identifier right = new Identifier(TutorialMod.MOD_ID, "block/sofa_right"); Identifier middle = new Identifier(TutorialMod.MOD_ID, "block/sofa_middle"); Identifier single = new Identifier(TutorialMod.MOD_ID, "block/sofa"); blockStateModelGenerator.blockStateCollector .accept(createSofaBlockState(ModBlocks.SOFA, left, right, middle, single));
public static BlockStateSupplier createSofaBlockState(Block block, Identifier left, Identifier right, Identifier middle, Identifier single) { return VariantsBlockStateSupplier.create(block) .coordinate( BlockStateVariantMap.create(SofaBlock.TYPE, Properties.HORIZONTAL_FACING) .register(SofaBlock.Type.LEFT, Direction.EAST, BlockStateVariant.create().put(VariantSettings.MODEL, left).put(VariantSettings.Y, VariantSettings.Rotation.R90)) .register(SofaBlock.Type.LEFT, Direction.SOUTH, BlockStateVariant.create().put(VariantSettings.MODEL, left).put(VariantSettings.Y, VariantSettings.Rotation.R180)) .register(SofaBlock.Type.LEFT, Direction.WEST, BlockStateVariant.create().put(VariantSettings.MODEL, left).put(VariantSettings.Y, VariantSettings.Rotation.R270)) .register(SofaBlock.Type.LEFT, Direction.NORTH, BlockStateVariant.create().put(VariantSettings.MODEL, left))
.register(SofaBlock.Type.RIGHT, Direction.EAST, BlockStateVariant.create().put(VariantSettings.MODEL, right).put(VariantSettings.Y, VariantSettings.Rotation.R90)) .register(SofaBlock.Type.RIGHT, Direction.SOUTH, BlockStateVariant.create().put(VariantSettings.MODEL, right).put(VariantSettings.Y, VariantSettings.Rotation.R180)) .register(SofaBlock.Type.RIGHT, Direction.WEST, BlockStateVariant.create().put(VariantSettings.MODEL, right).put(VariantSettings.Y, VariantSettings.Rotation.R270)) .register(SofaBlock.Type.RIGHT, Direction.NORTH, BlockStateVariant.create().put(VariantSettings.MODEL, right))
.register(SofaBlock.Type.MIDDLE, Direction.EAST, BlockStateVariant.create().put(VariantSettings.MODEL, middle).put(VariantSettings.Y, VariantSettings.Rotation.R90)) .register(SofaBlock.Type.MIDDLE, Direction.SOUTH, BlockStateVariant.create().put(VariantSettings.MODEL, middle).put(VariantSettings.Y, VariantSettings.Rotation.R180)) .register(SofaBlock.Type.MIDDLE, Direction.WEST, BlockStateVariant.create().put(VariantSettings.MODEL, middle).put(VariantSettings.Y, VariantSettings.Rotation.R270)) .register(SofaBlock.Type.MIDDLE, Direction.NORTH, BlockStateVariant.create().put(VariantSettings.MODEL, middle))
.register(SofaBlock.Type.SINGLE, Direction.EAST, BlockStateVariant.create().put(VariantSettings.MODEL, single).put(VariantSettings.Y, VariantSettings.Rotation.R90)) .register(SofaBlock.Type.SINGLE, Direction.SOUTH, BlockStateVariant.create().put(VariantSettings.MODEL, single).put(VariantSettings.Y, VariantSettings.Rotation.R180)) .register(SofaBlock.Type.SINGLE, Direction.WEST, BlockStateVariant.create().put(VariantSettings.MODEL, single).put(VariantSettings.Y, VariantSettings.Rotation.R270)) .register(SofaBlock.Type.SINGLE, Direction.NORTH, BlockStateVariant.create().put(VariantSettings.MODEL, single)) ); }
|
由于我们这里引入了一个自己定义的Type变量,所以没有原版的数据生成类可以调用了,但是我们可以自己写一个
这下面的一大串createSofaBlockState
就是根据原版的门的方块状态生成方法改的
当然要注意将原来方法中的UV锁
给去掉,不然我们的贴图就出问题了
这一串方法看着复杂,但很多都是重复的,其实质就是罗列出每一个状态对应的方块状态是什么
BlockStateVariantMap.create
传入SofaBlock.TYPE
和Properties.HORIZONTAL_FACING
这是我们的两个方块状态属性
1 2 3
| .register(SofaBlock.Type.LEFT, Direction.EAST, BlockStateVariant.create().put(VariantSettings.MODEL, left) .put(VariantSettings.Y, VariantSettings.Rotation.R90))
|
就拿其中的一个register
语句来看,指定当Type
为LEFT
,facing
为EAST
时,
返回的模型是left
字符串中定义的,并且按Y
轴旋转90
度
其他的也都是这样的,当然,这种方法或许还有更加简单的方法,尤其是上面的4
个Identifier
当你有很多这样的方块要添加的时候,就可以这么做了
另外的模型文件就是Blockbench
制作的了
测试
那么跑好数据生成之后,我们就可以进入游戏进行测试了