using System; using System.Threading; using System.Threading.Tasks; using UnityEngine; namespace Assets.Scripts.UI.AnimationSimulator { public class AnimationSimulatorComponent : MonoBehaviour { public class Parameter { public float scale; public float startValue; public float customVelocity; public bool offsetDirection = false; public Action AccelerationEndCallback = null; /// /// currentVelocity,currentValue /// public Action updateCallback = null; public Action revertTargetEndCallbcak = null; /// /// currentValue ,targetValue /// public Func uniformCompletionStartCallBack = null; } #region 转盘参数设置 [Header("LOG")] [SerializeField] private bool isShowLog = false; [Header("加速阶段")] [SerializeField, Min(0)] private float accelerationTime = 1f; [Header("匀速阶段")] [SerializeField, Min(0)] private float uniformSpeed = 2; [SerializeField, Min(0.1f)] private float uniformMaxTime = 1f; [SerializeField, Min(0.1f)] private float uniformMinTime = 1f; [Header("物理参数")] [SerializeField, Min(0.001f), Tooltip("弹簧刚度 k (N·m/rad)")] private float springStiffness = 200f; [SerializeField, Min(0.001f), Tooltip("阻尼系数 (N·m·s/rad)")] private float dampingRatio = 15f; [SerializeField, Min(0.001f), Tooltip("动摩擦 (N·m) T_k")] private float dynamicFriction = 0.3f; [SerializeField, Min(0.001f), Tooltip("转动惯量 (kg·m²)")] private float momentOfInertia = 1.0f; //第一阶段减速时,稳定角的角度,正数(减)在目标点顺时针方向,负数(加)在目标点逆时针方向 private float valueOffset = 1f; //匀速进入减速的角度,是相对于第一次减速阶段稳定角的角度,正数(减)在奖励顺时针方向,负数(加)在目标点逆时针方向 private float valueOffset2 = 0.2f; [Header("减速阶段")] [SerializeField, Tooltip("停留左侧时,第一阶段减速时,稳定角的角度,正数(减)在目标点顺时针方向,负数(加)在目标点逆时针方向")] private float[] valueTmpStableLeft = new float[2] { 0.025f, 0.07f }; [SerializeField, Tooltip("停留左侧时,匀速进入减速的角度,是相对于第一次减速阶段稳定角的角度")] private float[] valueOffsetToTmpStableLeft = new float[2] { 0.04f, 0.05f }; [SerializeField, Tooltip("停留右侧时,第一阶段减速时,稳定角的角度")] private float[] valueTmpStableRight = new float[2] { -0.07f, -0.025f }; [SerializeField, Tooltip("停留右侧时,匀速进入减速的角度")] private float[] valueOffsetToTmpStableRight = new float[2] { 0.04f, 0.05f }; [Header("停止条件")] [SerializeField, Min(0.1f), Tooltip("第一阶段减速的速度停止条件")] private float velocityStop = 0.1f; [SerializeField, Min(0.1f), Tooltip("第二阶段减速的速度停止条件,!!!必须小于 velocityStop !!! ")] private float velocityStopFinal = 0.1f; [SerializeField, Tooltip("第一阶段减速的角度停止条件")] private float valueStopRatio = 0.01f; [SerializeField, Tooltip("第二阶段减速的角度停止条件")] private float valueStopFinalRatio = 0.01f; #endregion // 运行时状态 private AnimationSimulatorStatus _status; private float currentValue = 0f; private float timer = 0; private float currentVelocity = 0f; private float targetValue = 0f; private float scale => _parameter.scale; private float valueOffsetRed => valueOffset2 * scale; private float valueOffsetRed2 => valueOffset * scale; private TaskCompletionSource effectTask; private CancellationToken? _cancellationToken; private Parameter _parameter; public async Task PlayEffect(Parameter parameter,CancellationToken? cancellationToken=null) { if (cancellationToken != null) { //Debug.LogError("AnimationSimulatorComponent PlayEffect CancellationToken "); _cancellationToken=cancellationToken; _cancellationToken.Value.Register(() => { effectTask?.TrySetCanceled(_cancellationToken.Value); effectTask = null; _status = AnimationSimulatorStatus.Idle; currentVelocity = 0; currentValue = this._parameter.uniformCompletionStartCallBack?.Invoke(currentValue)??0; parameter.updateCallback?.Invoke(currentVelocity, currentValue); //Debug.LogError("AnimationSimulatorComponent PlayEffect CancellationToken Register"); }); } this._parameter = parameter; cancellationToken?.ThrowIfCancellationRequested(); currentValue = this._parameter.startValue; _status = AnimationSimulatorStatus.Idle; timer = 0; //true 左 false 右 float tmpValue1, tmpValue2; if (this._parameter.offsetDirection) { tmpValue1 = UnityEngine.Random.Range(valueTmpStableLeft[0], valueTmpStableLeft[1]); tmpValue2 = UnityEngine.Random.Range(valueOffsetToTmpStableLeft[0], valueOffsetToTmpStableLeft[1]); } else { tmpValue1 = UnityEngine.Random.Range(valueTmpStableRight[0], valueTmpStableRight[1]); tmpValue2 = UnityEngine.Random.Range(valueOffsetToTmpStableRight[0], valueOffsetToTmpStableRight[1]); } valueOffset = tmpValue1; valueOffset2 = tmpValue2+tmpValue1; #if UNITY_EDITOR if (isShowLog) { Debug.Log($"jsd {this.GetType().Name} valueOffsetRed2 {valueOffsetRed2} valueOffset {valueOffset} tmpValue1 {tmpValue1}"); Debug.Log($"jsd {this.GetType().Name} valueOffsetRed {valueOffsetRed} valueOffset2 {valueOffset2} tmpValue1 {tmpValue1} tmpValue2 {tmpValue2}"); } #endif await AccelerationPhase(); this._parameter.AccelerationEndCallback?.Invoke(); cancellationToken?.ThrowIfCancellationRequested(); await UniformPhase(); targetValue = this._parameter.uniformCompletionStartCallBack?.Invoke(currentValue)??0; cancellationToken?.ThrowIfCancellationRequested(); await UniformCompletionPhase(); cancellationToken?.ThrowIfCancellationRequested(); await DecelerationPhase(); _status = AnimationSimulatorStatus.Idle; } private async Task AccelerationPhase() { _status = AnimationSimulatorStatus.Accelerating; timer = 0; effectTask = new TaskCompletionSource(); await effectTask.Task; } private async Task UniformPhase() { _status = AnimationSimulatorStatus.Uniforming; uniformRandomTime = UnityEngine.Random.Range(uniformMinTime, uniformMaxTime); timer = 0f; effectTask = new TaskCompletionSource(); await effectTask.Task; } private async Task UniformCompletionPhase() { _status = AnimationSimulatorStatus.UniformingCompletion; targetValue -= valueOffsetRed2;//距离第一段目标点 var endValue = targetValue - valueOffsetRed; var distance = currentValue - endValue; uniformCompletionTime = Mathf.Abs(distance / uniformSpeed); timer = 0f; effectTask = new TaskCompletionSource(); await effectTask.Task; } private async Task DecelerationPhase() { _status = AnimationSimulatorStatus.Decelerating; revertTargetValue = false; effectTask = new TaskCompletionSource(); await effectTask.Task; } void Update() { if (_status == AnimationSimulatorStatus.Idle) return; timer += Time.deltaTime; if (_status == AnimationSimulatorStatus.Accelerating) AccelerationAni(Time.deltaTime); else if (_status == AnimationSimulatorStatus.Uniforming) UniformAni(); else if (_status == AnimationSimulatorStatus.UniformingCompletion) UniformCompletionAni(); else if (_status == AnimationSimulatorStatus.Decelerating) DecelerationgAni(Time.deltaTime); currentValue -= currentVelocity * Time.deltaTime; #if UNITY_EDITOR if (isShowLog) { Debug.Log($"jsd {this.GetType().Name} _status {_status} target {targetValue} currentValue {currentValue} currentVelocity {currentVelocity}"); } #endif this._parameter.updateCallback?.Invoke(currentVelocity, currentValue); } private void AccelerationAni(float deltaTime) { this.currentVelocity += (uniformSpeed / accelerationTime) * deltaTime; currentVelocity = Mathf.Min(this.currentVelocity, uniformSpeed); if (timer >= accelerationTime || Mathf.Approximately(this.currentVelocity, uniformSpeed)) effectTask?.SetResult(true); } private float uniformRandomTime = 0f; private void UniformAni() { if (timer >= uniformRandomTime) effectTask?.SetResult(true); } private float uniformCompletionTime = 0f; private void UniformCompletionAni() { if (timer >= uniformCompletionTime) effectTask?.SetResult(true); } private void DecelerationgAni(float deltaTime) { float valueError = currentValue - targetValue; int direction = valueError > 0 ? 1 : -1; float springTorque = springStiffness * Mathf.Abs(valueError) * direction; float dampingTorque = -dampingRatio * currentVelocity; float frictionTorque = CalculateFrictionTorque(springTorque); float totalTorque = springTorque + dampingTorque + frictionTorque; float angularAcceleration = totalTorque / momentOfInertia; currentVelocity += angularAcceleration * deltaTime; CheckStopCondition(); } private float CalculateFrictionTorque(float springTorque) { return -dynamicFriction * Mathf.Sign(currentVelocity); } private bool revertTargetValue = false; private float valueStopRadFinal => valueStopFinalRatio * scale; private float valueStopRad => valueStopRatio * scale; private void CheckStopCondition() { float valueError = Mathf.Abs(currentValue - targetValue) % scale; bool inPosition = valueError < Mathf.Abs(valueStopRad % scale); bool slowEnough = Mathf.Abs(currentVelocity) < velocityStop; bool inPositionFinal = valueError < Mathf.Abs(valueStopRadFinal % scale); bool slowEnoughFinal = Mathf.Abs(currentVelocity) < velocityStopFinal; if (inPosition && slowEnough && !revertTargetValue) { targetValue += valueOffsetRed2; revertTargetValue = true; this._parameter.revertTargetEndCallbcak?.Invoke(); } else if (inPositionFinal && slowEnoughFinal) { currentVelocity = 0f; currentValue = targetValue; this._parameter.updateCallback?.Invoke(currentVelocity, currentValue); // 同步最终位置 revertTargetValue = false; effectTask?.SetResult(true); } } public async Task SharkAni(Parameter parameter) { _parameter= parameter; revertTargetValue = true; this.targetValue = _parameter.startValue; currentValue = _parameter.startValue; currentVelocity = _parameter.customVelocity; effectTask = new TaskCompletionSource(); _status = AnimationSimulatorStatus.Decelerating; await effectTask.Task; _status = AnimationSimulatorStatus.Idle; } #region public func public float GetAccelertationTime() { return accelerationTime; } public AnimationSimulatorStatus GetAnimationStatus() { return _status; } #endregion } }