Files
ft/Client/Assets/Scripts/UI/AnimationSimulator/AnimationSimulatorComponent.cs
2026-06-29 21:18:33 +08:00

289 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}
}