跳到正文
0:00 / 0:00
已成形

Spine、粒子特效与运行时材质

5 分钟

AI 摘要

系统梳理Unity中Spine换肤流程、灰度实现、粒子透明度控制及材质实例化陷阱,澄清GPU纹理内存与磁盘文件的区别,并揭示.unitypackage本质为可动态修改的JSON。

特效与角色表现层是「美术资源 + 运行时改动」交汇的地方,稍不留神就会改到共享资源或算错内存。这篇整理 Spine、粒子、材质与纹理四块经验。

一、Spine 换肤的正确流程

设置皮肤后,必须把插槽重置为 setup pose 并重新 Apply,否则可能出现插槽错位:

// 使用 SetSkin 并重置姿势
_skeletonAnimation.skeleton.SetSkin("skin-name");
_skeletonAnimation.skeleton.SetSlotsToSetupPose();
_skeletonAnimation.AnimationState.Apply(_skeletonAnimation.Skeleton);

三步缺一不可:换肤 → 重置到 setup pose → 重新应用。

二、SkeletonGraphic 的灰度(置灰)方案

给 SkeletonGraphic 做灰度,有几种由简到繁的做法:

  1. 直接用颜色近似:skeletonGraphic.color = Color.gray;,简单但对细节控制有限。
  2. 自定义材质 + 着色器:最灵活,能精确控制灰度算法。
  3. Spine 自带的灰度效果 / 逐插槽染色:
skeletonGraphic.color = Color.gray;

var skeleton = skeletonGraphic.Skeleton;
if (skeleton != null)
{
    // 设置所有插槽为灰色
    foreach (var slot in skeleton.Slots)
        slot.SetColor(Color.gray);
}
  1. UI Mask + 颜色叠加:在 Canvas 中叠一层灰色半透明 Image 覆盖:
overlayPanel.SetActive(true);

// 或按 alpha 混合
skeletonGraphic.color = new Color(0.3f, 0.3f, 0.3f, skeletonGraphic.color.a);

选择依据:整块置灰用颜色即可;需要保留原色细节、只降低饱和度,就用自定义 Shader。

三、粒子透明度渐隐渐显

修改粒子透明度一般有两种方法。

方法一:改粒子的 startColor 的 alpha(推荐,作用于新生成的粒子)

记得先 Stop() + Clear(),再设 duration、playOnAwake:

particleSystem.Stop();
particleSystem.Clear();

var main = particleSystem.main;
Color c = main.startColor.color;
c.a = 0;
main.startColor = new ParticleSystem.MinMaxGradient(c);
main.playOnAwake = false;
main.duration = effectShowTime;

配合 DOTween 渐显:

// 这里的 2f 是时长,1f 是目标透明度
DOTween.To(
    () => ps.main.startColor.color.a,
    x =>
    {
        var main = ps.main;
        Color c = main.startColor.color;
        c.a = x;
        main.startColor = new ParticleSystem.MinMaxGradient(c);
    },
    1f,
    2f);

方法二:改材质的透明度(Renderer 模块)

注意材质的 Surface Type 必须是 Transparent,而不是 Opaque;如果用的是旧版 Shader(如 Particles/Standard Unlit),Rendering Mode 要选 Fade 或 Transparent。

Material mat = ps.GetComponent<ParticleSystemRenderer>().material;
Color matCol = mat.GetColor("_Color"); // 属性名取决于 Shader,常见 _Color / _BaseColor
matCol.a = newAlpha;
mat.SetColor("_Color", matCol);

四、运行时材质:谁实例化,谁不实例化

这是一个非常容易踩的坑 —— 不同组件的 material 行为不一样:

类型 material 行为
Renderer.material 自动 new 一个新实例,不修改原资源
Renderer.sharedMaterial 直接修改原资源(影响所有使用者)
Graphic.material(RawImage / Image / Text) 不会实例化,直接指向资源
Graphic.materialForRendering 正确的实例化材质(推荐)

结论:UI 系列(Graphic 派生类)必须用 materialForRendering 才能拿到真正的运行时副本;直接改 Graphic.material 会改到共享资源上。

五、给 Spine 换材质:CustomMaterialOverride

替换 Spine 的渲染材质,并保证可恢复:

if (skeletonAnim != null)
{
    skeletonRenderer = skeletonAnim.GetComponent<SkeletonRenderer>();
    // 缓存原始材质
    originalMaterial = skeletonRenderer.GetComponent<MeshRenderer>().sharedMaterial;

    if (minShineyAmount > 0.001f)
    {
        // 创建材质副本,改属性,避免污染原材质
        var newMaterial = new Material(shineyMaterial);
        newMaterial.SetFloat("_tint_amount", minShineyAmount);
        skeletonRenderer.CustomMaterialOverride[originalMaterial] = newMaterial;
    }
}

恢复原始材质:

public virtual void RestoreOriginalMaterial()
{
    skeletonRenderer.CustomMaterialOverride.Remove(originalMaterial);
}

要点:用 new Material(...) 做副本,靠 CustomMaterialOverride 建立「原材质 → 替换材质」的映射,恢复时按 key 移除即可。

六、纹理内存:为什么导入后变大了

现象:硬盘上一张 1998 × 1081、456 KB 的图片,导入 Unity 后预览显示 0.9 MB(ASTC 6×6)。

原因:456 KB 是磁盘上的文件体积(高度压缩),0.9 MB 是 GPU 运行时必需的纹理内存,二者不是一回事。

ASTC 是块压缩格式,每个 6×6 像素块固定占用 16 字节(128 位)。纹理宽高会向上对齐到 6 的整数倍(只多占用必要的行列块)。计算:

宽度块数 = ceil(1998 / 6) = 333
高度块数 = ceil(1081 / 6) = 181
总字节数 = 333 × 181 × 16 = 964,368 字节 ≈ 941.8 KB ≈ 0.92 MB

关键点:只要分辨率不变、ASTC 块大小不变,无论画面是纯色还是照片,内存占用都是这个固定值。 这就是硬盘 456 KB 的 JPG/PNG 导入后变成 0.9 MB 的根本原因 —— 它已经按像素块数量换算成 GPU 可直接读取的固定大小纹理。

  • 456 KB 是压缩后的文件体积,压缩率随图片内容变化;
  • 0.9 MB 是 GPU 纹理内存,由分辨率与像素格式决定,与内容无关。

GPU 不能直接使用 PNG/JPG 做实时渲染,Unity 必须转成 GPU 纹理格式(这里是 ASTC 6×6,通常说明目标平台是移动 / ARM)。ASTC 是「按像素块固定比特率」的压缩,不像 JPG 可以无限压,所以在该分辨率下文件能压到 456 KB,但 GPU 纹理至少要近 1 MB。

实践约束:项目中图片最大尺寸控制在 2048。

七、unitypackage 的本质

Unity 的 .unitypackage 管理本质上是一个 JSON 文件,可以动态修改。

小结

  • Spine 换肤三步走:换肤 → SetSlotsToSetupPose() → Apply()。
  • 粒子透明度优先改 startColor;改材质记得 Surface Type 为 Transparent。
  • 运行时改材质前先搞清实例化规则:Renderer.material 会自动实例化,Graphic 系列必须用 materialForRendering。
  • 纹理内存由分辨率和像素格式决定,与文件体积无关;单图尺寸控制在 2048 以内。

相关:Unity 渲染与排序:Layer、Sorting Layer 与 UI 绘制顺序

这是一条笔记,不是成稿文章。它可能不完整、可能有错,也可能过段时间就被我推翻。 想看成体系的文章,去 Blog。

linked from1 处引用了这篇