289 lines
13 KiB
C#
289 lines
13 KiB
C#
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;
|
||
/// <summary>
|
||
/// currentVelocity,currentValue
|
||
/// </summary>
|
||
public Action<float, float> updateCallback = null;
|
||
public Action revertTargetEndCallbcak = null;
|
||
/// <summary>
|
||
/// currentValue ,targetValue
|
||
/// </summary>
|
||
public Func<float, float> 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<bool> 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<bool>();
|
||
await effectTask.Task;
|
||
}
|
||
private async Task UniformPhase()
|
||
{
|
||
_status = AnimationSimulatorStatus.Uniforming;
|
||
uniformRandomTime = UnityEngine.Random.Range(uniformMinTime, uniformMaxTime);
|
||
timer = 0f;
|
||
effectTask = new TaskCompletionSource<bool>();
|
||
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<bool>();
|
||
await effectTask.Task;
|
||
}
|
||
private async Task DecelerationPhase()
|
||
{
|
||
_status = AnimationSimulatorStatus.Decelerating;
|
||
revertTargetValue = false;
|
||
effectTask = new TaskCompletionSource<bool>();
|
||
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<bool>();
|
||
_status = AnimationSimulatorStatus.Decelerating;
|
||
await effectTask.Task;
|
||
_status = AnimationSimulatorStatus.Idle;
|
||
}
|
||
#region public func
|
||
public float GetAccelertationTime() { return accelerationTime; }
|
||
public AnimationSimulatorStatus GetAnimationStatus() { return _status; }
|
||
#endregion
|
||
}
|
||
} |