粒子系统中由于制作问题而导致的警告和错误

Particle System is trying to spawn on a mesh with zero surface area

项目中遇到这样一个警告, Particle System is trying to spawn on a mesh with zero surface area, 字面意思翻译过来就是说: 粒子系统正尝试在一个零表面的网格上生成粒子, 网上说是因为 Mesh 没有开启 Read/Write 选项导致的. 不过从字面意思来看, 这两者八竿子打不着嘛, 这里面肯定有蹊跷.

后来经过自己的尝试和研究发现, 证实 Mesh 的 Read/Write Enable 设置确实是引起这个警告的原因, 但不是全部原因! 下面先说一下 Unity 中粒子系统的相关知识, 进而分析问题的真正原因.

如何自定义 Unity 中的粒子系统的发射器形状

首先 Unity 中的粒子系统可以自定义发射器的形状, 而且可以使用 Mesh 来自定义发射器形状. 比如这里我选用了类似 "两个小盘子" 的 Mesh 来作为发射器的形状.

发射器

然后设置发射粒子的方式, Unity 提供了三种方式:

  1. Vertex : 只在 Mesh 的顶点处生成粒子

我们设置粒子速度为 0, 这样粒子生成后就不会改变位置, 我们就知道它们是在什么位置生成的了. 我设置粒子为一个个的小球. 生成的粒子就是这个样子的. 可以看到所有的小球都在 Mesh 的顶点上.

Vertex

  1. Edge : 只在 Mesh 的边上生成粒子

同样, 我们设置粒子速度为 0, 并且设置粒子生成的多一点, 生成的粒子就是这个样子的. 可以看到所有的小球全部都在 Mesh 的线段上.

Edge

  1. Triangle : 在 Mesh 的三角面上生成粒子, 也就是在 Mesh 的整个表面上生成粒子.

同样, 我们看一下生成的粒子. 可以看到所有的小球均匀覆盖在 Mesh 表面上.

Triangle

问题的原因

了解了上面的机制后就能分析出原因了, 特效组在制作特效时设置了自定义发射器形状为 Mesh, 但是却没有为其指定 Mesh, 这样就导致粒子系统在生成粒子的时候找不到顶点或者或者三角面, 所以出现警告.

注意: 这里有一个例外, 按理来说上面的分析没有什么问题了, 但是在我具体的实验中发现, 即使忘记了指定 Mesh, 但是发射类型选择的是顶点 Vertex, 一样不会出现警告, 不知道为啥, 可能是 Unity 觉得找不到, 找不到都算有问题, 找不到不算问题... 可能吧...

可能个鬼呀!! 怎么可能, 要么是 Unity 的 Bug, 漏写了一个警告输出, 要么就是 Unity 在这里的决策有问题, 在设计的时候就设计这里检测不到点不会输出警告, 总之我还是认为即使设置为 Vertex 也应该输出警告, 而不是现在的什么反馈也没有! 因为此时虽然 Unity 没有输出警告, 但是场景中也不会生成任何粒子, 也就是没有任何外观表现, 难道这不应该输出警告吗?

初步结论

当同时满足下面条件时会出现此警告:

  1. 粒子系统 Shape 组件的 Shape 设置为 Mesh.
  2. 粒子系统 Shape 组件的 Type 设置为 Edge 或者 Triangle.
  3. 粒子系统 Shape 组件的 Mesh 为空.

疑惑

那为什么网络上说和 Mesh 的读写权限有关呢? 这是因为在编辑器下 Mesh 的读写权限是强制开启的, 即使在模型的导入设置中关闭读写权限, Unity Editor 依然可以读取网格的顶点数据, 因此并不会出现读不到顶点信息的问题, 但是真机运行环境下呢? 真机上可就没有这种特殊待遇了, 如果网格的读写权限没有开启, 那么就是导致粒子系统去读取网格顶点信息的时候读取不到, 从而出现上面的警告.

因此粒子系统发射器使用的 Mesh 是需要开启读写权限的, 但是开启读写权限会导致网格在 RAM (内存) 和 VRAM (显存) 中各占用一份靠空间, 导致内存占用翻倍, 因此粒子系统的网格应该极其简单才行.

最终结论

当同时满足下面条件时会出现此警告:

  1. 粒子系统 Shape 组件的 Shape 设置为 Mesh.
  2. 粒子系统 Shape 组件的 Type 设置为 Edge 或者 Triangle.
  3. 粒子系统 Shape 组件的 Mesh 为空.
  4. 粒子系统 Shape 组件引用的 Mesh 没有开启读写权限

Sub-emitters must be children of the system that spawns them

收集外网客户端 Bug 的时候收到了一个几万次的警告: Sub-emitters must be children of the system that spawns them, 翻译过来就是: Sub-emitters 所引用的粒子系统必须是当前物体的子物体所挂载的粒子系统.

因此此问题的检测条件为:

  1. 粒子系统启用了 Sub-Emitter. []: 这个可以使用 particle.subEmitters.enabled 判断.
  2. Sub-Emitter 设置为 null 或者 Sub-Emitter 引用的粒子系统不是当前游戏物体的子物体所挂载的粒子系统.

参考代码

下面是参考代码, 为了检测的严谨性, 即使粒子系统没有启用 Sub-Emitter, 依旧会进行问题检测.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
var particles = asset.GetComponentsInChildren<ParticleSystem>(true);

foreach (var particle in particles)
{
// 获取所有的子粒子系统
var allSubParticleSystems = particle.GetComponentsInChildren<ParticleSystem>(true);

// 获取 Sub-Emitter 的个数
var subEmittersCount = particle.subEmitters.subEmittersCount;

// 遍历所有的 Sub-Emitter
for (var index = 0; index < subEmittersCount; index++)
{
// 依次获取 Sub-Emitter 设置
var setting = particle.subEmitters.GetSubEmitterSystem(index);

// Sub-Emitter : null
if (ReferenceEquals(setting, null))
{
var content = $"粒子特效的 Sub-Emitters 为空! : {assetPath} 子物件: {particle.name}";
report.Add(EffectCheckReport.AddReportInfo(asset, assetPath, EffectCheckReportInfo.EffectCheckReportType.ParticleSubEmittersError, content, item));
}
else
{
var isError = true;
foreach (var subParticleSystem in allSubParticleSystems)
{
if (setting == subParticleSystem)
{
isError = false;
}
}

if (isError)
{
var content = $"粒子特效 Sub-Emitters 设置错误: {assetPath} 子物件: {particle.name}";
report.Add(EffectCheckReport.AddReportInfo(asset, assetPath, EffectCheckReportInfo.EffectCheckReportType.ParticleSubEmittersError, content, item));
}
}
}
}

备注

经测试, 此问题在 2021.1.15f1 上的错误级别已经由 Warning 被调整为了 Error. 真是一件好事.