摄像机区域的优化
This commit is contained in:
@@ -1,46 +1,173 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using BaseGames.Core;
|
||||
|
||||
namespace BaseGames.Camera
|
||||
{
|
||||
/// <summary>
|
||||
/// 相机区域切换触发器。玩家进入时通知 <see cref="CameraStateController"/> 切换到目标 <see cref="CameraArea"/>。
|
||||
/// 相机区域切换触发器。
|
||||
/// 当触发区域重叠时,玩家必须先离开当前所在的触发区域,才会切换到下一个区域,
|
||||
/// 而不是进入重叠区域时立即切换。
|
||||
/// </summary>
|
||||
[ExecuteAlways]
|
||||
[RequireComponent(typeof(BoxCollider2D))]
|
||||
[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 BoxCollider2D _collider;
|
||||
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<BoxCollider2D>();
|
||||
_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.Start(priority=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;
|
||||
|
||||
var service = ServiceLocator.GetOrDefault<ICameraService>();
|
||||
if (service == null) return;
|
||||
_isPlayerInside = true;
|
||||
s_InsideZones.Add(this);
|
||||
|
||||
if (_targetArea != null)
|
||||
service.SwitchArea(_targetArea);
|
||||
// 没有激活的触发区域 → 立即切换
|
||||
// 已有激活的触发区域 → 等玩家离开后再接管(避免重叠区域间提前切换)
|
||||
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<BoxCollider2D>();
|
||||
Gizmos.color = new Color(0.2f, 0.8f, 1f, 0.25f);
|
||||
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;
|
||||
Gizmos.DrawCube(_collider.offset, _collider.size);
|
||||
Vector2 off = _collider.offset;
|
||||
Gizmos.color = new Color(0.2f, 0.8f, 1f, 0.8f);
|
||||
Gizmos.DrawWireCube(_collider.offset, _collider.size);
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user