[1.21.3] Rendering: Add State Extraction hook, custom state fields API
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
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ả
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:
RenderPlayerEventis not hooked to anything in Forge 53.0.9. It is instead calling anotherRenderLivingEvent. 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
- Không có Dockerfile hay tệp Docker Compose
- Không có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- 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.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của MinecraftForge/MinecraftForge
-
Triage
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 64/100
MinecraftForge/MinecraftForge#10518 · 2 bình luận ·
-
Shrink add onĐang mở
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 15/100
MinecraftForge/MinecraftForge#10901 ·
-
Triage
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 48/100
MinecraftForge/MinecraftForge#10861 · 9 bình luận ·
-
[INFO] The Big TODO ListĐang mở
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 15/100
MinecraftForge/MinecraftForge#10830 ·
-
Triage
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 35/100
MinecraftForge/MinecraftForge#10743 · 1 bình luận ·
Tất cả issue của MinecraftForge/MinecraftForge
Issue tương tự
-
False Negative: InnerTypeLastCheck does not report members declared after inner enums or annotationsĐang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
checkstyle/checkstyle#22027 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
bug follow-up lane:auto
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 85/100
Sanne/incus-spawn#1497 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 85/100
Maintainer thường phản hồi trong vòng 1 ngày
-
[CI] NettyClientTest.testServerDisconnection may fail with DisconnectExceptionCó thể đã có người làm @yangshangqing95 đã nhận hôm nay. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 86/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100