本篇教程的视频

本篇教程的源代码

GitHub地址:TutorialMod-Sofa-1.20.1

本篇教程目标

  • 利用方块状态为方块添加可连接的属性

自定义方块类

本期教程我们来写一个可连接方块——沙发

源代码的话我们这里就先不看了,因为我们现在写的并不是像栅栏这样的方块

未来的另外一期的可连接方块我们再来讲栅栏那样的方块

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方法,这个方法是我们自定义的方法,用于获取相关的方块状态

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方法也是我们自定义的方法

不过这里注意一下,因为我没有准备充分的问题

在本期教程中,沙发建模时采用的leftright左右)等参数,是对于玩家而言的

而在沙发是否可连接的判定中,是对于方块本身而言的,所以下面的判断是反过来的

isRelatedInDirection

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));

// 这个方法在generateBlockStateModels之外
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.TYPEProperties.HORIZONTAL_FACING

这是我们的两个方块状态属性

1
2
3
.register(SofaBlock.Type.LEFT, Direction.EAST,
BlockStateVariant.create().put(VariantSettings.MODEL, left)
.put(VariantSettings.Y, VariantSettings.Rotation.R90))

就拿其中的一个register语句来看,指定当TypeLEFTfacingEAST时,
返回的模型是left字符串中定义的,并且按Y轴旋转90

其他的也都是这样的,当然,这种方法或许还有更加简单的方法,尤其是上面的4Identifier

当你有很多这样的方块要添加的时候,就可以这么做了

另外的模型文件就是Blockbench制作的了

测试

那么跑好数据生成之后,我们就可以进入游戏进行测试了