角色能力,存档
This commit is contained in:
@@ -5,10 +5,27 @@ using BaseGames.Core;
|
||||
|
||||
namespace BaseGames.Camera
|
||||
{
|
||||
/// <summary>
|
||||
/// 相机区域切换模式。
|
||||
/// </summary>
|
||||
public enum CameraZoneSwitchMode
|
||||
{
|
||||
/// <summary>进入即切换(默认)。
|
||||
/// 只要走进触发区域就立刻切换,不需要完全离开当前区域。</summary>
|
||||
Immediate,
|
||||
|
||||
/// <summary>必须离开当前区域才切换。
|
||||
/// 进入新区域后仅将其加入候选列表,等玩家完全离开当前激活区域后再接管。</summary>
|
||||
ExitFirst,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 相机区域切换触发器。
|
||||
/// 当触发区域重叠时,玩家必须先离开当前所在的触发区域,才会切换到下一个区域,
|
||||
/// 而不是进入重叠区域时立即切换。
|
||||
/// 支持两种切换模式,可通过 Inspector 配置:
|
||||
/// <list type="bullet">
|
||||
/// <item><b>Immediate</b>:进入即切换,不等待离开旧区域。</item>
|
||||
/// <item><b>ExitFirst</b>:必须离开当前激活区域后才切换。</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[ExecuteAlways]
|
||||
[RequireComponent(typeof(PolygonCollider2D))]
|
||||
@@ -24,17 +41,36 @@ namespace BaseGames.Camera
|
||||
"相同优先级则后进入的胜出(推荐默认值 1)。")]
|
||||
[SerializeField] private int _priority = 1;
|
||||
|
||||
[Tooltip("切换模式。\n" +
|
||||
"Immediate:进入即切换,无需离开当前区域(默认)。\n" +
|
||||
"ExitFirst:必须离开当前激活区域后才切换。")]
|
||||
[SerializeField] private CameraZoneSwitchMode _switchMode = CameraZoneSwitchMode.Immediate;
|
||||
|
||||
[SerializeField] private string _playerTag = "Player";
|
||||
|
||||
private PolygonCollider2D _collider;
|
||||
private bool _isPlayerInside;
|
||||
|
||||
/// <summary>触发区域优先级(只读),供外部按优先级选择最佳区域。</summary>
|
||||
public int Priority => _priority;
|
||||
|
||||
// ── 静态:跨实例共享触发状态 ──────────────────────────────────────────
|
||||
// 玩家当前物理上所在的所有触发区域(按进入顺序排列)
|
||||
private static readonly List<CameraTriggerZone> s_InsideZones = new();
|
||||
// 当前已向 ICameraService 发出 SwitchArea 请求的触发区域
|
||||
private static CameraTriggerZone s_ActiveZone;
|
||||
|
||||
/// <summary>
|
||||
/// 在每次进入 Play Mode 前(或禁用 Domain Reload 时的跨会话)重置静态状态,
|
||||
/// 防止上一次游戏会话残留的区域引用导致触发逻辑错误。
|
||||
/// </summary>
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
private static void ResetStaticState()
|
||||
{
|
||||
s_InsideZones.Clear();
|
||||
s_ActiveZone = null;
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_collider = GetComponent<PolygonCollider2D>();
|
||||
@@ -44,12 +80,7 @@ namespace BaseGames.Camera
|
||||
private void OnDisable()
|
||||
{
|
||||
if (!Application.isPlaying) return;
|
||||
if (!_isPlayerInside) return;
|
||||
|
||||
_isPlayerInside = false;
|
||||
s_InsideZones.Remove(this);
|
||||
if (s_ActiveZone == this)
|
||||
Deactivate(this);
|
||||
HandlePlayerExit();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -76,46 +107,103 @@ namespace BaseGames.Camera
|
||||
s_InsideZones.Add(this);
|
||||
}
|
||||
|
||||
if (s_ActiveZone == null)
|
||||
Activate(this);
|
||||
EvaluateAndSwitch();
|
||||
}
|
||||
|
||||
private void OnTriggerEnter2D(Collider2D other)
|
||||
{
|
||||
if (!Application.isPlaying) return;
|
||||
if (!other.CompareTag(_playerTag)) return;
|
||||
// 兼容碰撞体挂在子节点的玩家结构:先检查碰撞体本身标签,
|
||||
// 再检查其挂载的 Rigidbody2D 所在节点标签(通常为带标签的角色根节点)。
|
||||
if (!other.CompareTag(_playerTag) &&
|
||||
(other.attachedRigidbody == null || !other.attachedRigidbody.CompareTag(_playerTag)))
|
||||
return;
|
||||
if (_targetArea == null || _isPlayerInside) return;
|
||||
|
||||
_isPlayerInside = true;
|
||||
s_InsideZones.Add(this);
|
||||
|
||||
// 没有激活的触发区域 → 立即切换
|
||||
// 已有激活的触发区域 → 等玩家离开后再接管(避免重叠区域间提前切换)
|
||||
if (s_ActiveZone == null)
|
||||
Activate(this);
|
||||
// Immediate:进入即评估切换。
|
||||
// ExitFirst:仅在当前无激活区域时才先先激活,否则等待玩家离开当前激活区域。
|
||||
if (_switchMode == CameraZoneSwitchMode.Immediate || s_ActiveZone == null)
|
||||
EvaluateAndSwitch();
|
||||
}
|
||||
|
||||
private void OnTriggerExit2D(Collider2D other)
|
||||
{
|
||||
if (!Application.isPlaying) return;
|
||||
if (!other.CompareTag(_playerTag)) return;
|
||||
if (!_isPlayerInside) return;
|
||||
if (!other.CompareTag(_playerTag) &&
|
||||
(other.attachedRigidbody == null || !other.attachedRigidbody.CompareTag(_playerTag)))
|
||||
return;
|
||||
|
||||
// 复合碰撞体场景:某个子碰撞体退出时,验证玩家根节点是否仍在区域内。
|
||||
// 若根节点还在区域内(其他碰撞体尚未退出),则忽略此次退出事件。
|
||||
Transform playerRoot = other.attachedRigidbody != null
|
||||
? other.attachedRigidbody.transform
|
||||
: other.transform;
|
||||
if (_collider != null && _collider.OverlapPoint(playerRoot.position)) return;
|
||||
|
||||
HandlePlayerExit();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 玩家离开触发区域的统一处理(<see cref="OnTriggerExit2D"/>、
|
||||
/// <see cref="FixedUpdate"/> 边缘检测及 <see cref="OnDisable"/> 共同调用)。
|
||||
/// 带幂等保护,多次调用安全。
|
||||
/// </summary>
|
||||
private void HandlePlayerExit()
|
||||
{
|
||||
if (!_isPlayerInside) return; // 幂等保护:防止重复触发
|
||||
|
||||
_isPlayerInside = false;
|
||||
s_InsideZones.Remove(this);
|
||||
|
||||
if (s_ActiveZone == this)
|
||||
Deactivate(this);
|
||||
else
|
||||
ServiceLocator.GetOrDefault<ICameraService>()?.ReleaseArea(_targetArea, null);
|
||||
}
|
||||
|
||||
// ── 静态辅助方法 ────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// 评估 <see cref="s_InsideZones"/> 并在需要时切换激活区域。
|
||||
/// <list type="bullet">
|
||||
/// <item>无激活区域时:直接激活最后进入的区域。</item>
|
||||
/// <item>新 <c>SelectBest()</c> 与当前激活不同时:立即覆盖切换。
|
||||
/// 由于 <c>SelectBest</c> 使用 >= 规则,进入任何新区域都会触发切换。</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
private static void EvaluateAndSwitch()
|
||||
{
|
||||
if (s_ActiveZone == null)
|
||||
{
|
||||
Activate(s_InsideZones[s_InsideZones.Count - 1]);
|
||||
return;
|
||||
}
|
||||
|
||||
CameraTriggerZone best = SelectBest();
|
||||
if (best != s_ActiveZone)
|
||||
OverrideActive(best);
|
||||
}
|
||||
|
||||
private static void Activate(CameraTriggerZone zone)
|
||||
{
|
||||
s_ActiveZone = zone;
|
||||
ServiceLocator.GetOrDefault<ICameraService>()?.SwitchArea(zone._targetArea, zone._priority);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 不经过 Exit 事件,直接将激活区域切换为 <paramref name="newZone"/>。
|
||||
/// 旧区域保留在 <see cref="s_InsideZones"/> 中(玩家仍在其内部),
|
||||
/// 不立即释放旧区域——等玩家物理离开旧区域时由 <see cref="OnTriggerExit2D"/> 清理。
|
||||
/// </summary>
|
||||
private static void OverrideActive(CameraTriggerZone newZone)
|
||||
{
|
||||
s_ActiveZone = newZone;
|
||||
ServiceLocator.GetOrDefault<ICameraService>()?.SwitchArea(newZone._targetArea, newZone._priority);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 离开 <paramref name="leaving"/> 时的处理:
|
||||
/// 若还有其他触发区域,先激活最优者再释放 leaving(避免短暂回退到房间基线);
|
||||
@@ -140,12 +228,16 @@ namespace BaseGames.Camera
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>从 <see cref="s_InsideZones"/> 中选出优先级最高的区域。</summary>
|
||||
/// <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)
|
||||
if (s_InsideZones[i]._priority >= best._priority) // >= 使同优先级时后进入的胜出
|
||||
best = s_InsideZones[i];
|
||||
return best;
|
||||
}
|
||||
@@ -155,19 +247,19 @@ namespace BaseGames.Camera
|
||||
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);
|
||||
var pts = new List<Vector2>();
|
||||
_collider.GetPath(0, pts);
|
||||
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));
|
||||
Vector3 a = new Vector3(pts[i].x, pts[i].y, 0f);
|
||||
Vector3 b = new Vector3(pts[(i + 1) % pts.Count].x, pts[(i + 1) % pts.Count].y, 0f);
|
||||
Gizmos.DrawLine(a, b);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user