关于Unity粒子系统碰撞的几个坑

关于Unity粒子系统碰撞的几个坑

最近公司的项目正好要用到粒子系统的碰撞,所以特意研究了一下。在实践中遇到了很多问题,所以借此文记录一下学习的过程。而且学习过程中发现网上这方面文章极少,所以也算借这篇文章填补一下行业漏洞,顺道给自己和后来者一个参考。本人接触Unity时间也不算太长,如果下面哪里说的不对,欢迎各位大神纠正。
大量图片预警

粒子与碰撞器的碰撞

这一部分官方用户手册有很明确的说明,首先找到手册下图这一层级目录 Collision Module,点进去就能看到关于面板所有参数的介绍了:
关于Unity粒子系统碰撞的几个坑
这里我用的是Unity5.6.4的用户手册,是本地文件,所以就不贴链接了,而且我相信愿意看这篇文章的人,应该不至于找不到用户手册吧……
这篇手册已经讲得详细了,所以我们只讨论一些文章里没有提到的东西和提到了但是说得比较模糊的东西。点进去之后拉到文章最后你会看到这样一段关于粒子碰撞的介绍:
关于Unity粒子系统碰撞的几个坑
简单的说就是告诉你怎么用碰撞检测脚本,但是请注意它说的是 the object with the particle system, or the one with the Collider, or both 也就是说只有带有粒子系统或者带有碰撞器的物体之间可以碰撞,而且经过我的实践发现:只有一个带有碰撞器组件的物体和一个带有粒子系统并开启了粒子碰撞的物体之间碰撞才会触发碰撞事件,其他任何情况都无法触发粒子碰撞事件,如果您不信可以继续看下去。
我们先来看一下简单的一种情况,也就是官方提供的这种情况,同时也是网上在我写这篇文章之前能搜到的最多的一种情况。所以我尽量用最短的篇幅介绍,如果您除了这种情况不会用到其他情况,那看完这一小节后下面的就不用看了。
我们打开Unity直接实践:
新建一个场景,创建一个平面当地面,删掉原有摄像机(我们会用FPS控制器,自带相机),拖上来一个粒子(这里你随意拖一个粒子就好,我用的是一个火焰粒子),开启粒子碰撞,我的设置如下,其中 Radius ScaleColliders With 要根据你的实际粒子来设置,前者为碰撞器大小,后者为需要碰撞的层。
关于Unity粒子系统碰撞的几个坑
然后我为了节省时间是把标准资源中的FPS控制器拖进来了,如果你有时间可以自己写一个FPS控制器……然后将控制器的标签和层都改成 “Player” ,如果没有可以自己添加,只要记得添加好后把 Colliders With 也相应改好就行。现在你选中粒子应该可以在Scene视图下看到粒子碰撞器了:
关于Unity粒子系统碰撞的几个坑
然后开始写脚本吧,我就用最简单的代码测试一下,代码如下,脚本拖到粒子身上:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FireTest : MonoBehaviour
{
    private void OnParticleCollision(GameObject other)
    {
        if (other.tag == "Player")
        {
            Debug.Log("一个玩家已死亡");
        }
    }
}

接下来运行游戏即可,走到火焰上就会看到打印,说明碰撞检测生效。你应该会注意到不但打印了而且打印了很多次,这说明每一个粒子的碰撞都会单独触发。
关于Unity粒子系统碰撞的几个坑
截至到此,如果您不需要用到其他的碰撞(只是粒子和带碰撞器组件的物体之间的碰撞),您可以不用往下看了。

粒子与射线的碰撞

接下来我们看一下射线与粒子的碰撞。这是我在做项目的时候,项目需要做一个准星检测前方区域是否有粒子然后变色的功能,自然得就想到要用射线检测。还用刚才的场景,为了加强说服力,我把 Colliders With 改成“Everything”,也就是可以和一切有碰撞的物体碰撞,然后写发射射线脚本,代码如下,挂在FPS控制器身上。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ShootTest : MonoBehaviour
{
	void Update ()
    {
        RaycastHit hit;
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

        if (Physics.Raycast(ray, out hit))
        {
            Debug.Log(hit.collider);

            if (hit.collider.gameObject.tag == "Fire")
            {
                Debug.Log("前方有火");
            }
        }
    }   
}

同时为了加强说服力我们把火焰粒子物体的标签改为“Fire”,并把层设置为“Player”,然后修改其身上的脚本如下,运行游戏

sing System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FireTest : MonoBehaviour
{
    private void OnParticleCollision(GameObject other)
    {
        Debug.Log("粒子发生碰撞");

        if (other.tag == "Player")
        {
            Debug.Log("一个玩家已死亡");
        }
    }
}

运行后我们,当我们把镜头转向火时只打印了地面,无论怎么改变镜头朝向都没有其他打印
关于Unity粒子系统碰撞的几个坑
经过这个例子,我想大家已经明白吧,首先射线不含有碰撞器组件,所以无法被粒子检测到,其次,粒子碰撞只是粒子组件的一个属性,所以也不能算成是碰撞器组件,而射线其实能够检测的也只有带有碰撞器组件的物体。
为了验证我的射线代码没有问题,我在场景中放了一个Cube,将其标签设置为“Fire”,如下:
关于Unity粒子系统碰撞的几个坑
这时运行游戏可以看到如下打印,说明我的射线代码也没有问题:
关于Unity粒子系统碰撞的几个坑
综上所述,得出结论 射线与粒子的碰撞器是无法发生碰撞 的。这是Unity这个引擎的问题,不是我们用代码可以解决的。

粒子与粒子的碰撞

最后让我们看一下粒子和粒子之间的碰撞,这个也是我在项目中实践发现的问题,因为我们的项目需要做一个水粒子扑灭火粒子的功能,所以也是自然就像到粒子之间是否会发生碰撞。
废话不多说,还是用实践来检验:
还用刚才的场景,不过这次不必用FPS控制器,所以删掉它,加一个摄像机,调好角度能看到火焰粒子就好。然后加入一个水的粒子(这个粒子您也可以随便加,只要保证粒子有初始速度,并且往火粒子那边发射就好),水粒子也同样要开启碰撞,设置同上,把 Colliders With 设置为“Everything”来增强说服力,调节好碰撞器大小,不要勾选 Play On Awake 因为我们要用代码控制粒子播放 ,接下来写脚本,代码如下,脚本拖到水粒子身上。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class WaterTest : MonoBehaviour
{
    private ParticleSystem par;

    void Start()
    {
        par = GetComponent<ParticleSystem>();
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Q))
        {
            par.Play();
        }
    }

    private void OnParticleCollision(GameObject other)
    {
        if (other.tag == "Fire")
        {
            Debug.Log("水粒子碰到了火粒子");
        }
    }
}

运行游戏,运行后按下Q键,可以看到水粒子落下,但是没有任何打印:
关于Unity粒子系统碰撞的几个坑
综上所述,得出结论 粒子与粒子之间无法发生碰撞

对于以上问题的我的解决方案

根据以上我们得出的结论,我们知道了 粒子的碰撞器只能和带有碰撞器组件的物体发生碰撞并触发事件 ,由于射线和粒子本身都不带有碰撞器组件,所以是无法发生碰撞的,那么这两种情况我们要怎么实现呢,以下仅是我个人能够想到的解决方案,但不一定是最好的,如果有大神有更好的解决方案 欢迎提出:

对于射线无法检测的解决方案

我能想到三种解决方法:
1、直接给需要检测的粒子物体加触发器,触发器区域要比粒子碰撞器能到达的最远区域大,从而在玩家即将进入粒子区域时通过 On Trigger Enter 通知玩家,该方法无法在玩家离粒子较远时使用,并且也不会随着玩家视野旋转而产生不同的通知(例如烟区域和火区域的区分);
2、给粒子物体加一个空子物体,在子物体上加碰撞器,给子物体设置一个单独的层,使该层的碰撞器不会碰到玩家,从而不会影响玩家移动,在下图窗口中有碰撞矩阵的设置,可以修改各层之间的碰撞。然后还是玩家发射射线,来检测子物体的碰撞器,该方法可能会影响到粒子的碰撞,不是很推荐,但是效果最好。
关于Unity粒子系统碰撞的几个坑
3、让粒子系统所在物体向自身所在环形区域发射射线,检测玩家后修改玩家身上记录区域的变量,此方法弥补了前两种方法的不足,但是最费性能,不推荐在手机游戏种使用。

对于粒子之间无法检测的解决方案

由于粒子的特殊性,暂时只能想到一种解决方法:
给其中一个粒子加一个碰撞器或者给其子物体加碰撞器。
但是请不要给粒子系统所在物体加刚体,我试过,会发生奇怪的现象,暂时还没有搞清楚这个现象的原因:粒子会掉在地上,弹跳几下,然后迅速飞走…………
可以给加了碰撞器的子物体加刚体,然后在粒子播放时触发刚体,从而到达和水粒子一起碰到火粒子的效果

以上内容,都为我个人看法和实践得出结论,如果说的不妥当处还望前辈高人指点,完结撒花……