// ============================================================ // 文件:FishOutOfScreenDetector.cs // 职责:出界检测(第二层)——挂在比屏幕大一圈的大 Box2D 触发器上, // 统一收所有鱼的 OnTriggerEnter2D / OnTriggerExit2D, // 当一条"沿路线移动(isMove=true)"的鱼的所有碰撞器都离开触发区、 // 且路线进度 > progressThreshold 时,静默回收这条鱼。 // // 场景布置(两层): // 第一层:贴屏幕四边的 4 个碰撞器 → 挡/反弹子弹(本脚本不管) // 第二层:比屏幕大一圈的一个 BoxCollider2D(IsTrigger=true) → 本脚本挂它上面 // // 使用前提: // 1. 第二层触发器所在物体需要一个 Kinematic 或 Dynamic 的 Rigidbody2D, // 否则 Trigger 事件不会触发(Static 的 Rigidbody2D 不算) // 2. Physics2D 层碰撞矩阵里,第二层所在层必须与 Fish 层勾选 // 3. 鱼身上的碰撞器 tag / layer 都是 Fish(可在 Inspector 里改过滤值) // ============================================================ using System.Collections.Generic; using UnityEngine; /// /// 出界检测:鱼离开"比屏幕大一圈"的触发器,且路线走过一半以上时,静默回收这条鱼。 /// public class FishOutOfScreenDetector : MonoBehaviour { [Header("鱼碰撞器过滤")] [Tooltip("鱼的 tag,留空表示不按 tag 过滤")] public string fishTag = "Fish"; [Tooltip("鱼所在的 layer 名,留空表示不按 layer 过滤")] public string fishLayer = "Fish"; [Header("出界判定")] [Tooltip("路线进度必须超过这个值才允许出界回收(0~1)。\n" + "防止出生点在屏外、正往屏内游的鱼被误删")] [Range(0f, 1f)] public float progressThreshold = 0.5f; [Header("调试")] public bool enableLog = false; /// 鱼 → 当前仍在触发区内的碰撞器集合(一条鱼可能有多个碰撞器) private readonly Dictionary> _tracked = new Dictionary>(); /// 碰撞器 → 所属鱼(Exit 时快速反查,避免再 GetComponentInParent) private readonly Dictionary _colliderOwner = new Dictionary(); private int _fishLayerId = -1; private void Awake() { _fishLayerId = string.IsNullOrEmpty(fishLayer) ? -1 : LayerMask.NameToLayer(fishLayer); if (!string.IsNullOrEmpty(fishLayer) && _fishLayerId < 0) Debug.LogWarning($"[出界检测] 找不到 layer: {fishLayer},请检查 Layer 配置"); } /// 这个碰撞器是不是"要检测的鱼"(tag + layer 都符合才受理) private bool IsFishCollider(Collider2D col) { if (col == null) return false; // ⭐ 用字符串比较而不是 CompareTag:tag 未在 Tag Manager 里定义时 CompareTag 会抛异常 if (!string.IsNullOrEmpty(fishTag) && col.gameObject.tag != fishTag) return false; if (_fishLayerId >= 0 && col.gameObject.layer != _fishLayerId) return false; return true; } /// 进入触发区:登记这条鱼(开始出发时存起来) private void OnTriggerEnter2D(Collider2D other) { if (!IsFishCollider(other)) return; // 鱼脚本挂在根节点,碰撞器可能是子物体 → 往上找 var monster = other.GetComponentInParent(); if (monster == null) return; // ⭐ 只处理沿路线移动的怪 if (!monster.isMove) return; // 同一个碰撞器重复进入(池复用/漏收 Exit):先清掉旧登记再重新登记 if (_colliderOwner.TryGetValue(other, out var oldOwner) && oldOwner != null) { if (_tracked.TryGetValue(oldOwner, out var oldSet)) oldSet.Remove(other); } _colliderOwner.Remove(other); if (!_tracked.TryGetValue(monster, out var set)) { set = new HashSet(); _tracked[monster] = set; } set.Add(other); _colliderOwner[other] = monster; if (enableLog) Debug.Log($"[出界检测] 进入触发区 guid={monster.guid} collider={other.name} 该鱼碰撞器数={set.Count}"); } /// 离开触发区:全部碰撞器都离开后,再按路线进度决定是否回收 private void OnTriggerExit2D(Collider2D other) { if (other == null) return; // 没登记过的碰撞器(不是鱼/被过滤/没收到 Enter):忽略 if (!_colliderOwner.TryGetValue(other, out var monster)) return; _colliderOwner.Remove(other); if (monster == null || !_tracked.TryGetValue(monster, out var set)) return; set.Remove(other); // ⭐ 这条鱼还有别的碰撞器贴着触发区 → 不回收,继续等 if (set.Count > 0) { if (enableLog) Debug.Log($"[出界检测] 离开触发区 guid={monster.guid} collider={other.name} 剩余碰撞器={set.Count}"); return; } // 所有碰撞器都离开了 → 先把这条鱼从列表里删掉 _tracked.Remove(monster); TryRecycle(monster); } /// 所有碰撞器都出界后:路线进度超过阈值才回收 private void TryRecycle(MonsterContral monster) { if (monster == null || monster.gameObject == null) return; // 已被击杀/回池(池对象会失活)→ 不做 if (!monster.gameObject.activeInHierarchy) return; if (!monster.isMove) return; var line = monster.monsterData != null ? monster.monsterData.moveLine : null; if (line == null || line.moveTime <= 0f) return; // 没有路线时长的(非路线鱼)不处理 float progress = monster.moveTime / line.moveTime; if (progress <= progressThreshold) { if (enableLog) Debug.Log($"[出界检测] 已出界但进度不足({progress:F2} <= {progressThreshold:F2}),只清列表不回收 guid={monster.guid}"); return; } var mgr = CreateFishManager.Instance; if (mgr == null || mgr.fishDatas_hashCodekey == null) return; // 已经不在鱼列表里(被击杀/已回收)→ 不做,避免刷 NOT_FOUND if (!mgr.fishDatas_hashCodekey.ContainsKey(monster.guid)) { if (enableLog) Debug.Log($"[出界检测] 出界但已不在鱼列表(可能已被击杀) guid={monster.guid}"); return; } if (enableLog) Debug.Log($"[出界检测] 出界回收 guid={monster.guid} 进度={progress:F2} 位置={monster.transform.position}"); // 静默回收:无死亡粒子/无金币特效,直接从鱼列表移除并回池 mgr.Id_DieFish_NotFX(monster.guid); } }