Files
5.24BuYu/Assets/MyGame/Scripts/Manager/FishOutOfScreenDetector.cs
T
2026-10-08 07:25:02 +08:00

160 lines
6.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ============================================================
// 文件: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;
/// <summary>
/// 出界检测:鱼离开"比屏幕大一圈"的触发器,且路线走过一半以上时,静默回收这条鱼。
/// </summary>
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;
/// <summary>鱼 → 当前仍在触发区内的碰撞器集合(一条鱼可能有多个碰撞器)</summary>
private readonly Dictionary<MonsterContral, HashSet<Collider2D>> _tracked =
new Dictionary<MonsterContral, HashSet<Collider2D>>();
/// <summary>碰撞器 → 所属鱼(Exit 时快速反查,避免再 GetComponentInParent)</summary>
private readonly Dictionary<Collider2D, MonsterContral> _colliderOwner =
new Dictionary<Collider2D, MonsterContral>();
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 配置");
}
/// <summary>这个碰撞器是不是"要检测的鱼"(tag + layer 都符合才受理)</summary>
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;
}
/// <summary>进入触发区:登记这条鱼(开始出发时存起来)</summary>
private void OnTriggerEnter2D(Collider2D other)
{
if (!IsFishCollider(other)) return;
// 鱼脚本挂在根节点,碰撞器可能是子物体 → 往上找
var monster = other.GetComponentInParent<MonsterContral>();
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<Collider2D>();
_tracked[monster] = set;
}
set.Add(other);
_colliderOwner[other] = monster;
if (enableLog)
Debug.Log($"[出界检测] 进入触发区 guid={monster.guid} collider={other.name} 该鱼碰撞器数={set.Count}");
}
/// <summary>离开触发区:全部碰撞器都离开后,再按路线进度决定是否回收</summary>
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);
}
/// <summary>所有碰撞器都出界后:路线进度超过阈值才回收</summary>
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);
}
}