using UnityEngine;
using Unity.Cinemachine;
using BaseGames.Core;
namespace BaseGames.Camera
{
///
/// 相机状态单例控制器。须放置在 Persistent 场景中。
///
/// 支持两种相机切换模式:
/// 1. 全局双 VCam 模式(推荐):
/// 两台全局 CinemachineCamera(_vcamA / _vcamB)交替承接各区域,
/// 通过优先级 ping-pong 触发 Cinemachine 混合过渡。场景内无需每个区域都放置 VCam。
///
/// 2. 专有 VCam 模式:(区域含 dedicatedCamera 时自动使用)
/// 激活该区域专属的 CinemachineCamera(优先级 > 全局 VCam),
/// 适用于需要独特相机参数的特殊区域。
///
[DefaultExecutionOrder(-100)]
public class CameraStateController : MonoBehaviour, ICameraService
{
[Header("引用")]
[SerializeField] private CinemachineBrain _brain;
[SerializeField] private CinemachineImpulseSource _impulseSource;
[Header("全局双 VCam(Persistent 场景中放置两台通用虚拟相机)")]
[Tooltip("两台 VCam 交替承接各相机区域,通过优先级 ping-pong 触发混合过渡。\n" +
"须各自挂载 CinemachineCamera + CinemachineConfiner2D;\n" +
"Follow 指向 Player/CameraFollowTarget(或运行时调用 SetFollowTarget 赋值)。")]
[SerializeField] private CinemachineCamera _vcamA;
[SerializeField] private CinemachineCamera _vcamB;
[Tooltip("全局 VCam 激活时的优先级(非活跃时为 0)。专有 VCam 的 _dedicatedPriority 须高于此值。")]
[SerializeField] private int _globalActivePriority = 10;
[Header("默认混合配置")]
[SerializeField] private CameraBlendProfileSO _defaultBlendProfile;
// ── 状态 ──────────────────────────────────────────────────────────────
private int _activeSlot = -1; // -1 = 未初始化;0 = A;1 = B
private CinemachineCamera _activeDedicatedCam;
private CinemachineConfiner2D _confinerA;
private CinemachineConfiner2D _confinerB;
// ── Lifecycle ────────────────────────────────────────────────────────
private void Awake()
{
if (ServiceLocator.GetOrDefault() != null) { Destroy(gameObject); return; }
ServiceLocator.Register(this);
// 缓存 Confiner 引用
if (_vcamA != null) _confinerA = _vcamA.GetComponent();
if (_vcamB != null) _confinerB = _vcamB.GetComponent();
// 初始两台 VCam 均处于非活跃优先级
if (_vcamA != null) _vcamA.Priority = 0;
if (_vcamB != null) _vcamB.Priority = 0;
}
private void OnDestroy()
{
ServiceLocator.Unregister(this);
}
// ── 公开 API ──────────────────────────────────────────────────────────
///
/// 切换到目标相机区域。
///
/// - 区域有专有 VCam → 激活它(高优先级),全局 VCam 保持当前状态。
/// - 区域无专有 VCam → 配置非活跃全局 VCam,ping-pong 切换优先级触发混合。
///
///
public void SwitchArea(CameraArea targetArea)
{
if (targetArea == null) return;
ApplyBlendProfile(targetArea.BlendProfile ?? _defaultBlendProfile);
if (targetArea.HasDedicated)
ActivateDedicated(targetArea);
else
ActivateGlobalSlot(targetArea);
}
///
/// 运行时为两台全局 VCam 统一设置跟随目标(如 Player/CameraFollowTarget)。
/// 可在 Player 生成后由任意系统调用。
///
public void SetFollowTarget(Transform followTarget)
{
if (_vcamA != null) _vcamA.Follow = followTarget;
if (_vcamB != null) _vcamB.Follow = followTarget;
}
/// 触发屏幕抖动。
public void TriggerImpulse(Vector3 velocity)
{
if (_impulseSource != null) _impulseSource.GenerateImpulse(velocity);
}
/// 以默认强度触发屏幕抖动。
public void TriggerImpulse(float strength = 0.3f)
=> TriggerImpulse(Vector3.down * strength);
// ── 内部方法 ──────────────────────────────────────────────────────────
/// 激活区域的专有 VCam(高优先级)。
private void ActivateDedicated(CameraArea area)
{
// 降低前一个专有 VCam(若与新的不同)
if (_activeDedicatedCam != null && _activeDedicatedCam != area.DedicatedCamera)
_activeDedicatedCam.Priority = 0;
_activeDedicatedCam = area.DedicatedCamera;
_activeDedicatedCam.Priority = area.DedicatedPriority;
}
///
/// 使用全局 VCam ping-pong 切换到新区域。
/// 配置非活跃 VCam 的 Confiner → 提升其优先级 → 降低旧 VCam 优先级。
/// Cinemachine Brain 检测到优先级变化后自动触发混合。
///
private void ActivateGlobalSlot(CameraArea area)
{
// 收回专有 VCam
if (_activeDedicatedCam != null)
{
_activeDedicatedCam.Priority = 0;
_activeDedicatedCam = null;
}
bool noVCams = _vcamA == null && _vcamB == null;
if (noVCams)
{
Debug.LogWarning("[CameraStateController] 全局 VCam A / B 均未绑定,无法切换相机区域。");
return;
}
// 首次调用:直接激活 VCamA(场景淡入阶段,无需混合动画)
if (_activeSlot < 0)
{
var cam = _vcamA ?? _vcamB;
var confiner = _vcamA != null ? _confinerA : _confinerB;
ConfigureSlot(cam, confiner, area);
cam.Priority = _globalActivePriority;
_activeSlot = _vcamA != null ? 0 : 1;
return;
}
// Ping-pong:配置非活跃槽 → 升级其优先级 → 降低活跃槽优先级
bool nextIsA = _activeSlot != 0;
var inactiveCam = nextIsA ? _vcamA : _vcamB;
var activeCam = nextIsA ? _vcamB : _vcamA;
var inactiveConfiner = nextIsA ? _confinerA : _confinerB;
// 只有一台 VCam 时降级处理(仍能工作,但无混合动画)
if (inactiveCam == null) inactiveCam = activeCam;
ConfigureSlot(inactiveCam, inactiveConfiner, area);
inactiveCam.Priority = _globalActivePriority;
activeCam.Priority = 0;
_activeSlot = nextIsA ? 0 : 1;
}
private static void ConfigureSlot(
CinemachineCamera vcam, CinemachineConfiner2D confiner, CameraArea area)
{
if (confiner != null && area.ConfinerCollider != null)
confiner.BoundingShape2D = area.ConfinerCollider;
}
private void ApplyBlendProfile(CameraBlendProfileSO profile)
{
if (_brain != null && profile != null)
_brain.DefaultBlend = profile.ToBlendDefinition();
}
}
}