Skip to content

8.6 示例

我们曾经在4.2演示过通过方块状态来实现水箱的水位,但是每个水位的方块状态就需要一个模型,那么如果我们想要更加自由的操作,就可以使用方块实体

水位数据

根据前一章学到的,首先在方块实体中添加一个字段waterLevel,然后使数据持久化,也就是load和saveAdditional方法

    public void load(@NotNull CompoundTag pTag) {
        super.load(pTag);
        this.waterLevel = getPersistentData().getInt("water_level");
    }

    protected void saveAdditional(@NotNull CompoundTag pTag) {
        getPersistentData().putInt("water_level", this.waterLevel);
        super.saveAdditional(pTag);
    }

交互

你直接右击交互的是BaseEntityBlock基础实体方块,这时候就需要覆写use方法来实现与方块实体的交互

    @SuppressWarnings("deprecation")
    @Override
    public @NotNull InteractionResult use(BlockState blockState, Level level, BlockPos blockPos, Player player, InteractionHand interactionHand, BlockHitResult result) {
        if (!level.isClientSide) {
            BlockEntity blockEntity = level.getBlockEntity(blockPos);
            return InteractionResult.SUCCESS;
        }
        return InteractionResult.CONSUME;
    }

可能注意到这里有一个@SuppressWarnings("deprecation")注释,这个注释是告诉IDE,不用管这个方法中的被注释为弃用的方法,要不然IDE就会标黄报警。

(题外话:天知道为什么麻将或者Forge要把这些方法标记为弃用,有的是真弃用了,有的还在用)

这时候,你就拿到了服务端上的方块实体对象。下面就是在if执行中,在服务端上对这个实例进行操作

            if (blockEntity instanceof TestTE TestTE) {
                ItemStack mainHandItem = player.getMainHandItem().copy();
                if (mainHandItem.is(Items.WATER_BUCKET)) {
                    testTE.waterLevel++;
                    player.setItemInHand(interactionHand, Items.BUCKET.getDefaultInstance());
                    level.sendBlockUpdated(blockPos, blockState, blockState, Block.UPDATE_CLIENTS);
                }
            }

这里首先检测手上物品是否是水桶,如果是,就使得方块实体中的waterLevel自增,并把玩家手上的物品设置为桶。最后再告诉mc,把服务端上方块实体的数据同步到客户端上。

下面就是额外内容,也就是渲染)

渲染

接下来的内容就是在渲染类,也就是TestBlockRenderer中。

首先,我们得拿到原版水的精灵图

    private static Optional<TextureAtlasSprite> getStillFluidSprite() {
        TextureAtlasSprite sprite = Minecraft.getInstance().getTextureAtlas(InventoryMenu.BLOCK_ATLAS).apply(WATER_STILL);
        return Optional.of(sprite).filter(s -> s.atlasLocation() != MissingTextureAtlasSprite.getLocation());
    }

然后在render方法里,通过写顶点的方式,渲染出一个水平面

        if (crucibleTE.waterLevel > 0) {
            getStillFluidSprite().ifPresent(fluidSprite -> {
                poseStack.pushPose();
                int waterLevel = crucibleTE.waterLevel;
                // long hue = (System.currentTimeMillis() / 10) % 360;
                // int[] rgb = HSLtoRGB(hue, 0.5f, 0.5f);
                int[] rgb = new int[]{255,255,255}
                float height = waterLevel / 16.0F + 0.15F;
                Matrix4f matrix4f = poseStack.last().pose();
                RenderType renderType = RenderType.translucent();
                VertexConsumer vertexConsumer = pBuffer.getBuffer(renderType);
                float r = rgb[0];
                float g = rgb[1];
                float b = rgb[2];
                float alpha = 1.0f;
                vertexConsumer.vertex(matrix4f, 0f, height, 0f)
                        .color(r, g, b, alpha)
                        .uv(fluidSprite.getU0(), fluidSprite.getV0())
                        .overlayCoords(OverlayTexture.NO_OVERLAY)
                        .uv2(pPackedLight).normal(0.0F, 1.0F, 0.0F).endVertex();

                vertexConsumer.vertex(matrix4f, 0f, height, 1f)
                        .color(r, g, b, alpha)
                        .uv(fluidSprite.getU0(), fluidSprite.getV1())
                        .overlayCoords(OverlayTexture.NO_OVERLAY)
                        .uv2(pPackedLight).normal(0.0F, 1.0F, 0.0F).endVertex();

                vertexConsumer.vertex(matrix4f, 1f, height, 1f)
                        .color(r, g, b, alpha)
                        .uv(fluidSprite.getU1(), fluidSprite.getV1())
                        .overlayCoords(OverlayTexture.NO_OVERLAY)
                        .uv2(pPackedLight).normal(0.0F, 1.0F, 0.0F).endVertex();

                vertexConsumer.vertex(matrix4f, 1f, height, 0f)
                        .color(r, g, b, alpha)
                        .uv(fluidSprite.getU1(), fluidSprite.getV0())
                        .overlayCoords(OverlayTexture.NO_OVERLAY)
                        .uv2(pPackedLight).normal(0.0F, 1.0F, 0.0F).endVertex();
                poseStack.popPose();
            });
        }

这段代码中,我们可以看到这个部分重复了四次,也就是这个面的四个顶点,四次操作中vertex方法的后三个参数为一个高度为height,边长为1的正方形的顶点坐标。

                vertexConsumer.vertex(matrix4f, 1f, height, 0f)
                        .color(r, g, b, alpha)
                        .uv(fluidSprite.getU1(), fluidSprite.getV0())
                        .overlayCoords(OverlayTexture.NO_OVERLAY)
                        .uv2(pPackedLight).normal(0.0F, 1.0F, 0.0F).endVertex();

.vertex开始写入的顶点数据,matrix4f是当前的位姿,后面三个参数是顶点坐标的x,y,z

.color写入rgba数据,也就是颜色和透明度

.uv写入这个顶点在对应精灵图上的uv坐标

.overlayCoord详情可以看这篇教程 OverlayTexture

.uv2写入光照数据

.normal写入法线数据

.endVertex封装顶点数据

那么我们分别写入四个坐标为(0,height,0) (0,height,1) (1,height,1) (1,height,0)的顶点,就在height高度,渲染出来一个白色的1*1的水面

那如果我们想要一个原版水的效果呢,就可以手动设置一下rgb数组中的数据了

私货

在上述代码中还有一个被注释掉的部分

    long hue = (System.currentTimeMillis() / 10) % 360;
    int[] rgb = HSLtoRGB(hue, 0.5f, 0.5f);

第一行是拿到系统时间然后取模360,来获得一个每3.6秒在0-360之间循环的值 下一步是把这个数字作为色相,现了 HSL 到 RGB 的转换,这样我们就有了一个rgb炫彩变色水面了

    public static int[] HSLtoRGB(float hue, float saturation, float lightness) {
        float c = (1 - Math.abs(2 * lightness - 1)) * saturation;
        float x = c * (1 - Math.abs((hue / 60) % 2 - 1));
        float m = lightness - c / 2;

        float r, g, b;
        if (hue < 60) {
            r = c;
            g = x;
            b = 0;
        } else if (hue < 120) {
            r = x;
            g = c;
            b = 0;
        } else if (hue < 180) {
            r = 0;
            g = c;
            b = x;
        } else if (hue < 240) {
            r = 0;
            g = x;
            b = c;
        } else if (hue < 300) {
            r = x;
            g = 0;
            b = c;
        } else {
            r = c;
            g = 0;
            b = x;
        }

        int red = (int) ((r + m) * 255);
        int green = (int) ((g + m) * 255);
        int blue = (int) ((b + m) * 255);

        return new int[]{red, green, blue};
    }