using SmoothShakeFree; using UnityEngine; public class effectEventController : MonoBehaviour { public enum CameraOffsetAxis { X, Y, Z } public static effectEventController Instance { get; private set; } [Header("Multi Note Screen Shake")] public bool enableMultiNoteScreenShake = true; public float multiNoteScreenShakeAmplitude = 0.08f; [Header("Camera Drift")] public bool enableCameraDrift = false; public Vector2 cameraDriftRange = new Vector2(0.15f, 0.08f); public float cameraDriftSpeed = 0.3f; [Header("Player Skill 10 Camera Offset")] public float playerSkillCameraOffsetAmount = 0.6f; public CameraOffsetAxis playerSkillCameraOffsetAxis = CameraOffsetAxis.Y; [Header("Track Crash")] [SerializeField] private TrackCrashController trackCrashController; private Transform cachedShakeTarget; private Transform cachedCameraDriftPivot; private Transform cachedCameraSkillPivot; private Vector3 cameraDriftBaseLocalPosition; private Vector3 cameraSkillBaseLocalPosition; private float cameraDriftSeedX; private float cameraDriftSeedY; // 分辨率相机微调:改的是"漂移 pivot"(顶层节点,承载 -40°/-8 基准),不碰相机自身, // 因此不与 SmoothShake(写相机 local)、技能偏移(写技能 pivot)、冲刺(写相机 z)冲突。 // 改 pivot 的 X 旋转无任何系统覆盖;改 pivot 的 Z 必须同步 cameraDriftBaseLocalPosition.z, // 否则 UpdateCameraDrift/ResetCameraDriftIfNeeded 会用旧基准把 z 拉回。 private bool _cameraBaseCaptured; private float _designPivotRotX; // pivot 初始 X 旋转(如 -40) private float _designPivotZ; // pivot 初始 Z(如 -8) private void Awake() { if (Instance == null) { Instance = this; cameraDriftSeedX = Random.Range(0f, 1000f); cameraDriftSeedY = Random.Range(1000f, 2000f); CacheShakeTarget(); return; } if (Instance != this) { Destroy(gameObject); } } private void OnEnable() { CacheShakeTarget(); ResetCameraDriftIfNeeded(); CacheTrackCrashController(); } // 门控:漂移关闭且已归位后,跳过每帧的 Perlin/缓存查找;开关切换时才归位一次。 private bool _driftResetDone; private void Update() { if (enableCameraDrift) { _driftResetDone = false; UpdateCameraDrift(); } else if (!_driftResetDone) { // 关闭瞬间归位一次,之后不再每帧空跑。 UpdateCameraDrift(); _driftResetDone = true; } UpdatePlayerSkillCameraOffset(); } private void CacheShakeTarget() { if (Camera.main != null) { cachedShakeTarget = Camera.main.transform; PrewarmShakeComponent(); EnsureCameraDriftPivot(); EnsureCameraSkillPivot(); } } private void CacheTrackCrashController() { if (trackCrashController == null) { trackCrashController = SceneObjectLookupCache.FindAny(); } } // 捕获漂移 pivot 的设计基准(X旋转/Z)一次。pivot 建好后调用即安全。 private void CaptureCameraBaseIfNeeded() { if (_cameraBaseCaptured) return; if (cachedCameraDriftPivot == null) { CacheShakeTarget(); // 确保 pivot 已建立 if (cachedCameraDriftPivot == null) return; } _designPivotRotX = cachedCameraDriftPivot.localEulerAngles.x; _designPivotZ = cachedCameraDriftPivot.localPosition.z; _cameraBaseCaptured = true; } /// /// 按分辨率微调相机:在设计基准上叠加 X 旋转增量与 Z 位置增量。 /// 改的是漂移 pivot(不动相机本身),且同步 cameraDriftBaseLocalPosition.z, /// 与漂移/抖动/技能偏移/冲刺全部不冲突。可重复调用(总是相对设计基准,不累积)。 /// public void SetResolutionCameraAdjust(float extraRotX, float extraZ) { CaptureCameraBaseIfNeeded(); if (cachedCameraDriftPivot == null) return; // X 旋转:设计值 + 增量。pivot 的 rotation 无系统写入,直接设安全。 Vector3 euler = cachedCameraDriftPivot.localEulerAngles; euler.x = _designPivotRotX + extraRotX; cachedCameraDriftPivot.localEulerAngles = euler; // Z:设计值 + 增量,并同步漂移基准 z(漂移只改 x,y,z 恒取基准)。 float targetZ = _designPivotZ + extraZ; Vector3 lp = cachedCameraDriftPivot.localPosition; lp.z = targetZ; cachedCameraDriftPivot.localPosition = lp; cameraDriftBaseLocalPosition.z = targetZ; // 关键:否则会被 UpdateCameraDrift/Reset 拉回 } private void PrewarmShakeComponent() { if (cachedShakeTarget == null) return; SmoothShake shake = cachedShakeTarget.GetComponent(); if (shake == null) { shake = cachedShakeTarget.gameObject.AddComponent(); } if (shake.positionShake == null) shake.positionShake = new Shaker(); if (shake.rotationShake == null) shake.rotationShake = new Shaker(); if (shake.shakers == null || shake.shakers.Length != 2) shake.shakers = new[] { shake.positionShake, shake.rotationShake }; if (shake.sum == null || shake.sum.Length != 2) shake.sum = new Vector3[2]; } private void EnsureCameraDriftPivot() { if (cachedShakeTarget == null) return; if (cachedCameraDriftPivot != null) return; Transform currentParent = cachedShakeTarget.parent; if (currentParent != null && currentParent.name == "__effect_event_camera_drift_pivot") { cachedCameraDriftPivot = currentParent; cameraDriftBaseLocalPosition = cachedCameraDriftPivot.localPosition; return; } GameObject pivotObject = new GameObject("__effect_event_camera_drift_pivot"); Transform pivot = pivotObject.transform; if (currentParent != null) { pivot.SetParent(currentParent, false); pivot.localPosition = cachedShakeTarget.localPosition; pivot.localRotation = cachedShakeTarget.localRotation; pivot.localScale = Vector3.one; } else { pivot.position = cachedShakeTarget.position; pivot.rotation = cachedShakeTarget.rotation; } cachedShakeTarget.SetParent(pivot, true); cachedShakeTarget.localRotation = Quaternion.identity; cachedCameraDriftPivot = pivot; cameraDriftBaseLocalPosition = cachedCameraDriftPivot.localPosition; } private void EnsureCameraSkillPivot() { if (cachedShakeTarget == null) return; if (cachedCameraSkillPivot != null) return; Transform currentParent = cachedShakeTarget.parent; if (currentParent != null && currentParent.name == "__effect_event_camera_skill_pivot") { cachedCameraSkillPivot = currentParent; cameraSkillBaseLocalPosition = cachedCameraSkillPivot.localPosition; return; } GameObject pivotObject = new GameObject("__effect_event_camera_skill_pivot"); Transform pivot = pivotObject.transform; if (currentParent != null) { pivot.SetParent(currentParent, false); pivot.localPosition = cachedShakeTarget.localPosition; pivot.localRotation = cachedShakeTarget.localRotation; pivot.localScale = Vector3.one; } else { pivot.position = cachedShakeTarget.position; pivot.rotation = cachedShakeTarget.rotation; pivot.localScale = Vector3.one; } cachedShakeTarget.SetParent(pivot, true); cachedShakeTarget.localPosition = Vector3.zero; cachedShakeTarget.localRotation = Quaternion.identity; cachedCameraSkillPivot = pivot; cameraSkillBaseLocalPosition = cachedCameraSkillPivot.localPosition; } private void UpdateCameraDrift() { if (cachedShakeTarget == null) { CacheShakeTarget(); } if (cachedShakeTarget == null) return; EnsureCameraDriftPivot(); if (cachedCameraDriftPivot == null) return; if (!enableCameraDrift) { ResetCameraDriftIfNeeded(); return; } float speed = Mathf.Max(0f, cameraDriftSpeed); float time = GameplayClock.NowSongTime * speed; float offsetX = (Mathf.PerlinNoise(cameraDriftSeedX, time) - 0.5f) * 2f * Mathf.Abs(cameraDriftRange.x); float offsetY = (Mathf.PerlinNoise(cameraDriftSeedY, time) - 0.5f) * 2f * Mathf.Abs(cameraDriftRange.y); cachedCameraDriftPivot.localPosition = cameraDriftBaseLocalPosition + new Vector3(offsetX, offsetY, 0f); } private void ResetCameraDriftIfNeeded() { if (cachedCameraDriftPivot == null) return; cachedCameraDriftPivot.localPosition = cameraDriftBaseLocalPosition; } private void UpdatePlayerSkillCameraOffset() { if (cachedShakeTarget == null) { CacheShakeTarget(); } if (cachedShakeTarget == null) return; EnsureCameraSkillPivot(); if (cachedCameraSkillPivot == null) return; Vector3 targetLocalPosition = cameraSkillBaseLocalPosition; if (PlayerSkillService.IsSkillEnabled(10)) { float offset = playerSkillCameraOffsetAmount; switch (playerSkillCameraOffsetAxis) { case CameraOffsetAxis.X: targetLocalPosition.x += offset; break; case CameraOffsetAxis.Y: targetLocalPosition.y += offset; break; case CameraOffsetAxis.Z: targetLocalPosition.z += offset; break; } } if (cachedCameraSkillPivot.localPosition != targetLocalPosition) { cachedCameraSkillPivot.localPosition = targetLocalPosition; } } public static void RefreshPlayerSkillCameraOffset() { effectEventController controller = Instance != null ? Instance : SceneObjectLookupCache.FindAny(); if (controller == null) return; controller.CacheShakeTarget(); controller.UpdatePlayerSkillCameraOffset(); } public static void TryTriggerMultiNoteShake() { effectEventController controller = Instance != null ? Instance : SceneObjectLookupCache.FindAny(); if (controller == null) return; controller.TriggerMultiNoteShakeInternal(); } public static void TryTriggerTrackCrash(int trackIndex) { effectEventController controller = Instance != null ? Instance : SceneObjectLookupCache.FindAny(); if (controller == null) return; controller.TriggerTrackCrashInternal(trackIndex); } private void TriggerMultiNoteShakeInternal() { if (!enableMultiNoteScreenShake) return; if (cachedShakeTarget == null) { CacheShakeTarget(); } if (cachedShakeTarget == null) return; float amplitudeValue = Mathf.Max(0f, multiNoteScreenShakeAmplitude); if (amplitudeValue <= 0f) return; SmoothShake shake = cachedShakeTarget.GetComponent(); if (shake == null) { shake = cachedShakeTarget.gameObject.AddComponent(); } if (shake.positionShake == null) shake.positionShake = new Shaker(); if (shake.rotationShake == null) shake.rotationShake = new Shaker(); shake.positionShake.noiseType = Shaker.NoiseType.SineWave; shake.positionShake.amplitude = new Vector3(amplitudeValue, amplitudeValue, 0f); shake.positionShake.frequency = new Vector3(20f, 20f, 0f); shake.rotationShake.amplitude = Vector3.zero; shake.rotationShake.frequency = Vector3.zero; shake.timeSettings.constantShake = false; shake.timeSettings.fadeInDuration = 0.03f; shake.timeSettings.holdDuration = 0.04f; shake.timeSettings.fadeOutDuration = 0.05f; if (shake.timeSettings.fadeInCurve == null || shake.timeSettings.fadeInCurve.length == 0) shake.timeSettings.fadeInCurve = AnimationCurve.Linear(0f, 0f, 1f, 1f); if (shake.timeSettings.fadeOutCurve == null || shake.timeSettings.fadeOutCurve.length == 0) shake.timeSettings.fadeOutCurve = AnimationCurve.Linear(0f, 1f, 1f, 0f); shake.shakers = new[] { shake.positionShake, shake.rotationShake }; shake.sum = new Vector3[2]; shake.StartShake(); } private void TriggerTrackCrashInternal(int trackIndex) { CacheTrackCrashController(); if (trackCrashController == null) return; trackCrashController.TriggerTrackCrash(trackIndex); } }