feat(fix-system): 优化线缆收起动画

This commit is contained in:
2026-07-26 17:35:40 +08:00
parent 681ac01eb9
commit 0e0cc66546
3 changed files with 872 additions and 1363 deletions
File diff suppressed because it is too large Load Diff
@@ -128,10 +128,10 @@ public class PhysicCable : MonoBehaviour
private Vector3 _insertImpactDir;
private Vector3 _insertImpactNormal;
// 收拢(Collapsing)状态:当前形状纯插值收缩到目标点,时长由外部(如强制收起指令)指定
// 收拢(Collapsing)状态:沿当前线径从尾端逐段吸回出线口
private Vector3[] _collapseStart;
private Vector3 _collapseTarget;
private float _collapseStartLength;
private float[] _collapseDistances;
private float _collapsePathLength;
private float _collapseDuration;
private float _collapseElapsed;
@@ -239,7 +239,7 @@ public class PhysicCable : MonoBehaviour
}
/// <summary>
/// 强制收起:不走拖拽物理,从当前形状纯运动学插值收缩到 target(出线口收纳位)。
/// 强制收起:不走拖拽物理,保留当前曲线路径并从尾端逐段缩短到 target(出线口)。
/// 用于指令强制收起等需要在极短、精确可控时长内完成的场景,避免拖拽跟随参数(为秒级手动拖拽标定)
/// 在几帧内来不及收敛而产生的甩鞭/抖动。
/// </summary>
@@ -248,10 +248,18 @@ public class PhysicCable : MonoBehaviour
EnsureInit();
_collapseStart ??= new Vector3[_n];
for (int i = 0; i < _n; i++) _collapseStart[i] = _pts[i].pos;
_collapseDistances ??= new float[_n];
_collapsePathLength = 0f;
_collapseStart[0] = FlattenZ(target);
_collapseDistances[0] = 0f;
for (int i = 1; i < _n; i++)
{
_collapseStart[i] = _pts[i].pos;
_collapsePathLength += Vector3.Distance(_collapseStart[i - 1], _collapseStart[i]);
_collapseDistances[i] = _collapsePathLength;
}
_collapseTarget = FlattenZ(target);
_collapseStartLength = _length;
_collapseDuration = Mathf.Max(0.0001f, duration);
_collapseElapsed = 0f;
@@ -492,20 +500,38 @@ public class PhysicCable : MonoBehaviour
UpdatePlugAngle(plugged);
}
/// <summary>Collapsing 状态的纯运动学插值:所有点从收拢起点直接 Lerp 到目标点,不经过 Verlet/约束求解。</summary>
/// <summary>
/// Collapsing 状态沿收线前的曲线逐段截短。线身形状不做整体缩放,
/// 只有尾端沿原路径倒退,形成被线盘吸入的观感。
/// </summary>
private void SimulateCollapse(float dt)
{
_collapseElapsed += dt;
float t = Mathf.Clamp01(_collapseElapsed / _collapseDuration);
float eased = t * t; // ease-in:起步慢、收尾快,贴近"被吸回线盘"的观感
float eased = t * t * (3f - 2f * t);
float remainingLength = _collapsePathLength * (1f - eased);
int segment = 1;
for (int i = 0; i < _n; i++)
{
Vector3 pos = Vector3.Lerp(_collapseStart[i], _collapseTarget, eased);
float distance = remainingLength * (i / (float)(_n - 1));
while (segment < _n - 1 && _collapseDistances[segment] < distance)
{
segment++;
}
int previous = segment - 1;
float segmentLength = _collapseDistances[segment] - _collapseDistances[previous];
float segmentT = segmentLength > 1e-5f
? (distance - _collapseDistances[previous]) / segmentLength
: 0f;
Vector3 pos = Vector3.Lerp(_collapseStart[previous], _collapseStart[segment], segmentT);
_pts[i].pos = pos;
_pts[i].prev = pos;
}
_length = Mathf.Lerp(_collapseStartLength, 0f, eased);
_length = remainingLength;
SyncPlugAngleToTail();
}
private Vector3 ApplyUnplugResistance(Vector3 rawPointer)
@@ -8,7 +8,7 @@ namespace AibisDream.FixSystem
public class CablePanel : MonoBehaviour, IOperatorPanel
{
private const int SortingOrderRetracted = 41;
private const float RetractDuration = 0.18f; // 强制收起:插头 DOMove 与绳收拢共用时长
private const float RetractDuration = 0.22f;
#region
@@ -44,8 +44,9 @@ namespace AibisDream.FixSystem
private ISocket curSocket;
// 线动画期间插头由 DOTween 驱动,暂停"插头跟随绳末端"
// 线动画期间插头由 DOTween 驱动,暂停"插头跟随绳末端"
private bool suppressPlugFollow;
private Tween retractTween;
#endregion
@@ -152,6 +153,8 @@ namespace AibisDream.FixSystem
public void DropDownCableImmediate()
{
targetCableRetracted = false;
retractTween?.Kill(false);
retractTween = null;
PlugRef.transform.DOKill(false);
PlugRef.PullUpSocketSilent();
CableReelRef.ResetRotation();
@@ -202,7 +205,8 @@ namespace AibisDream.FixSystem
&& physicCableRef.State != PhysicCable.CableState.Hidden)
{
bool active = physicCableRef.State == PhysicCable.CableState.Dragging
|| physicCableRef.State == PhysicCable.CableState.Plugged;
|| physicCableRef.State == PhysicCable.CableState.Plugged
|| physicCableRef.State == PhysicCable.CableState.Collapsing;
CableReelRef.UpdateRotation(active);
}
}
@@ -213,7 +217,9 @@ namespace AibisDream.FixSystem
// 闲置/拖拽时插头挂在绳末端,位置与朝向都由绳给出
var state = physicCableRef.State;
if (state == PhysicCable.CableState.Free || state == PhysicCable.CableState.Dragging)
if (state == PhysicCable.CableState.Free
|| state == PhysicCable.CableState.Dragging
|| state == PhysicCable.CableState.Collapsing)
{
physicCableRef.ApplyPlugPose(plugRef.transform, plugRef.PlugRoot);
plugRef.ApplyFeedbackOffset();
@@ -355,7 +361,10 @@ namespace AibisDream.FixSystem
targetCableRetracted = true;
isCableRetracted = true;
retractTween?.Kill(false);
retractTween = null;
PlugRef.transform.DOKill(false);
PlugRef.CancelFeedbackTween();
if (physicCableRef.State == PhysicCable.CableState.Hidden)
{
@@ -363,11 +372,17 @@ namespace AibisDream.FixSystem
return;
}
suppressPlugFollow = true;
physicCableRef.CollapseTo(retractedPos.position, RetractDuration);
PlugRef.transform.DOMove(retractedPos.position, RetractDuration)
.OnComplete(CompleteRetractIfCurrent)
.OnKill(CompleteRetractIfCurrent);
suppressPlugFollow = false;
physicCableRef.CollapseTo(physicCableRef.startTransform.position, RetractDuration);
retractTween = DOVirtual.DelayedCall(RetractDuration, CompleteRetractIfCurrent)
.OnKill(() =>
{
// DelayedCall 正常完成后会自动 Kill;此时回调已经把字段清空,不重复结算。
if (retractTween != null)
{
CompleteRetractIfCurrent();
}
});
AudioManager.Instance.PlaySfx("event:/ActionFB/mods_close");
AudioManager.Instance.PlaySfx("event:/FollowInput/plugdrop");
@@ -376,6 +391,7 @@ namespace AibisDream.FixSystem
private void CompleteRetractIfCurrent()
{
if (!targetCableRetracted) return;
retractTween = null;
ApplyRetractedState();
isCableRetracted = true;
}
@@ -385,6 +401,8 @@ namespace AibisDream.FixSystem
if (!isCableRetracted && !targetCableRetracted) return;
targetCableRetracted = false;
retractTween?.Kill(false);
retractTween = null;
PlugRef.PullUpSocket();
PlugRef.transform.DOKill(false);
CableReelRef.ResetRotation();
@@ -421,6 +439,8 @@ namespace AibisDream.FixSystem
{
targetCableRetracted = true;
isCableRetracted = true;
retractTween?.Kill(false);
retractTween = null;
PlugRef.transform.DOKill(false);
ApplyRetractedState();
}