From e79c57a40fcdbb831ca1f4b2d638b585819cf822 Mon Sep 17 00:00:00 2001
From: CL <62653664+Chen-Luan@users.noreply.github.com>
Date: Mon, 23 Mar 2026 17:18:12 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=B8=A6=E5=B9=B3?=
=?UTF-8?q?=E6=BB=91=E7=9A=84DSP=E8=AE=A1=E6=97=B6=E5=99=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Scripts/Framework/Timer/SmoothDspTimer.cs | 119 ++++++++++++++++++
.../Framework/Timer/SmoothDspTimer.cs.meta | 2 +
2 files changed, 121 insertions(+)
create mode 100644 Cyan-Stars/Assets/Scripts/Framework/Timer/SmoothDspTimer.cs
create mode 100644 Cyan-Stars/Assets/Scripts/Framework/Timer/SmoothDspTimer.cs.meta
diff --git a/Cyan-Stars/Assets/Scripts/Framework/Timer/SmoothDspTimer.cs b/Cyan-Stars/Assets/Scripts/Framework/Timer/SmoothDspTimer.cs
new file mode 100644
index 000000000..464d9ae2a
--- /dev/null
+++ b/Cyan-Stars/Assets/Scripts/Framework/Timer/SmoothDspTimer.cs
@@ -0,0 +1,119 @@
+#nullable enable
+
+using System;
+using UnityEngine;
+
+namespace CyanStars.Framework.Timer
+{
+ public class SmoothDspTimer
+ {
+ /* DspTimerManager 计算逻辑:
+ * Manager 时间以 AudioSetting.dspTime 为准
+ * 但是 dspTime 不保证每帧更新,而下游需要每帧获取时间变化量
+ * 因此在 dspTime 没有刷新的间隔内,暂且采用 Time.unscaledDeltaTime 作为差值,牺牲一些精度来换取每帧更新
+ * 一旦 dspTime 发生更新,计算当前累计时间和 dspTime 的误差,并在后续的每帧更新中逐步抹平这些误差时间
+ */
+
+ ///
+ /// 阻尼系数,[0,1]
+ ///
+ ///
+ /// 每秒抹除当前积累误差的几倍,就像半衰期一样
+ ///
+ private const double ErrorCorrectionRatePerSecond = 0.2;
+
+ ///
+ /// 最大误差时间 (s),[0,+∞)
+ ///
+ ///
+ /// 若 currentTime 延后超过此误差值,则强制跳转到 dspTime;
+ /// 若 currentTime 提前超过此误差值,则停止 currentTime 更新直到误差小于范围
+ ///
+ private const double MaxErrorTime = 1;
+
+
+ ///
+ /// 此 manager 实例记录的,经过矫正后的时间 (s)
+ ///
+ ///
+ /// 每帧更新,用于在两个 dspTime 的间隔之间平滑差值;保证时间正向更新,不会在 dspTime 变化时发生时光倒流
+ ///
+ private double currentTime;
+
+ ///
+ /// dspTime 上次更新时的值 (s)
+ ///
+ ///
+ /// dspTime 更准确,但不保证每帧更新
+ ///
+ private double previousDspTime;
+
+ ///
+ /// currentTime 与 previousDspTime 之间的误差时间,currentTime 提前时为负数 (s)
+ ///
+ /// 用于纠正 currentTime 的误差
+ private double errorTime;
+
+ ///
+ /// 构造函数:为内部属性赋初始值
+ ///
+ public SmoothDspTimer()
+ {
+ Reset();
+ }
+
+ ///
+ /// 重置内部属性,遗忘之前记录的累计时间,在下次 Update() 时视为暂停后重新开始计时而不是试图纠正到暂停前的时间
+ ///
+ public void Reset()
+ {
+ currentTime = AudioSettings.dspTime;
+ previousDspTime = AudioSettings.dspTime;
+ errorTime = 0;
+ }
+
+ ///
+ /// 由调用者在进行计时时每帧驱动,返回平滑后的 deltaDspTime
+ ///
+ ///
+ /// 在暂停后重新启动计时时,请先调用一次 Reset(),否则 SmoothDspTimer 将会把这个暂停理解成一个巨大的卡顿并返回很大的 deltaTime。
+ /// 首次启动也可以调用,避免实例化到首次调用之间的误差时间。
+ ///
+ public double OnUpdate()
+ {
+ double smoothDeltaDspTime;
+ if (Math.Abs(AudioSettings.dspTime - previousDspTime) > 0.000001)
+ {
+ // dspTime 发生更新
+ previousDspTime = AudioSettings.dspTime;
+ errorTime = previousDspTime - currentTime;
+ }
+
+ if (errorTime > MaxErrorTime)
+ {
+ // 如果 managerTime 延后较大,则强制向前跳转以纠正误差
+ Debug.LogWarning($"{nameof(currentTime)} 误差达到 {errorTime}s,强制跳转时间到 {AudioSettings.dspTime}s。");
+ smoothDeltaDspTime = AudioSettings.dspTime - currentTime;
+ currentTime = AudioSettings.dspTime;
+ errorTime = 0;
+ }
+ else if (-errorTime > MaxErrorTime)
+ {
+ // 如果 managerTime 提前较大,则停止此帧更新以纠正误差
+ Debug.LogWarning($"{nameof(currentTime)} 误差达到 {errorTime}s,本帧停止更新。");
+ smoothDeltaDspTime = 0;
+ }
+ else
+ {
+ double frameCorrectionRate = 1.0 - Math.Pow(1.0 - ErrorCorrectionRatePerSecond, Time.unscaledDeltaTime);
+ double correctionTime = errorTime * frameCorrectionRate;
+ correctionTime = Math.Max(correctionTime, -Time.unscaledDeltaTime); // 修正之后的 deltaTime 必须为非负数
+ smoothDeltaDspTime = Time.unscaledDeltaTime + correctionTime;
+ errorTime -= correctionTime;
+ currentTime += smoothDeltaDspTime;
+ }
+
+ return smoothDeltaDspTime;
+ }
+ }
+}
diff --git a/Cyan-Stars/Assets/Scripts/Framework/Timer/SmoothDspTimer.cs.meta b/Cyan-Stars/Assets/Scripts/Framework/Timer/SmoothDspTimer.cs.meta
new file mode 100644
index 000000000..0a469d2bb
--- /dev/null
+++ b/Cyan-Stars/Assets/Scripts/Framework/Timer/SmoothDspTimer.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 81495774952449af9c0f47e3346b4988
\ No newline at end of file