404 lines
13 KiB
C#
404 lines
13 KiB
C#
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<TrackCrashController>();
|
||
}
|
||
}
|
||
|
||
// 捕获漂移 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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 按分辨率微调相机:在设计基准上叠加 X 旋转增量与 Z 位置增量。
|
||
/// 改的是漂移 pivot(不动相机本身),且同步 cameraDriftBaseLocalPosition.z,
|
||
/// 与漂移/抖动/技能偏移/冲刺全部不冲突。可重复调用(总是相对设计基准,不累积)。
|
||
/// </summary>
|
||
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<SmoothShake>();
|
||
if (shake == null)
|
||
{
|
||
shake = cachedShakeTarget.gameObject.AddComponent<SmoothShake>();
|
||
}
|
||
|
||
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<effectEventController>();
|
||
if (controller == null)
|
||
return;
|
||
|
||
controller.CacheShakeTarget();
|
||
controller.UpdatePlayerSkillCameraOffset();
|
||
}
|
||
|
||
public static void TryTriggerMultiNoteShake()
|
||
{
|
||
effectEventController controller = Instance != null ? Instance : SceneObjectLookupCache.FindAny<effectEventController>();
|
||
if (controller == null)
|
||
return;
|
||
|
||
controller.TriggerMultiNoteShakeInternal();
|
||
}
|
||
|
||
public static void TryTriggerTrackCrash(int trackIndex)
|
||
{
|
||
effectEventController controller = Instance != null ? Instance : SceneObjectLookupCache.FindAny<effectEventController>();
|
||
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<SmoothShake>();
|
||
if (shake == null)
|
||
{
|
||
shake = cachedShakeTarget.gameObject.AddComponent<SmoothShake>();
|
||
}
|
||
|
||
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);
|
||
}
|
||
}
|