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 协作:

  1. Entity 状态层 —— 给 EntityPlayer 加 ssb$slim 标记(MixinEntityPlayer)
  2. 皮肤资源层 —— AbstractClientPlayer.getLocationSkin() 重定向到 mod 内置皮肤(MixinAbstractClientPlayer)
  3. 下载解析层 —— SkinManager.func_152789_a 用 64x64 解析器解析玩家皮肤 PNG(MixinSkinManager)
  4. 模型构建层 —— 把 32x64 模型升级到 64x64、生成 slim/wide 手臂与 armwear(MixinModelBiped、MixinModelBox、MixinModelSkull)
  5. 渲染调度层 —— 在渲染实体前后切换手臂状态、隐藏头盔(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

相关条目