Unity中使碰撞检测的碰撞箱处于不同层级的方法

  • Unity在做碰撞检测时,Mono基类的碰撞与触发检测函数诸如OnCollisionEnter()之类必须得获取到与其代码附着地同层级的碰撞箱才能进行检测,当碰撞箱单独拎出来时,代码就不会工作了。
  • 我被这狗屎问题困扰了多年,今天终于有了解决方案。
  • 当然这个方法只是解决此问题的众多方法之一,成熟点的方法也许会用到接口与委托,这里我就使用我的方法。

(甲)物理碰撞的条件

  • 达成物理碰撞有如下的条件:
    • 碰撞物体双方都拥有碰撞箱(Collider抽象类及其子类)
    • 碰撞物体至少有一方拥有刚体(Rigidbody类)

(乙)碰撞及触发检测方法

  • 基础的碰撞检测方法如下:
    • private void OnCollisionEnter(Collision collision)
    • private void OnCollisionStay(Collision collision)
    • private void OnCollisionExit(Collision collision)
  • 基础的触发检测方法如下:
    • private void OnTriggerEnter(Collider other)
    • private void OnTriggerStay(Collider other)
    • private void OnTriggerExit(Collider other)

(丙)使用代理碰撞器

  • 首先需要一个基类,负责管理所有的碰撞事件,在此命名为ObjectManager
1
2
3
4
public class ObjectManager : MonoBehaviour
{

}
  • 再创建一个代理碰撞器类,代理碰撞器会代替基类去碰撞,在此命名为CollisionProxy
1
2
3
4
public class CollisionProxy : MonoBehaviour
{

}
  • 在代理器中加入需要代理的检测方法,注意代理器与基类都需要继承自MonoBehaviour
1
2
3
4
5
6
7
8
9
10
11
12
13
public class ObjectManager : MonoBehaviour
{
//Mono基类的碰撞或触发检测方法,这里是碰撞器进入方法
private void OnCollisionEnter(Collision collision)
{

}
//Mono基类的碰撞或触发检测方法,这里是触发器进入方法
private void OnTriggerEnter(Collider other)
{

}
}
  • 代理器中还需要获取代理的基类以及自身的代理序号
1
2
public ObjectManager objectManager;//代理器所代理的基类
public int proxyIndex = 0;//此代理器的代理序号
  • 在基类里定义与代理器中相对应的代理方法,需要在参数中传递碰撞信息与代理器自身
  • 代理方法需要用virtual设置为虚拟方法,这样子类就能够用override重写
1
2
3
4
5
6
7
8
9
10
11
12
13
public class ObjectManager : MonoBehaviour
{
//代理碰撞器进入方法(虚拟)
public virtual void OnCollisionEnterProxy(Collision collision, CollisionProxy proxy)
{

}
//代理触发器进入方法(虚拟)
public virtual void OnTriggerEnterProxy(Collider other, CollisionProxy proxy)
{

}
}
  • 加入用来调用测试的行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ObjectManager : MonoBehaviour
{
//代理碰撞器进入方法(虚拟)
public virtual void OnCollisionEnterProxy(Collision collision, CollisionProxy proxy)
{
print($"Manager的代理{proxy.name}序号为{proxy.proxyIndex},碰撞了物体{collision.collider.name}。");
if (proxy.proxyIndex == 3)
{
print("检测到了3号碰撞器");
}
}
//代理触发器进入方法(虚拟)
public virtual void OnTriggerEnterProxy(Collider other, CollisionProxy proxy)
{
print($"Manager的代理{proxy.name}序号为{proxy.proxyIndex},触发了物体{other.name}。");
if (proxy.proxyIndex == 3)
{
print("检测到了3号碰撞器");
}
}
}
  • 在代理器中调用此基类的代理方法,将信息传入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class CollisionProxy : MonoBehaviour
{
public ObjectManager objectManager;//代理器所代理的基类
public int proxyIndex = 0;//此代理器的代理序号

//Mono基类的碰撞或触发检测方法,这里是碰撞器进入方法
private void OnCollisionEnter(Collision collision)
{
//调用所代理的基类的方法
objectManager.OnCollisionEnterProxy(collision, this);
}
//Mono基类的碰撞或触发检测方法,这里是触发器进入方法
private void OnTriggerEnter(Collider other)
{
//调用所代理的基类的方法
objectManager.OnTriggerEnterProxy(other, this);
}
}
  • 回到Unity里,创建一个球,携带碰撞箱,在其身上加上刚体,勾去使用重力,勾选Is Kinematic,这个球就是需要被检测的对象
  • 再新建一个方体,把碰撞箱移除,附上ObjectManager基类
  • 在方体的子级里添加三个空物体,附上碰撞箱组件(这里是Sphere Collider),再附上CollisionProxy代理器组件,将父级赋给代理器组件中的objectManager参数,更改每一个代理器中的proxyIndex序号
  • 这里为了测试触发器,将代理器中的碰撞箱的Is Trigger勾上了
  • 这时运行工程,手动移动方体的位置,使得子级代理器的碰撞箱触发球体
  • 测试成功,基类获取到了触发信息(包括触发的物体、代理器序号的信息)
  • 为了使得此方法变得更好用,这里定义一个新脚本来继承ObjectManager基类,在此命名为ObjectManagerSon
  • 因为面向对象的多态特性,代理器组件所调用的基类函数会下达到其子类的重写函数,所以只需要在ObjectManagerSon中,重写代理碰撞方法,稍微对测试行进行修改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ObjectManagerSon : ObjectManager
{
//代理碰撞器进入方法(重写)
public override void OnCollisionEnterProxy(Collision collision, CollisionProxy proxy)
{
print($"ManagerSon的代理{proxy.name}序号为{proxy.proxyIndex},碰撞了物体{collision.collider.name}。");
if (proxy.proxyIndex == 3)
{
print("检测到了3号碰撞器,而且此管理器为ObjectManager的子类");
}
}
//代理触发器进入方法(重写)
public override void OnTriggerEnterProxy(Collider other, CollisionProxy proxy)
{
print($"ManagerSon的代理{proxy.name}序号为{proxy.proxyIndex},触发了物体{other.name}。");
if (proxy.proxyIndex == 3)
{
print("检测到了3号碰撞器,而且此管理器为ObjectManager的子类");
}
}
}
  • 将场景中方体身上的ObjectManager替换为子类ObjectManagerSon,并赋给三个代理器的objectManager参数
  • 成功