Spine、粒子特效与运行时材质
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 做灰度,有几种由简到繁的做法:
- 直接用颜色近似:
skeletonGraphic.color = Color.gray;,简单但对细节控制有限。 - 自定义材质 + 着色器:最灵活,能精确控制灰度算法。
- Spine 自带的灰度效果 / 逐插槽染色:
skeletonGraphic.color = Color.gray;
var skeleton = skeletonGraphic.Skeleton;
if (skeleton != null)
{
// 设置所有插槽为灰色
foreach (var slot in skeleton.Slots)
slot.SetColor(Color.gray);
}
- 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 以内。
这是一条笔记,不是成稿文章。它可能不完整、可能有错,也可能过段时间就被我推翻。 想看成体系的文章,去 Blog。
linked from1 处引用了这篇