1.8 皮肤模型系统(1.8 Skin Model System)
基本信息
| 属性 | 值 |
|---|---|
| 类型 | 客户端渲染 Mixin 集合 |
| 实现范围 | ModelBiped、ModelBox、ModelSkeletonHead、SkinManager、AbstractClientPlayer、EntityPlayer |
| 适用端 | 客户端(MixinEnvironment.Side.CLIENT) |
| 关键 Mixin | MixinModelBiped、MixinModelBox、MixinModelSkull、MixinSkinManager、MixinAbstractClientPlayer、MixinEntityPlayer、MixinRenderingRegistry |
功能
将现代 Minecraft(1.8+)的皮肤/渲染特性移植到 1.7.10。系统涉及五层 mixin 协作:
- Entity 状态层 —— 给
EntityPlayer加ssb$slim标记(MixinEntityPlayer) - 皮肤资源层 ——
AbstractClientPlayer.getLocationSkin()重定向到 mod 内置皮肤(MixinAbstractClientPlayer) - 下载解析层 ——
SkinManager.func_152789_a用 64x64 解析器解析玩家皮肤 PNG(MixinSkinManager) - 模型构建层 —— 把 32x64 模型升级到 64x64、生成 slim/wide 手臂与 armwear(
MixinModelBiped、MixinModelBox、MixinModelSkull) - 渲染调度层 —— 在渲染实体前后切换手臂状态、隐藏头盔(
ClientEventHandler)
系统架构
启动顺序
SimpleSkinBackportEarlyMixins.getMixins() 在 mod 加载阶段按以下顺序向 Mixin 环境添加:
MixinEntityPlayer // 服务端/客户端通用
[CLIENT 专属]
MixinItemSkull // 注入 Roadhog360 skull 到创造栏
MixinAbstractClientPlayer// 默认皮肤替换 + 64x 解析器
MixinModelBiped // 64x 模型与 slim/wide 切换
MixinModelSkull // 骷髅头 hat layer
MixinModelBox // 透明 box(hat layer 用)
MixinSkinManager // 64x 解析器 + 皮肤 URL 重定向
MixinRenderingRegistry // 实体渲染器加载完成后升级模型
64x64 升级流程(ssb$set64x)
MixinModelBiped.ssb$set64x() 在 MixinRenderingRegistry.setup64xModels 中被调用 —— 遍历 RenderManager.instance.entityRenderMap,对每个 RenderPlayer 或 RenderBiped,若其模型是 INewBipedModel 且渲染器"复制玩家皮肤"或属于 EntityPlayer,则升级:
public void ssb$set64x() {
if (textureHeight == 32) {
textureHeight = 64;
Utils.changeTextureSize(this, textureWidth, 64);
ssb$setupBoxes(); // 生成 armwear/bodywear/legwear
ssb$createArmBoxes(); // 生成 wide/slim 手臂 variants
}
}
slim/wide 切换(ssb$setSlim)
ssb$setSlim(boolean) 在 ClientEventHandler.checkAndSetArmsState 中被调用:
public void ssb$setSlim(boolean slim) {
if (ssb$slimArms == null || ssb$wideArms == null) ssb$createArmBoxes();
ArmPair arms = slim ? ssb$slimArms : ssb$wideArms;
if (ConfigMain.oldSlimArms) {
// 1.15 前的半像素低偏移
bipedLeftArm.rotationPointY = ssb$armsRotationPointY + (slim ? 0.5F : 0.0F);
bipedRightArm.rotationPointY = ssb$armsRotationPointY + (slim ? 0.5F : 0.0F);
}
bipedLeftArm.displayList = arms.getLeftDisplayList();
bipedRightArm.displayList = arms.getRightDisplayList();
bipedLeftArm.cubeList = arms.getLeft();
bipedRightArm.cubeList = arms.getRight();
SmartRenderCompat.updateSmartRenderFields(bipedLeftArm, bipedRightArm);
if (textureHeight == 64 && ssb$slimArmwear != null && ssb$wideArmwear != null) {
ArmPair armwear = slim ? ssb$slimArmwear : ssb$wideArmwear;
ssb$bipedLeftArmwear.displayList = armwear.getLeftDisplayList();
ssb$bipedRightArmwear.displayList = armwear.getRightDisplayList();
ssb$bipedLeftArmwear.cubeList = armwear.getLeft();
ssb$bipedRightArmwear.cubeList = armwear.getRight();
}
}
手臂/衣着 ArMixine 缓存
MixinModelBiped 用 ArmPair 缓存每只手的两组 ModelBox:
| 字段 | 内容 |
|---|---|
ssb$wideArms |
ArmPair.of(bipedLeftArm, bipedRightArm) —— 原版 4px 宽 |
ssb$slimArms |
cloneModel(..., SLIM_LEFT_ARM) + cloneModel(..., SLIM_RIGHT_ARM) —— 3px 宽 |
ssb$wideArmwear |
ssb$bipedLeftArmwear + ssb$bipedRightArmwear(64x 纹理) |
ssb$slimArmwear |
上述两者的 slim 克隆 |
64x 皮肤解析(ImageBufferDownloadPlayerSkin)
MixinAbstractClientPlayer 用 use64xSkinParser() 把原版 ImageBufferDownload 替换为自定义类:
@Redirect(method = "getDownloadImageSkin",
at = @At(value = "NEW", target = "()Lnet/minecraft/client/renderer/ImageBufferDownload;"))
private static ImageBufferDownload use64xSkinParser() {
return new ImageBufferDownloadPlayerSkin(null, null);
}
ImageBufferDownloadPlayerSkin.parseUserSkin(BufferedImage) 把 32x64 老格式通过 8 个 drawImage 调用升级到 64x64:
源区域 (legacy 32x64) → 目标区域 (64x64)
(4,16)-(8,20) → (24,48)-(20,52) // 左臂顶层
(8,16)-(12,20) → (28,48)-(24,52) // 左臂顶层(镜像)
(8,20)-(12,32) → (20,52)-(16,64) // 左臂下半
(4,20)-(8,32) → (24,52)-(20,64) // 左臂下半(镜像)
(0,20)-(4,32) → (28,52)-(24,64) // 身体左半
(12,20)-(16,32) → (32,52)-(28,64) // 身体右半
(右臂/右腿完全对称)
随后调用 setAreaOpaque/setAreaTransparent 标记 alpha 区域,与原版 parseUserSkin 一致。
透明 Hat Layer(ITransparentBox + MixinModelBox)
MixinModelBox 给 ModelBox 加 ssb$isTransparent 标记 + ssb$boxSize 字段。render 方法被注入:
@Inject(method = "render", at = @At(value = "HEAD"))
private void doTransparency(Tessellator t, float scale, CallbackInfo ci) {
if (ssb$isTransparent()) {
GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
GL11.glEnable(GL11.GL_BLEND);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
}
}
@Inject(method = "render", at = @At(value = "TAIL"))
private void finishTransparency(...) {
if (ssb$isTransparent()) GL11.glPopAttrib();
}
调用 Utils.setBoxTransparent(box)(递归至 setAllBoxesTransparent)即可把整套 ModelRenderer 设为透明。
Skin URL 重定向(MixinSkinManager.func_152789_a)
玩家皮肤下载到本地后,需要放在 simpleskinbackport 命名空间(避免与原版 minecraft:textures/entity/player/... 冲突):
@WrapOperation(method = "func_152789_a",
at = @At(value = "NEW", target = "(Ljava/lang/String;)Lnet/minecraft/util/ResourceLocation;"))
private ResourceLocation changeDownloadLocation(String p_i1293_1_, Operation<ResourceLocation> original,
@Local MinecraftProfileTexture.Type type,
@Local SkinManager.SkinAvailableCallback callback) {
if (Utils.isPlayer(type, callback)) {
return new ResourceLocation(MOD_ID, p_i1293_1_); // 用 modid 而非 minecraft
}
return original.call(p_i1293_1_);
}
随后 injectCallbackSkinCheck 在下载完成时调用 Utils.setSlimFromMetadata,根据 textures.SKIN.metadata.model = "slim" 把玩家标记为 slim。
头盔/穿戴渲染自动隐藏(ClientEventHandler)
@SubscribeEvent
public void onRenderEntity(RenderLivingEvent.Pre event) {
checkAndSetArmsState(event.entity, event.renderer); // 切手臂
ItemStack helmet = event.entity.getEquipmentInSlot(4);
if (helmet != null && isHead(helmet.getItem())) {
headwear.showModel = false; // 戴头盔就关掉 hat layer
hidingHeadwear = true;
}
}
onPostRenderEntity 在渲染后恢复 showModel。isHead 同时识别 ItemSkull 与 HEE 的 chylex.hee.item.ItemEndermanHead(后者不继承 ItemSkull)。
第三方 mod 集成
| Mod | 集成点 | Mixin |
|---|---|---|
| Twilight Forest | RenderTFGiant.getEntityTexture |
MixinRenderTFGiant(late mixin) |
| Botania | ClientProxy.initRenderers(去 skulls 覆盖)+ RenderTileSkullOverride.render(去 model override + 换 Steve 贴图) |
MixinClientProxy、MixinRenderTileSkullOverride(late mixin) |
| HEE | ItemEndermanHead 通过 isHead 字符串比对 |
ClientEventHandler |
| SmartRender | ModelRotationRenderer.UpdateLocals 反射调用 |
SmartRenderCompat |
| OptiFine | displayList = 0 检查规避 + 不破坏 display list 缓存 |
Utils.createDisplaylistFor |
数值
| 数值 | 值 | 来源 |
|---|---|---|
| slim 手臂宽度 | 3px | BoxTransformType.SLIM_RIGHT_ARM 中 boxMaxX -= 1 |
| wide 手臂宽度 | 4px | 原版 |
oldSlimArms 偏移 |
+0.5F |
bipedLeftArm.rotationPointY += 0.5F |
| 解析器目标尺寸 | 64x64 | imageWidth = imageHeight = 64 |
| 模型纹理(升级后) | 64x64 | textureHeight = 64 |
| ArMixine 缓存数 | 4(wideArms/slimArms/wideArmwear/slimArmwear) | MixinModelBiped 字段 |
| ArMixine 子组件数 | 2(left/right) | ArmPair 字段 |
| hat layer box 厚度附加 | +0.25F |
BoxTransformType.HAT:size += 0.25F |
相关条目
- 主配置 -
defaultSkinSet、oldSlimArms配置 - Twilight Forest 巨人兼容 - 巨人皮肤 mixin
- Botania 兼容 - Doppleganger + Gaia Guardian 头兼容
- SmartRender 兼容 - SmartRender reflection 反射更新