Рендеры сущностей блока
Иногда использования формата моделей Minecraft недостаточно. Если вам нужно добавить динамический рендеринг к визуальным эффектам блока, вам нужно использовать BlockEntityRenderer.
Например, давайте сделаем так, чтобы блок Counter из статьи Block Entities показывал кол-во кликов на своей верхней стороне.
Создание BlockEntityRenderer
Рендеринг блоков, сущностей использует систему submit/render, в которой вы сначала отправляете данные, необходимые для рендеринга объекта на экран, а затем игра рендерит объект, используя его отправленное состояние.
При создании BlockEntityRenderer для CounterBlockEntity важно поместить класс в соответствующий набор исходных текстов, например src/client/, если ваш проект использует раздельные наборы исходных текстов для клиента и сервера. Обращение к классам, связанным с рендерингом, непосредственно в наборе исходных текстов src/main/ небезопасно, поскольку эти классы могут быть загружены на сервере.
Во-первых, нам нужно создать BlockEntityRenderState для нашего CounterBlockEntity, чтобы хранить данные, которые будут использоваться для рендеринга. В этом случае нам нужно, чтобы клики были доступны во время рендеринга.
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
Затем мы создадим BlockEntityRenderer для нашего CounterBlockEntity.
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 или Font. Также, включение такого конструктора позволяет использовать конструктор в качестве функционального интерфейса 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
Мы переопределим несколько методов, чтобы настроить состояние рендера, а также метод submit, в котором будет реализована логика рендеринга.
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.
Рисование на блоках
Теперь, когда у нас есть рендерер, мы можем рисовать. Метод submit вызывается каждый кадр — именно здесь происходит вся магия рендеринга.
Передвижение
Сначала нам нужно сместить и повернуть текст так, чтобы он находился на верхней стороне блока.
INFO
Как следует из названия, PoseStack — это стек, то есть вы можете добавлять (push) и удалять (pop) преобразования. Хорошее правило — делать push в начале метода submit и pop в конце, чтобы рендер одного блока не влиял на другие.
Больше информации о PoseStack можно найти в статье Основы рендеринга.
Чтобы упростить понимание необходимых смещений и вращений, давайте визуализируем их. На этом изображении зелёный блок показывает, где будет отрисован текст — по умолчанию в самой дальней нижней левой точке блока:

Сначала нужно сдвинуть текст на половину блока по осям X и Z, а затем поднять его до верхней грани блока по оси Y:

Это делается одним вызовом translate:
java
matrices.translate(0.5, 1, 0.5);1
Смещение выполнено, остаются вращение и масштабирование.
По умолчанию текст рисуется в плоскости XY, поэтому нужно повернуть его на 90 градусов вокруг оси X, чтобы он был направлен вверх в плоскости XZ:

У PoseStack нет функции rotate, вместо этого нужно использовать mulPose и Axis.XP:
java
matrices.mulPose(Axis.XP.rotationDegrees(90));1
Теперь текст находится в правильной позиции, но он слишком большой. BlockEntityRenderer отображает весь блок в кубе [-0.5, 0.5], тогда как Font использует координаты Y в диапазоне [0, 9]. Следовательно, его нужно уменьшить в 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, передаваемый в конструктор нашего рендерера, содержит Font, который можно использовать для измерения текста (width), что полезно для центрирования.
Чтобы отрисовать текст, мы будем отправлять необходимые данные в очередь рендеринга. Поскольку мы рисуем текст, можно использовать метод submitText, предоставляемый через экземпляр SubmitNodeCollector, переданный в метод submit.
java
String text = state.getClicks() + "";
float width = font.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; PoseStack, описывающий, как его следует преобразовать.
И после всей этой работы вот результат:


