非实心方块与自定义模型

在这一节中,我们将创建一个有着特殊外形且是透明的方块,这里我们以黑曜石框架举例。

首先我们创建一个叫做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。最后我们用VoxelShapesor方法将这些东西拼在了一起。VoxelShapes 下还有很多好用的空间操作方法,请自行选用。如果你不给你的方块设置合适的的碰撞箱的话,你的方块内部空间会显得非常的暗。

可以看见这里最为特别的是调用了notSoild方法,这个方法是告知Minecraft我们的方块不是一个「实心」方块,需要进行特殊的对待。之所以这么做,是因为Minecraft的世界里有非常多的方块,如果方块的每一个面都要渲染,包括那些被遮挡的面和遮挡起来的方块,那么会非常地耗费性能,所以出于优化的考虑,Minecraft只会渲染那些没有被遮挡起来的面。而noSoild的作用就是告诉Minecraft,要渲染这个方块遮挡的那些面。

如果不开启这个就会出现这样效果。

image-20200428204119348

如果对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

obsidian_frame

这里我的模型和材质都是用BlockBench制作的。

物品模型obsidian_frame.json:

{
  "parent": "boson:block/obsidian_frame"
}

打开游戏,你应该就能看见我们的黑曜石框架了。

image-20200428214022814

以及含水的

image-20201017113953896

源代码