有的时候,用 Minecraft 自带的模型格式和渲染器并不足够。 如果需要为你的方块的视觉效果添加动态渲染,则需要使用 BlockEntityRenderer。
举个例子,让我们来制作一个在 方块实体 文章中出现的 Counter Block,这个方块会在方块顶部显示点击次数。
创建一个 BlockEntityRenderer
方块实体渲染使用提交/渲染系统,首先将渲染对象所需的数据提交到屏幕,然后游戏使用其提交状态渲染对象。
在为 CounterBlockEntity 创建 BlockEntityRenderer 时,如果您的项目对客户端和服务器端使用了不同的源代码集,则需要确保将该渲染器类放置于对应的源代码集中,例如客户端相关的类应放在 src/client/ 目录下。 直接访问 src/main/ 源代码集中与渲染相关的类并不安全,因为这些类可能已在服务器上加载。
首先,我们需要为 CounterBlockEntity 创建一个 BlockEntityRenderState 来保存将用于渲染的数据。 在这种情况下,我们需要 clicks 在渲染期间可用。
java
public class CounterBlockEntityRenderState extends BlockEntityRenderState {
private int clicks = 0;
public int getClicks() {
return clicks;
}
public void setClicks(int clicks) {
this.clicks = clicks;
}
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
然后我们为 CounterBlockEntity 创建一个 BlockEntityRenderer。
java
public class CounterBlockEntityRenderer implements BlockEntityRenderer<CounterBlockEntity, CounterBlockEntityRenderState> {
public CounterBlockEntityRenderer(BlockEntityRendererProvider.Context context) {
}
@Override
public CounterBlockEntityRenderState createRenderState() {
return new CounterBlockEntityRenderState();
}
@Override
public void extractRenderState(CounterBlockEntity blockEntity, CounterBlockEntityRenderState state, float tickProgress, Vec3 cameraPos, @Nullable ModelFeatureRenderer.CrumblingOverlay crumblingOverlay) {
}
@Override
public void submit(CounterBlockEntityRenderState state, PoseStack matrices, SubmitNodeCollector queue, CameraRenderState cameraState) {
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
新类有一个构造函数,以 BlockEntityRendererProvider.Context 作为参数。 Context 有几个非常有用的渲染辅助工具,比如 ItemRenderer 或 TextRenderer。 此外,通过包含这样一个构造函数,就可以将该构造函数用作 BlockEntityRendererProvider 函数式接口本身:
java
public class ExampleModBlockEntityRenderer implements ClientModInitializer {
@Override
public void onInitializeClient() {
BlockEntityRenderers.register(ModBlockEntities.COUNTER_BLOCK_ENTITY, CounterBlockEntityRenderer::new);
}
}1
2
3
4
5
6
2
3
4
5
6
我们将重写一些方法来设置渲染状态以及设置渲染逻辑的 render 方法。
createRenderState 可以用于初始化渲染状态。
java
@Override
public CounterBlockEntityRenderState createRenderState() {
return new CounterBlockEntityRenderState();
}1
2
3
4
2
3
4
extractRenderState 可以用于使用实体数据更新渲染状态。
java
@Override
public void extractRenderState(CounterBlockEntity blockEntity, CounterBlockEntityRenderState state, float tickProgress, Vec3 cameraPos, @Nullable ModelFeatureRenderer.CrumblingOverlay crumblingOverlay) {
// :::1
BlockEntityRenderer.super.extractRenderState(blockEntity, state, tickProgress, cameraPos, crumblingOverlay);
state.setClicks(blockEntity.getClicks());
// :::1
}1
2
3
4
5
6
7
2
3
4
5
6
7
你应该在 ClientModInitializer 类中注册你的方块实体渲染器。
BlockEntityRenderers 是一个注册表,它将每个具有自定义渲染代码的 BlockEntityType 映射到其各自的 BlockEntityRenderer。
在方块上绘制
现在我们有了渲染器,就可以开始绘制了。 render 方法在每一帧都会被调用,这就是渲染魔法发生的地方。
四处移动
首先,我们需要偏移和旋转文本,使其位于方块的顶部。
INFO
顾名思义,PoseStack 是一个_堆栈_,这意味着你可以压入和弹出变换。 一个好的经验法则是在 render 方法开始时压入一个新的方块,并在结束时弹出,这样一个方块的渲染就不会影响到其他方块。
有关 PoseStack 的更多信息,请参阅基本渲染概念文章。
为了更容易理解所需的平移和旋转,不妨将其可视化。 在该图中,绿色方块是绘制文本的位置,默认情况下位于方块的最左下角:

因此,首先我们需要在 X 轴和 Z 轴上将文本移动到方块的一半,然后在 Y 轴上将其移动到方块的顶部:

这些都可以以单个 translate 调用来实现:
java
matrices.translate(0.5, 1, 0.5);1
我们已经完成了 平移,接下来是 旋转 和 缩放。
默认情况下,文字会在 XY 平面上渲染,所以我们需要将其绕 X 轴旋转 90 度,让他面向上方的 XZ 平面:

PoseStack 没有 rotate 函数,我们需要使用 multiply 和 Axis.XP:
java
matrices.multiply(Axis.XP.rotationDegrees(90));1
那么现在的文字就在正确的位置了,但是文字现在太大了。 BlockEntityRenderer 映射整个方块到一个 [-0.5, 0.5] 的立方体,而 TextRenderer 使用 [0, 9] 的 Y 坐标。 因此,我们需要将其缩小到原来的 1/18:
java
matrices.scale(1/18f, 1/18f, 1/18f);1
那么,我们整个的变换看起来就像这样:
java
matrices.pushPose();
matrices.translate(0.5, 1, 0.5);
matrices.mulPose(Axis.XP.rotationDegrees(90));
matrices.scale(1/18f, 1/18f, 1/18f);1
2
3
4
2
3
4
绘制文本
如前所述,传递到渲染器构造函数的 Context 包含一个 TextRenderer,我们可以用它来测量文本(width),这对于居中很有用。
为了绘制文本,我们将向渲染队列提交必要的数据。 由于我们正在绘制一些文本,因此我们可以使用通过传递到 render 方法的 OrderedRenderCommandQueue 实例提供的 SubmitText 方法。
java
String text = state.getClicks() + "";
float width = textRenderer.width(text);
// draw the text. params:
// text, x, y, color, ordered text, shadow, text layer type, light, color, background color, outline color
queue.submitText(
matrices,
-width / 2, -4f,
Component.literal(text).getVisualOrderText(),
false,
Font.DisplayMode.SEE_THROUGH,
state.lightCoords,
0xffffffff,
0,
0
);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
submitText 方法接受许多的参数,但最重要的几个是:
- 要绘制的
FormattedCharSequence; - 文字的
X和Y坐标; - 文字的 RGB 颜色
color值; - 描述其变换方式的
Matrix4f矩阵(要从PoseStack中获取该矩阵,我们可以使用.last().pose()来获取最顶层条目的Matrix4f矩阵)。
经过我们的努力,这就是最终结果:


