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
}
}