Unity如何适配OnTrigger触发逻辑并实现通用碰撞检测脚本
Unity 碰撞区域拦截逻辑改造方案
问题场景说明
当前项目需要实现两类碰撞区域的玩家越界拦截:玩家越界后自动减速、停留提示、转身返回安全区,两类区域触发顺序完全相反:
- 大尺寸活动范围:玩家初始在区域内部,离开碰撞体(OnTriggerExit) 判定为越界,回到碰撞体(OnTriggerEnter)判定为返回安全区
- 小尺寸火焰禁区:玩家初始在区域外部,进入碰撞体(OnTriggerEnter) 判定为越界,离开碰撞体(OnTriggerExit)判定为返回安全区

(左侧小碰撞体为火焰禁区,游戏启动时玩家位于大碰撞体活动范围内)
① 触发顺序兼容实现逻辑
核心思路是放弃硬绑定「Exit=越界、Enter=返回」的固定逻辑,给每个碰撞体增加区域类型标记,统一越界/返回的执行入口:
- 为每个碰撞体配置
IsInnerArea布尔值:大活动范围类区域勾选该值,判定逻辑为「Exit触发越界,Enter触发返回」;火焰禁区类区域不勾选该值,判定逻辑为「Enter触发越界,Exit触发返回」 - 增加全局状态锁
isHandling,同一时间只处理一个越界事件,避免多个碰撞体触发时协程冲突、逻辑打架 - 抽离统一的越界处理方法和返回恢复方法,不管是Enter还是Exit事件触发,只要匹配对应判定规则就调用统一逻辑,无需重复编写代码
② 单脚本挂载玩家的通用配置实现
无需把脚本挂在每个碰撞体上,直接把脚本挂在玩家对象上,通过自定义可序列化配置类实现多碰撞体独立参数配置:
- 定义
[System.Serializable]的BoundaryConfig类,每个配置项包含:碰撞体引用、对应提示文本、转身朝向目标点、是否为内部区域类型 - 脚本中公开
List<BoundaryConfig> boundaryConfigs列表,在Inspector面板中可以直接增删区域,每个区域独立配置所有参数,不需要维护多个列表的下标对应关系 - 触发碰撞事件时,遍历配置列表匹配当前碰撞到的对象,读取对应配置执行逻辑即可
完整修改后代码
using System.Collections; using System.Collections.Generic; using TMPro; using UnityEngine; using UnityStandardAssets.Characters.ThirdPerson; public class DistanceCheck : MonoBehaviour { [Header("全局参数配置")] public float lerpDuration = 1f; public float rotationSpeed = 5f; public float returnTriggerDistance = 5f; public float waitTimeBeforeReturn = 3f; public GameObject descriptionTextImage; public TextMeshProUGUI text; public ThirdPersonUserControl thirdPersonUserControl; [Header("所有边界区域配置")] public List<BoundaryConfig> boundaryConfigs = new List<BoundaryConfig>(); private Animator anim; private float timeElapsed = 0; private float startValue = 1; private float endValue = 0; private float valueToLerp = 0; private bool startRotating = false; private bool slowOnBack = true; private bool isHandling = false; private Vector3 exitPosition; private float distance; private BoundaryConfig currentActiveBoundary; [System.Serializable] public class BoundaryConfig { public Collider targetCollider; [TextArea(1,2)] public string tipText; public Transform rotateTarget; [Tooltip("勾选=玩家初始在区域内,离开算越界;不勾选=玩家初始在区域外,进入算越界")] public bool isInnerArea = true; } void Start() { anim = transform.GetComponent<Animator>(); } private void FixedUpdate() { if (startRotating && currentActiveBoundary != null) { Vector3 lookDir = currentActiveBoundary.rotateTarget.position - transform.position; lookDir.y = 0; transform.rotation = Quaternion.RotateTowards(transform.rotation, Quaternion.LookRotation(lookDir), rotationSpeed * Time.deltaTime); } if (exitPosition != Vector3.zero && slowOnBack) { distance = Vector3.Distance(transform.position, exitPosition); } if (distance > returnTriggerDistance && slowOnBack) { slowOnBack = false; StartCoroutine(SlowDown()); } } private void OnTriggerExit(Collider other) { BoundaryConfig config = GetMatchConfig(other); if (config == null || isHandling) return; // 内部区域Exit才是越界,非内部区域Exit是返回安全区 if (config.isInnerArea) { TriggerBoundaryHandle(config); } else { TriggerReturnHandle(); } } private void OnTriggerEnter(Collider other) { BoundaryConfig config = GetMatchConfig(other); if (config == null) return; // 非内部区域Enter才是越界,内部区域Enter是返回安全区 if (!config.isInnerArea && !isHandling) { TriggerBoundaryHandle(config); } else if (config.isInnerArea && config == currentActiveBoundary) { TriggerReturnHandle(); } } private BoundaryConfig GetMatchConfig(Collider other) { foreach (var config in boundaryConfigs) { if (config.targetCollider == other) { return config; } } return null; } private void TriggerBoundaryHandle(BoundaryConfig config) { isHandling = true; currentActiveBoundary = config; slowOnBack = true; exitPosition = transform.position; thirdPersonUserControl.enabled = false; descriptionTextImage.SetActive(true); text.text = config.tipText; StartCoroutine(SlowDown()); } private void TriggerReturnHandle() { isHandling = false; startRotating = false; text.text = ""; descriptionTextImage.SetActive(false); currentActiveBoundary = null; StopAllCoroutines(); anim.SetFloat("Forward", startValue); } IEnumerator SlowDown() { timeElapsed = 0; while (timeElapsed < lerpDuration) { valueToLerp = Mathf.Lerp(startValue, endValue, timeElapsed / lerpDuration); anim.SetFloat("Forward", valueToLerp); timeElapsed += Time.deltaTime; yield return null; } if (isHandling) { yield return new WaitForSeconds(waitTimeBeforeReturn); startRotating = true; StartCoroutine(SpeedUp()); } if (!slowOnBack) { thirdPersonUserControl.enabled = true; } } IEnumerator SpeedUp() { timeElapsed = 0; while (timeElapsed < lerpDuration) { valueToLerp = Mathf.Lerp(endValue, startValue, timeElapsed / lerpDuration); anim.SetFloat("Forward", valueToLerp); timeElapsed += Time.deltaTime; yield return null; } } }
配置注意事项
- 所有需要检测的碰撞体都要勾选
Is Trigger选项 - 玩家对象必须挂载Collider和Rigidbody组件,Rigidbody建议勾选
Is Kinematic,避免物理碰撞干扰角色控制 - 配置列表中每个区域的
rotateTarget要拖入对应区域玩家返回时需要朝向的目标点,比如火焰区域就拖到火焰反方向的安全点,大活动区域就拖到活动范围中心
内容的提问来源于stack exchange,提问作者Daniel Lip
相关产品推荐
相关产品推荐

