Files
zeling_v2/Assets/_Game/Scripts/Camera/CameraTriggerZone.cs
2026-05-17 07:56:12 +08:00

174 lines
6.8 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.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using BaseGames.Core;
namespace BaseGames.Camera
{
/// <summary>
/// 相机区域切换触发器。
/// 当触发区域重叠时,玩家必须先离开当前所在的触发区域,才会切换到下一个区域,
/// 而不是进入重叠区域时立即切换。
/// </summary>
[ExecuteAlways]
[RequireComponent(typeof(PolygonCollider2D))]
public class CameraTriggerZone : MonoBehaviour
{
[SerializeField] private CameraArea _targetArea;
[Tooltip("玩家离开此触发区域时回退到的区域(留空则退出时不做处理)。\n" +
"通常设为上级/相邻的包含区域,使玩家返回时相机自然过渡。")]
[SerializeField] private CameraArea _exitFallbackArea;
[Tooltip("触发区域优先级。同时在多个触发区域内时,高优先级区域胜出。\n" +
"相同优先级则后进入的胜出(推荐默认值 1。")]
[SerializeField] private int _priority = 1;
[SerializeField] private string _playerTag = "Player";
private PolygonCollider2D _collider;
private bool _isPlayerInside;
// ── 静态:跨实例共享触发状态 ──────────────────────────────────────────
// 玩家当前物理上所在的所有触发区域(按进入顺序排列)
private static readonly List<CameraTriggerZone> s_InsideZones = new();
// 当前已向 ICameraService 发出 SwitchArea 请求的触发区域
private static CameraTriggerZone s_ActiveZone;
private void Awake()
{
_collider = GetComponent<PolygonCollider2D>();
_collider.isTrigger = true;
}
private void OnDisable()
{
if (!Application.isPlaying) return;
if (!_isPlayerInside) return;
_isPlayerInside = false;
s_InsideZones.Remove(this);
if (s_ActiveZone == this)
Deactivate(this);
}
/// <summary>
/// 若玩家出生时已在触发区域内OnTriggerEnter2D 不会触发。
/// 延迟一帧(确保 RoomController.Start 先完成基准区域设置)后主动检测。
/// </summary>
private IEnumerator Start()
{
if (!Application.isPlaying) yield break;
// 等一帧:让 RoomController.Startpriority=0先建立基准区域
// 再以 _priority 叠加子区域,保证栈顺序正确。
yield return null;
if (_targetArea == null) yield break;
GameObject player = GameObject.FindWithTag(_playerTag);
if (player == null || !_collider.OverlapPoint(player.transform.position)) yield break;
// OnTriggerEnter2D 可能已先一步处理,避免重复加入
if (!_isPlayerInside)
{
_isPlayerInside = true;
s_InsideZones.Add(this);
}
if (s_ActiveZone == null)
Activate(this);
}
private void OnTriggerEnter2D(Collider2D other)
{
if (!Application.isPlaying) return;
if (!other.CompareTag(_playerTag)) return;
if (_targetArea == null || _isPlayerInside) return;
_isPlayerInside = true;
s_InsideZones.Add(this);
// 没有激活的触发区域 → 立即切换
// 已有激活的触发区域 → 等玩家离开后再接管(避免重叠区域间提前切换)
if (s_ActiveZone == null)
Activate(this);
}
private void OnTriggerExit2D(Collider2D other)
{
if (!Application.isPlaying) return;
if (!other.CompareTag(_playerTag)) return;
if (!_isPlayerInside) return;
_isPlayerInside = false;
s_InsideZones.Remove(this);
if (s_ActiveZone == this)
Deactivate(this);
}
// ── 静态辅助方法 ────────────────────────────────────────────────────────
private static void Activate(CameraTriggerZone zone)
{
s_ActiveZone = zone;
ServiceLocator.GetOrDefault<ICameraService>()?.SwitchArea(zone._targetArea, zone._priority);
}
/// <summary>
/// 离开 <paramref name="leaving"/> 时的处理:
/// 若还有其他触发区域,先激活最优者再释放 leaving避免短暂回退到房间基线
/// 否则直接释放并使用 <see cref="_exitFallbackArea"/>。
/// </summary>
private static void Deactivate(CameraTriggerZone leaving)
{
ICameraService svc = ServiceLocator.GetOrDefault<ICameraService>();
if (s_InsideZones.Count > 0)
{
// 先激活下一个,再释放 leaving —— 此时 _currentArea 已更新为 next
// ReleaseArea(leaving) 中 wasActive=false仅从 _activeZones 移除,不触发额外跳转
CameraTriggerZone next = SelectBest();
s_ActiveZone = next;
svc?.SwitchArea(next._targetArea, next._priority);
svc?.ReleaseArea(leaving._targetArea, null);
}
else
{
s_ActiveZone = null;
svc?.ReleaseArea(leaving._targetArea, leaving._exitFallbackArea);
}
}
/// <summary>从 <see cref="s_InsideZones"/> 中选出优先级最高的区域。</summary>
private static CameraTriggerZone SelectBest()
{
CameraTriggerZone best = s_InsideZones[0];
for (int i = 1; i < s_InsideZones.Count; i++)
if (s_InsideZones[i]._priority > best._priority)
best = s_InsideZones[i];
return best;
}
private void OnDrawGizmos()
{
if (_collider == null) _collider = GetComponent<PolygonCollider2D>();
if (_collider == null || _collider.pathCount == 0) return;
var pts = new System.Collections.Generic.List<Vector2>();
_collider.GetPath(0, pts);
if (pts.Count < 2) return;
Gizmos.matrix = transform.localToWorldMatrix;
Vector2 off = _collider.offset;
Gizmos.color = new Color(0.2f, 0.8f, 1f, 0.8f);
for (int i = 0; i < pts.Count; i++)
{
Vector2 a = pts[i] + off;
Vector2 b = pts[(i + 1) % pts.Count] + off;
Gizmos.DrawLine(new Vector3(a.x, a.y), new Vector3(b.x, b.y));
}
}
}
}