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; } }
原有方案的问题
- 实体间仅通过触发器交互,触发器无法提供
ContactPoints等物理信息,难以获取碰撞法线 - 每个实体需挂载两个同类型Collider,修改碰撞盒时需同步调整,操作繁琐
- 实例化实体时遍历场景所有GameObject,大量实例化场景下性能开销大
尝试过的无效思路
- 无法用Static/Dynamic标签区分实体,移动平台等非实体也可能是动态的
- 碰撞发生后再调用
IgnoreCollision()会出现明显的短暂物理碰撞卡顿 - 修改Layer Collision Matrix关闭实体层间碰撞会同时禁用触发器和
OnCollisionEnter()等检测
解决方案
方案1:分层物理+主动检测(推荐)
- 创建两个专属层:
EntityPhysics(处理与地形的物理碰撞)、EntityDetection(处理实体间的重叠检测) - 在Layer Collision Matrix中配置:
EntityPhysics仅与地形层碰撞,与其他EntityPhysics层无碰撞EntityDetection仅与其他EntityDetection层碰撞
- 给每个实体添加单个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:物理材质关闭碰撞响应
- 创建自定义
PhysicsMaterial2D,设置Friction=0、Bounciness=0,开启Enable Collision Response为false(Unity 2020+版本支持) - 将该材质赋予实体的Collider2D,设置实体Layer仅与地形层碰撞
- 此时实体间会触发
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:优化原有忽略碰撞逻辑
- 维护全局列表存储实体的非触发碰撞器,避免遍历整个场景:
此方案解决了原有的性能问题,同时保留单个Collider的简洁性。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); } } }
内容的提问来源于stack exchange,提问作者TT87
相关产品推荐
相关产品推荐

