You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity Physics:实现部分碰撞引擎处理、全碰撞代码可检测的方法

需求与问题

核心需求

  • 实体与地形的碰撞由Unity物理系统正常处理
  • 实体之间可互相穿过(无物理阻挡)
  • 实体间仍能检测到重叠,且能获取碰撞法线等物理信息

原有实现方案

给每个实体添加两个Collider2D:一个开启IsTrigger(用于实体间重叠检测),一个关闭IsTrigger(用于与地形的物理碰撞),并通过IgnoreSolidColliders组件让实体的非触发碰撞器互相忽略。代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Linq;

//Has object ignore non-trigger collisions with objects on the given layers.
public class IgnoreSolidColliders : MonoBehaviour
{
    //The layers an object has to be on to be affected by this script.
    [SerializeField] int[] layers;

    // Start is called before the first frame update
    void Start()
    {
        Collider2D solidCollider = new Collider2D();

        foreach(Collider2D collider in GetComponents<Collider2D>())
        {
            if(!collider.isTrigger)
            {
                solidCollider = collider;
                break;
            }
        }

        foreach(GameObject gameObject in GetAllObjectsOnlyInScene())
        {
            //Check to make sure the object has colliders and is on a targeted layer before wasting any more time on it
            if(gameObject.GetComponent<Collider2D>() != null && layers.Contains(gameObject.layer))
            {
                foreach(Collider2D collider in gameObject.GetComponents<Collider2D>())
                {
                    if(!collider.isTrigger)
                    {
                        Physics2D.IgnoreCollision(solidCollider, collider);
                    }
                }
            }
        }
    }

    List<GameObject> GetAllObjectsOnlyInScene()
    {
        List<GameObject> objectsInScene = new List<GameObject>();

        foreach (GameObject go in Resources.FindObjectsOfTypeAll(typeof(GameObject)) as GameObject[])
        {
            if (!EditorUtility.IsPersistent(go.transform.root.gameObject) && !(go.hideFlags == HideFlags.NotEditable || go.hideFlags == HideFlags.HideAndDontSave))
                objectsInScene.Add(go);
        }

        return objectsInScene;
    }
}

原有方案的问题

  1. 实体间仅通过触发器交互,触发器无法提供ContactPoints等物理信息,难以获取碰撞法线
  2. 每个实体需挂载两个同类型Collider,修改碰撞盒时需同步调整,操作繁琐
  3. 实例化实体时遍历场景所有GameObject,大量实例化场景下性能开销大

尝试过的无效思路

  • 无法用Static/Dynamic标签区分实体,移动平台等非实体也可能是动态的
  • 碰撞发生后再调用IgnoreCollision()会出现明显的短暂物理碰撞卡顿
  • 修改Layer Collision Matrix关闭实体层间碰撞会同时禁用触发器和OnCollisionEnter()等检测

解决方案

方案1:分层物理+主动检测(推荐)

  1. 创建两个专属层:EntityPhysics(处理与地形的物理碰撞)、EntityDetection(处理实体间的重叠检测)
  2. 在Layer Collision Matrix中配置:
    • EntityPhysics仅与地形层碰撞,与其他EntityPhysics层无碰撞
    • EntityDetection仅与其他EntityDetection层碰撞
  3. 给每个实体添加单个Collider2D,设置其Layer为EntityPhysics;在FixedUpdate中通过Physics2D.OverlapCollider检测同层EntityDetection的实体,同时可获取完整的碰撞信息:
    using UnityEngine;
    
    public class EntityDetector : MonoBehaviour
    {
        private Collider2D _collider;
        public LayerMask entityDetectionLayer;
    
        void Awake()
        {
            _collider = GetComponent<Collider2D>();
        }
    
        void FixedUpdate()
        {
            ContactFilter2D filter = new ContactFilter2D();
            filter.SetLayerMask(entityDetectionLayer);
            List<Collider2D> hitColliders = new List<Collider2D>();
            
            if (_collider.OverlapCollider(filter, hitColliders) > 0)
            {
                foreach (var collider in hitColliders)
                {
                    // 获取碰撞法线等信息
                    Physics2D.GetContacts(_collider, collider, out ContactPoint2D[] contacts);
                    foreach (var contact in contacts)
                    {
                        Debug.Log("碰撞法线:" + contact.normal);
                    }
                    // 处理实体交互逻辑,如转向
                    HandleEntityInteraction(collider.gameObject);
                }
            }
        }
    
        void HandleEntityInteraction(GameObject otherEntity)
        {
            // 自定义交互逻辑
        }
    }
    

方案2:物理材质关闭碰撞响应

  1. 创建自定义PhysicsMaterial2D,设置Friction=0、Bounciness=0,开启Enable Collision Response为false(Unity 2020+版本支持)
  2. 将该材质赋予实体的Collider2D,设置实体Layer仅与地形层碰撞
  3. 此时实体间会触发OnCollisionEnter2D等回调,但无物理阻挡,同时可直接获取ContactPoint2D中的法线信息:
    using UnityEngine;
    
    public class EntityCollision : MonoBehaviour
    {
        void OnCollisionEnter2D(Collision2D collision)
        {
            if (collision.gameObject.CompareTag("Entity"))
            {
                foreach (ContactPoint2D contact in collision.contacts)
                {
                    Debug.Log("碰撞法线:" + contact.normal);
                }
                // 处理实体交互逻辑
            }
        }
    }
    

方案3:优化原有忽略碰撞逻辑

  1. 维护全局列表存储实体的非触发碰撞器,避免遍历整个场景:
    using System.Collections.Generic;
    using UnityEngine;
    
    public class EntityCollisionHandler : MonoBehaviour
    {
        public static List<Collider2D> EntitySolidColliders = new List<Collider2D>();
        private Collider2D _solidCollider;
    
        void Awake()
        {
            _solidCollider = GetComponent<Collider2D>();
            if (_solidCollider != null && !_solidCollider.isTrigger)
            {
                // 忽略与已有实体的碰撞
                foreach (var collider in EntitySolidColliders)
                {
                    Physics2D.IgnoreCollision(_solidCollider, collider);
                }
                EntitySolidColliders.Add(_solidCollider);
            }
        }
    
        void OnDestroy()
        {
            if (_solidCollider != null)
            {
                EntitySolidColliders.Remove(_solidCollider);
            }
        }
    }
    
    此方案解决了原有的性能问题,同时保留单个Collider的简洁性。

内容的提问来源于stack exchange,提问作者TT87

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 02:55:12