欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Unity武器与子弹碰撞检测

程序员文章站 2024-03-15 22:17:30
...


在想进行碰撞检测制造伤害或生成特效时,有两种常用的方法,一种是使用碰撞器和刚体,检测到碰撞后会自动调用OnCollisionEnter等函数,一种是使用Physics接口下的OverlapBox等检测函数。


一、采用碰撞器和刚体进行碰撞检测

在使用Collider进行碰撞检测时,至少要有一方是带有刚体组件。在默认设置的情况下这样就能够进行碰撞检测,但是在设置物体为静态或勾选刚体的动力学选项后会有不同。
关于这两种碰撞什么情况下可以检测到请看该链接

不关心碰撞位置的情况:

可以把碰撞检测方的碰撞器设置为IsTrigger,并在碰撞组件同级挂载碰撞检测后调用的代码。把碰撞器设置为IsTrigger后,能检测到碰撞并且调用OnTriggerEnter,OnTriggerStay,OnTriggerExit,但是不会对双方造成物理作用。

	/// <summary>
    /// 进入碰撞范围时自动调用
    /// </summary>
    /// <param name="other">碰撞检测到的物体,以下简称目标物</param>
    private void OnTriggerEnter(Collider other)
    {
    	// owner是持有武器的角色身上的碰撞器,目标物的碰撞体为拥有者的碰撞体时直接返回
        if (other.GetInstanceID() == owner.GetInstanceID())
            return;
		
		// 判断目标物是否为可以被攻击的物体
        Controller entity;
        if (!other.TryGetComponent(out entity))
            return;
		
		// 调用目标物的受到伤害的函数
        entity.TakeDamage(1);
    }

关心碰撞位置的情况:

有时可能需要碰撞的位置来生成特效,比如子弹打中哪里就在哪里生成击中特效,在OnTriggerEnter中做不到这一点,当然也能在此时碰撞体所在的位置生成特效,但位置仍是不准确的。
此时不能把碰撞器设置为IsTrigger,因为需要使用OnCollisionEnter函数,同样在碰撞组件同级挂载碰撞检测后调用的代码。

/// <summary>
    /// 进入碰撞范围时自动调用
    /// </summary>
    /// <param name="collision">碰撞检测到的物体,以下简称目标物</param>
	private void OnCollisionEnter(Collision collision)
    {
    	// 发生碰撞的位置
        Vector3 hitPoint = collision.contacts[0].point;
        // 实例化特效
        GameObject hitEffect = Instantiate(effectPrefab);
        // 将特效放置在碰撞点
        hitEffect .transform.position = hitPoint;
    }

如果想要更精确的模拟击中特效,可以将生成的特效的方向改为碰撞体的反方向,比如子弹击中后,血液往子弹来的方向喷。

二、使用代码检测

Unity武器与子弹碰撞检测
Physics接口中有许多碰撞检测的函数可以调用,每个函数的返回值和参数都不一样,但用法大同小异,这里就拿OverlapBox作为例子。

	// 进行检测的层级,暴露在属性页面进行勾选
	[SerializeField] LayerMask impactLayer;
	// 碰撞检测的物体
	[SerializeField] Transform colBox;
	
	private void Update()
    {
        BoxCheck(colBox);
    }

	/// <summary>
    /// 箱型检测
    /// </summary>
    /// <param name="colBox">箱子物体,可以空物体,作为武器的子物体跟随移动</param>
    void BoxCheck(Transform colBox)
    {
        // 检测中心点
        Vector3 center = colBox.position;
        // 碰撞器的缩放,以Unity默认的正方体为标准
        Vector3 checkBox= new Vector3(1, 0.1f, 0.1f);
        
        // 碰撞检测到的碰撞体
        Collider[] colliders = Physics.OverlapBox(center, checkBox, colBox.rotation, impactLayer);
        foreach (var col in colliders)
        {
            // 碰撞体是自身
            if (col.GetInstanceID() == owner.GetInstanceID())
                continue;
            
            // 判断目标物是否为可以被攻击的物体
            Controller entity;
            if (!col.TryGetComponent(out entity))
                continue;
            
            // 调用目标物的受到伤害的函数
            entity.TakeDamage(1);
            
        }
    }

用代码检测碰撞时不需要刚体组件,但与OnTriggerEnter一样获取不到碰撞位置。

在使用武器攻击敌人时,如果不对敌人的碰撞体进行记录,会产生多段伤害,因为每一帧都在调用碰撞检测,所以上面的代码需要进行优化。

1、只在武器挥出的时候进行检测,可以在动画里设置帧事件,在攻击动作的前后设置开启和关闭
2、标记碰撞体以避免重复检测

	// 已经被伤害过的碰撞体
	HashSet<int> colsMarked = new HashSet<int>();

	//是否开启碰撞检测
	bool enableDamage;

    private void Update()
    {
        if (!enableDamage)
            return;
            
        BoxCheck(colBox);
    }	

	/// <summary>
    /// 箱型检测
    /// </summary>
    /// <param name="colBox">箱子物体,可以空物体,作为武器的子物体跟随移动</param>
    void BoxCheck(Transform colBox)
    {
        // 检测中心点
        Vector3 center = colBox.position;
        // 碰撞器的缩放,以Unity默认的正方体为标准
        Vector3 checkBox= new Vector3(1, 0.1f, 0.1f);
        
        // 碰撞检测到的碰撞体
        Collider[] colliders = Physics.OverlapBox(center, checkBox, colBox.rotation, impactLayer);
        foreach (var col in colliders)
        {
            // 碰撞体是自身
            if (col.GetInstanceID() == owner.GetInstanceID())
                continue;
			
            // 判断目标物是否为可以被攻击的物体
            Controller entity;
            if (!col.TryGetComponent(out entity))
                continue;

			// 向HashSet添加成功时返回True,否则返回False
			// 返回False时说明已经被添加过了,不能再次造成伤害
			if (!colsMarked.Add(col.GetInstanceID()))
				continue;
            
            // 调用目标物的受到伤害的函数
            entity.TakeDamage(1);
        }

		/// <summary>
    	/// 清空标记,可以通过动画帧事件进行调用
    	/// </summary>
    	/// <param name="enable">是否开启碰撞检测</param>
    	public void EnableDamage(bool enable)
    	{
    		// 清空标记过的碰撞体
    	    colsMarked.Clear();
    	    // 开启或关闭检测
    	    enableDamage = enable;
    	}

    }