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(); } } }