User Tools

Site Tools


zh_cn:tutorial:blockentityrenderers

使用方块实体渲染器动态渲染方块和物品

这是本教程的 1.15 以上版本。对于 1.14 版本,请参见使用方块实体渲染器动态渲染方块和物品(1.14)

阅读本教程之前,请确保您已添加方块实体

介绍

方块本身并不是那么有趣,只是在某个位置和某个大小保持静止直到损坏。我们可以使用方块实体渲染器(block entity renderer)更加动态地渲染与方块实体有关的物品和方块——在不同的位置、以不同的大小渲染多个物品。

例子

在本教程中,我们将通过向其添加 BlockEntityRenderer 来构建所创建的方块实体。渲染器将显示一个唱片机,漂浮在方块上方,上下移动并旋转。

我们需要做的第一件事是创建我们的 BlockEntityRenderer 类:

@Environment(EnvType.CLIENT)
public class DemoBlockEntityRenderer implements BlockEntityRenderer<DemoBlockEntity> {
    // 唱片机物品堆
    private static ItemStack stack = new ItemStack(Items.JUKEBOX, 1);
 
    public DemoBlockEntityRenderer(BlockEntityRendererFactory.Context ctx) {}
 
    @Override
    public void render(DemoBlockEntity blockEntity, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay) {
    }
}

我们将需要注册我们的 BlockEntityRenderer,但仅针对客户端。在单人游戏设置中这无关紧要,因为服务器与客户端在同一进程中运行。但是,在多人游戏设置中,服务器在与客户端不同的进程中运行,服务器代码没有 BlockEntityRenderer 的概念,因此不接受注册。要仅为客户端运行初始化代码,我们需要设置一个 client 入口点(entrypoint)。

在实现 ClientModInitializer 的主类旁边创建一个新类:

@Environment(EnvType.CLIENT)
public class ExampleModClient implements ClientModInitializer {
    @Override
    public void onInitializeClient() {
        // 这里我们放置只在客户端注册的代码
    }
}

将此类设置为 fabric.mod.json 中的“client”入口点(根据需要修改路径):

"fabric.mod.json"
"entrypoints": {
    [...]
    "client": [
      {
        "value": "net.fabricmc.example.ExampleModClient"
      }
    ]
}    

在我们的 ClientModInitializer 中注册 BlockEntityRenderer

    @Override
    public void onInitializeClient() {
        BlockEntityRendererRegistry.register(DEMO_BLOCK_ENTITY, DemoBlockEntityRenderer::new);
        //不行就试试BlockEntityRendererRegistry.INSTANCE.register(DEMO_BLOCK_ENTITY, DemoBlockEntityRenderer::new);
    }

我们重写在每一帧都会被调用的 render 方法,我们将在其中进行渲染——对于初学者,请调用 matrices.push();,这在进行GL调用时是必需的(我们将在紧接之后进行):

    public void render(DemoBlockEntity blockEntity, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay) {
       matrices.push();
    }

然后,我们对唱片机进行平移(matrices.translate)和旋转(matrices.multiply)。平移分为两部分:将其平移到高于方块中心的 0.5、1.25 和 0.5。第二部分是变化的部分:y 值的偏移量。偏移量是任何给定帧的物品高度。每次我们都要重新计算,因为我们希望它可以动画上下跳跃。我们通过以下方式计算:

  • 获取当前世界时间,该时间会随着时间而变化。
  • 添加部分刻。(部分刻是一个小数值,代表最后一次完整刻和现在之间的时间间隔。我们使用此方法,要不然动画会抖动,因为每秒的刻数少于每秒的帧数。)
  • 将其除以8以减慢运动速度。
  • 以正弦值产生介于-1和1之间的值,类似于正弦波
  • 将其除以4可垂直压缩正弦波,这样该物品不会过度上下移动。
    public void render(DemoBlockEntity blockEntity, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay) {
        [...]
        // 计算当前y值的偏移
        double offset = Math.sin((blockEntity.getWorld().getTime() + tickDelta) / 8.0) / 4.0;
        // 移动物品
        matrices.translate(0.5, 1.25 + offset, 0.5);
 
        // 旋转物品
        matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees((blockEntity.getWorld().getTime() + tickDelta) * 4));
    }

最后,我们将获得 Minecraft 的 ItemRenderer,并使用 renderItem 渲染唱片机物品。我们还将 ModelTransformation.Type.GROUND 传递给 renderItem,因为我们希望有类似与物品置于地上的效果。尝试对此值进行试验,看看会发生什么(这是一个枚举)。在这些 GL 调用之后,我们还需要调用matrices.pop();

    public void render(DemoBlockEntity blockEntity, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay) {
        [...]
        MinecraftClient.getInstance().getItemRenderer().renderItem(stack, ModelTransformation.Mode.GROUND, light, overlay, matrices, vertexConsumers, 0);
 
        // GL 调用之后的必要调用
        matrices.pop();
    }

您现在就可以尝试新创建的方块实体渲染器。但是,如果您没有使方块透明,您会发现有些不对劲——这个浮动的方块,即唱片机,是黑色的!这是因为默认情况下,无论您在方块实体中渲染什么,都将接收该方块实体所在位置的光照度。所以浮动的方块接收这个不透明方块内部的光照,这意味着接收不到光。为了解决此问题,我们会让Minecraft接收方块实体上方位置的光照强度。

要获取光照,我们在方块实体上方的位置调用 WorldRenderer#getLightmapCoordinates();,并在 renderItem() 使用这个光照。

    @Override
    public void render(DemoBlockEntity blockEntity, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay) {
        [...]
 
        int lightAbove = WorldRenderer.getLightmapCoordinates(blockEntity.getWorld(), blockEntity.getPos().up());
        MinecraftClient.getInstance().getItemRenderer().renderItem(stack, ModelTransformation.Mode.GROUND, lightAbove, OverlayTexture.DEFAULT_UV, matrices, vertexConsumers, 0);
 
        [...]
    }

唱片机现在应该得到了适当的光照。

根据方块实体数据进行渲染

有时候你需要根据方块实体的数据(nbt)进行渲染,结果发现这些数据全是空的,尽管通过 /data get block 命令可以正常访问数据。这是因为你没有将服务器的数据同步至客户端。参见将服务器数据同步至客户端

zh_cn/tutorial/blockentityrenderers.txt · Last modified: 2023/08/29 10:31 by wjz_p