非实心方块与自定义模型
在这一节中,我们将创建一个有着特殊外形且是透明的方块,这里我们以黑曜石框架举例。
首先我们创建一个叫做ObsidianFrame
的类,内容如下:
public class ObsidianFrame extends Block implements IWaterLoggable {
private static final VoxelShape shape;
private static final BooleanProperty WATERLOGGED = BooleanProperty.create("waterlogged");
static {
VoxelShape base = Block.makeCuboidShape(0, 0, 0, 16, 1, 16);
VoxelShape column1 = Block.makeCuboidShape(0, 1, 0, 1, 15, 1);
VoxelShape column2 = Block.makeCuboidShape(15, 1, 0, 16, 15, 1);
VoxelShape column3 = Block.makeCuboidShape(0, 1, 15, 1, 15, 16);
VoxelShape column4 = Block.makeCuboidShape(15, 1, 15, 16, 15, 16);
VoxelShape top = Block.makeCuboidShape(0, 15, 0, 16, 16, 16);
shape = VoxelShapes.or(base, column1, column2, column3, column4, top);
}
public ObsidianFrame() {
super(Properties.create(Material.ROCK).hardnessAndResistance(5).notSolid());
this.setDefaultState(this.stateContainer.getBaseState().with(WATERLOGGED, false));
}
@Override
protected void fillStateContainer(StateContainer.Builder<Block, BlockState> builder) {
builder.add(WATERLOGGED);
super.fillStateContainer(builder);
}
@Override
public VoxelShape getShape(BlockState state, IBlockReader worldIn, BlockPos pos, ISelectionContext context) {
return shape;
}
@Nullable
@Override
public BlockState getStateForPlacement(BlockItemUseContext context) {
BlockPos blockpos = context.getPos();
BlockState blockstate = context.getWorld().getBlockState(blockpos);
if (blockstate.isIn(this)) {
return blockstate.with(WATERLOGGED, Boolean.valueOf(false));
} else {
FluidState fluidstate = context.getWorld().getFluidState(blockpos);
BlockState blockstate1 = this.getDefaultState().with(WATERLOGGED, Boolean.valueOf(fluidstate.getFluid() == Fluids.WATER));
return blockstate1;
}
}
public BlockState updatePostPlacement(BlockState stateIn, Direction facing, BlockState facingState, IWorld worldIn, BlockPos currentPos, BlockPos facingPos) {
if (stateIn.get(WATERLOGGED)) {
worldIn.getPendingFluidTicks().scheduleTick(currentPos, Fluids.WATER, Fluids.WATER.getTickRate(worldIn));
}
return super.updatePostPlacement(stateIn, facing, facingState, worldIn, currentPos, facingPos);
}
@Override
public FluidState getFluidState(BlockState state) {
return state.get(WATERLOGGED) ? Fluids.WATER.getStillFluidState(false) : super.getFluidState(state);
}
}
在最上方我们创建了一个VoxelShape
,我们将在getShape
方法中返回这个形状,这个VoxelShape
就是我们方块的碰撞箱,很不幸的是Minecraft的碰撞箱子只能由方块组成,这个方块的大小是16*16*16
,所以我们在静态代码块中自己创建了一系列的长方体和立方体,拼成了我们方块的碰撞箱,其中Block.makeCuboidShape
的6个参数分别是起始点的XYZ和结束点的XYZ。最后我们用VoxelShapes
的or
方法将这些东西拼在了一起。VoxelShapes
下还有很多好用的空间操作方法,请自行选用。如果你不给你的方块设置合适的的碰撞箱的话,你的方块内部空间会显得非常的暗。
可以看见这里最为特别的是调用了notSoild
方法,这个方法是告知Minecraft我们的方块不是一个「实心」方块,需要进行特殊的对待。之所以这么做,是因为Minecraft的世界里有非常多的方块,如果方块的每一个面都要渲染,包括那些被遮挡的面和遮挡起来的方块,那么会非常地耗费性能,所以出于优化的考虑,Minecraft只会渲染那些没有被遮挡起来的面。而noSoild
的作用就是告诉Minecraft,要渲染这个方块遮挡的那些面。
如果不开启这个就会出现这样效果。
如果对Minecraft方块渲染相关的内容感兴趣,可以阅读这篇博客文章,以及这个博客下其他文章(如果你打不开这个页面,说明你所在的国家或地区封锁了这个网站)。
在1.13之后,Minecraft加入「含水」这一个特性,对于原版的非完整方块很多都具有这个特性,而我们也可以给我们的方块加上这个特性。
首先你需要让你的方块实现IWaterLoggable
这个接口:
public class ObsidianFrame extends Block implements IWaterLoggable
接下来你必须要给你的方块,添加一个叫做waterlogged
的布尔类型的BlockState.
private static final BooleanProperty WATERLOGGED = BooleanProperty.create("waterlogged");
public ObsidianFrame() {
super(Properties.create(Material.ROCK).hardnessAndResistance(5).notSolid());
this.setDefaultState(this.stateContainer.getBaseState().with(WATERLOGGED, false));
}
@Override
protected void fillStateContainer(StateContainer.Builder<Block, BlockState> builder) {
builder.add(WATERLOGGED);
super.fillStateContainer(builder);
}
接下去实现如下方块,代码抄自原版的半砖。
@Nullable
@Override
public BlockState getStateForPlacement(BlockItemUseContext context) {
BlockPos blockpos = context.getPos();
BlockState blockstate = context.getWorld().getBlockState(blockpos);
if (blockstate.isIn(this)) {
return blockstate.with(WATERLOGGED, Boolean.valueOf(false));
} else {
FluidState fluidstate = context.getWorld().getFluidState(blockpos);
BlockState blockstate1 = this.getDefaultState().with(WATERLOGGED, Boolean.valueOf(fluidstate.getFluid() == Fluids.WATER));
return blockstate1;
}
}
public BlockState updatePostPlacement(BlockState stateIn, Direction facing, BlockState facingState, IWorld worldIn, BlockPos currentPos, BlockPos facingPos) {
if (stateIn.get(WATERLOGGED)) {
worldIn.getPendingFluidTicks().scheduleTick(currentPos, Fluids.WATER, Fluids.WATER.getTickRate(worldIn));
}
return super.updatePostPlacement(stateIn, facing, facingState, worldIn, currentPos, facingPos);
}
@Override
public FluidState getFluidState(BlockState state) {
return state.get(WATERLOGGED) ? Fluids.WATER.getStillFluidState(false) : super.getFluidState(state);
}
不过总体来说,含水方块的目前的设计更像是个hack,所以这些实现有点莫名其妙的……
注册方块:
public static final RegistryObject<Block> obsidianFrame = BLOCKS.register("obsidian_frame", ObsidianFrame::new);
注册物品:
public static RegistryObject<Item> obsidianFrame = ITEMS.register("obsidian_frame", () -> new BlockItem(BlockRegistry.obsidianFrame.get(), new Item.Properties().group(ModGroup.itemGroup)));
然后是方块状态文件obsidian_frame.json
:
{
"variants": {
"": { "model": "bosons:block/obsidian_frame" }
}
}
模型文件obsidian_frame.json
:
{
"texture_size": [64, 64],
"textures": {
"0": "boson:block/obsidian_frame",
"particle": "boson:block/obsidian_frame"
},
"elements": [
{
"from": [0, 0, 0],
"to": [16, 1, 16],
"faces": {
"north": {"uv": [4, 8.25, 8, 8.5], "texture": "#0"},
"east": {"uv": [0, 8.25, 4, 8.5], "texture": "#0"},
"south": {"uv": [12, 8.25, 16, 8.5], "texture": "#0"},
"west": {"uv": [8, 8.25, 12, 8.5], "texture": "#0"},
"up": {"uv": [8, 8.25, 4, 4.25], "texture": "#0"},
"down": {"uv": [12, 4.25, 8, 8.25], "texture": "#0"}
}
},
{
"from": [0, 15, 0],
"to": [16, 16, 16],
"faces": {
"north": {"uv": [4, 4, 8, 4.25], "texture": "#0"},
"east": {"uv": [0, 4, 4, 4.25], "texture": "#0"},
"south": {"uv": [12, 4, 16, 4.25], "texture": "#0"},
"west": {"uv": [8, 4, 12, 4.25], "texture": "#0"},
"up": {"uv": [8, 4, 4, 0], "texture": "#0"},
"down": {"uv": [12, 0, 8, 4], "texture": "#0"}
}
},
{
"from": [0, 1, 0],
"to": [1, 15, 1],
"faces": {
"north": {"uv": [2.25, 0.25, 2.5, 3.75], "texture": "#0"},
"east": {"uv": [2, 0.25, 2.25, 3.75], "texture": "#0"},
"south": {"uv": [2.75, 0.25, 3, 3.75], "texture": "#0"},
"west": {"uv": [2.5, 0.25, 2.75, 3.75], "texture": "#0"},
"up": {"uv": [2.5, 0.25, 2.25, 0], "texture": "#0"},
"down": {"uv": [2.75, 0, 2.5, 0.25], "texture": "#0"}
}
},
{
"from": [15, 1, 0],
"to": [16, 15, 1],
"faces": {
"north": {"uv": [1.25, 0.25, 1.5, 3.75], "texture": "#0"},
"east": {"uv": [1, 0.25, 1.25, 3.75], "texture": "#0"},
"south": {"uv": [1.75, 0.25, 2, 3.75], "texture": "#0"},
"west": {"uv": [1.5, 0.25, 1.75, 3.75], "texture": "#0"},
"up": {"uv": [1.5, 0.25, 1.25, 0], "texture": "#0"},
"down": {"uv": [1.75, 0, 1.5, 0.25], "texture": "#0"}
}
},
{
"from": [0, 1, 15],
"to": [1, 15, 16],
"faces": {
"north": {"uv": [0.25, 0.25, 0.5, 3.75], "texture": "#0"},
"east": {"uv": [0, 0.25, 0.25, 3.75], "texture": "#0"},
"south": {"uv": [0.75, 0.25, 1, 3.75], "texture": "#0"},
"west": {"uv": [0.5, 0.25, 0.75, 3.75], "texture": "#0"},
"up": {"uv": [0.5, 0.25, 0.25, 0], "texture": "#0"},
"down": {"uv": [0.75, 0, 0.5, 0.25], "texture": "#0"}
}
},
{
"from": [15, 1, 15],
"to": [16, 15, 16],
"faces": {
"north": {"uv": [3.25, 0.25, 3.5, 3.75], "texture": "#0"},
"east": {"uv": [3, 0.25, 3.25, 3.75], "texture": "#0"},
"south": {"uv": [3.75, 0.25, 4, 3.75], "texture": "#0"},
"west": {"uv": [3.5, 0.25, 3.75, 3.75], "texture": "#0"},
"up": {"uv": [3.5, 0.25, 3.25, 0], "texture": "#0"},
"down": {"uv": [3.75, 0, 3.5, 0.25], "texture": "#0"}
}
}
]
}
材质文件obsidian_frame.png
:
这里我的模型和材质都是用BlockBench制作的。
物品模型obsidian_frame.json
:
{
"parent": "boson:block/obsidian_frame"
}
打开游戏,你应该就能看见我们的黑曜石框架了。
以及含水的