Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

[1.21.3] Rendering: Add State Extraction hook, custom state fields API

Đang mở
#10,165 3 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Đánh giá

Độ khó
5/5
Thời gian dự kiến
Hơn một tuần
Mức phù hợp với người mới
25/100
Loại issue
Tính năng
Độ rõ ràng
Cần làm rõ
Mức độ hoạt động
Đình trệ
Công nghệ
java
Lĩnh vực
game-dev

Hướng nghiên cứu

Bắt đầu bằng việc xem xét EntityRenderer#createRenderState và extractRenderState cùng với EntityRenderState và các luồng bị ảnh hưởng của RenderLivingEvent và RenderPlayerEvent. So sánh extraction hook được đề xuất với API custom render-state fields, sau đó xác nhận phạm vi mà các maintainer chấp nhận. Để được xem là hoàn tất, cần có thiết kế và kế hoạch triển khai đã được thống nhất trước khi có thể xác định phạm vi code.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

1.21 Feature

I'm making this first an issue to discuss if the following is ok/relevant to be considered into Forge.
I'm willing to convert it into a PR if all is fine.

TL;DR: A new ExctractRenderStateEvent.Pre/Post should be implemented to compensate for the lack of an entity reference in the reformed RenderLivingEvent, as well as to adapt for Minecraft's new render states abstraction.

There is also the possibility to provide an API for modders to add custom rendering state fields to different entities and states.


ExctractRenderStateEvent

In 1.21.2, a refactoring change has made it so that rendering-related fields in EntityRenderers are now separated into another data class - EntityRenderState.
Hence, the new EntityRenderer#render method is now defined with the following signature:

public void render(S state, PoseStack poseStack, MultiBufferSource multiBufferSource, int packedLight);

In this form, the entity is now omitted from context.
There is no way, as of current, to trace back to the original entity that possesses the renderer OR its state (as far as I'm aware).

This consequently affects RenderLivingEvent - which no longer passes an entity reference.

Here is an example method from 1.21.1 that used this reference, for instance:

// Handle block instrument arm pose
@SubscribeEvent
public static void prePlayerRenderEvent(final RenderPlayerEvent.Pre event) {
    final Player player = event.getEntity();

    if (!(InstrumentOpenProvider.isOpen(player) && !InstrumentOpenProvider.isItem(player)))
        return;


    final Block block = player.level().getBlockState(InstrumentOpenProvider.getBlockPos(player)).getBlock();
    if (!(block instanceof AbstractInstrumentBlock instrumentBlock))
        return;

    final PlayerModel<AbstractClientPlayer> model = event.getRenderer().getModel();
    model.leftArmPose = model.rightArmPose = instrumentBlock.getClientBlockArmPose();
}

This method operates a bunch of checks on the player, that if passed - applies a certain ArmPose to them before rendering is applied.

This is now impossible.

The solution I came with (diff) was to hook an event to the end of the EntityRenderer#extractRenderState methods - where the initialization of the state's fields are to be completed, and we can manipulate them so as to not be overwritten or be too late into rendering.

Since the way this method works is by chaining a bunch of overrides-calls-super, we can't determine where the method really ends - so I placed the hook at the end of the EntityRenderer#createRenderState, which is basically the wrapper method mostly referred to for this operation anyways.

Also added Pre/Post phases, just because we can.

Custom Render State Fields API

The approach to add one's own render state field and then use it for rendering without an entity reference aligns with that of the new Minecraft API.

This was my initial solution to the problem as well.

My implementation (same diff) involves defining the following class, to identify custom fields:

/**
 * Represents a custom {@link EntityRenderState} field.
 * @param initValSupplier Called on {@link EntityRenderer#extractRenderState}. Supplies the initial value to be provided to this field.
 * @param entityType The entity type to have the field attached to
 * @param renderStateType The render state type to have this field attached to
 * @param <T> The field type
 */
@OnlyIn(Dist.CLIENT)
public record CustomRenderStateField<T>(
    // For filtering:
    Class<? extends Entity> entityType,
    Class<? extends EntityRenderState> renderStateType,
    RenderStateSupplier<T> initValSupplier
) {
    @FunctionalInterface
    public static interface RenderStateSupplier<T> {
        /**
         * Supplies a value to be provided to a render state field.
         * @param entity The entity having this field attached to
         * @param state The state that the field will get attached to
         * @param packedLight
         * @return The value to be put in the state field
         */
        T get(EntityRenderer<?, ?> entityRenderer,
              Entity entity,
              EntityRenderState state,
              float packedLight);
    }
}

Which would then be put into a client-only registry - mine uses a simple HashMap:

private static final HashMap<ResourceLocation, CustomRenderStateField<?>> CUSTOM_RENDER_STATE_FIELDS = new HashMap<>();

Example custom field:

public static final CustomRenderStateField<Optional<ArmPose>> INSTRUMENT_BLOCK_PLAYED = CustomRenderStateFieldRegistry.register(
    GInstrumentMod.loc("instrument_block_played"),
    new CustomRenderStateField<>(
        Player.class,
        GIRenderStates::extractInstrumentBlockPlayingState
    )
);

And at the end of vanilla's render state initialization (aka where the ExctractRenderStateEvent.Post will be hooked), we can perform the following operation:

public static <E extends Entity, S extends EntityRenderState> void initFields(EntityRenderer<E, S> entityRenderer,
                                                                              E entity,
                                                                              S state,
                                                                              float packedLight) {
    CUSTOM_RENDER_STATE_FIELDS.values().stream()
        .filter((field) ->
            field.entityType().isInstance(entity) && field.renderStateType().isInstance(state)
        )
        .forEach((field) ->
            state.setCustomField(
                field,
                field.initValSupplier().get(entityRenderer, entity, state, packedLight)
            )
        );
}

Where get/setCustomField are new methods patched into the EntityRenderState class:

private final HashMap<CustomRenderStateField<?>, Object> customStateFields = new HashMap<>();

@SuppressWarnings("unchecked")
public <T> T getCustomField(CustomRenderStateField<T> field) {
    return (T) customStateFields.get(field);
}
public <T> void setCustomField(CustomRenderStateField<T> field, T value) {
    customStateFields.put(field, value);
}

This would allow us to define and use custom render fields, and access a set of defined entity-related data.

Example usage:

@SubscribeEvent
// Handle block instrument arm pose
public static void onLivingRenders(final RenderLivingEvent.Pre<?, ?, ?> event) {
    final PlayerRenderState renderState = (PlayerRenderState) event.getState();
    
    renderState.getCustomField(GIRenderStates.INSTRUMENT_BLOCK_PLAYED)
        .ifPresent((pose) -> {
            renderState.mainHandState.pose = renderState.offhandState.pose = pose;
            renderState.mainHandState.isEmpty = renderState.offhandState.isEmpty = false;
        });
}

Note: RenderPlayerEvent is not hooked to anything in Forge 53.0.9. It is instead calling another RenderLivingEvent. Will push PR to fix.

Ngôn ngữ chính
Java
Star
7.8k
Fork
2.8k
Merge trung bình
2 ngày 10 giờ
Pull request đã merge (30 ngày)
5

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của MinecraftForge/MinecraftForge

Tất cả issue của MinecraftForge/MinecraftForge

Issue tương tự

Thêm issue về Java

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.