feat(cable): 优化插线手感与线缆表现
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+1645
-1622
File diff suppressed because it is too large
Load Diff
+802
-4079
File diff suppressed because it is too large
Load Diff
@@ -44,6 +44,20 @@ public class PhysicCable : MonoBehaviour
|
||||
[SerializeField] private float plugTurnRate = 0.18f;
|
||||
[SerializeField] private float plugMaxTurnStep = 0.11f; // 每步最大转角(弧度)
|
||||
|
||||
[Header("插拔手感")]
|
||||
[SerializeField] private bool enableSocketAssist = true;
|
||||
[SerializeField] private float defaultAssistRadius = 1.45f; // 没有外部半径时的吸附范围
|
||||
[SerializeField, Range(0f, 1f)] private float socketAssistStrength = 0.62f;
|
||||
[SerializeField] private float plugSnapRate = 0.55f;
|
||||
[SerializeField] private float insertImpactDuration = 0.22f;
|
||||
[SerializeField] private float insertShoveDistance = 0.18f;
|
||||
[SerializeField] private float insertShakeAmplitude = 0.055f;
|
||||
[SerializeField] private float insertShakeFrequency = 34f;
|
||||
[SerializeField] private float insertTailKick = 0.12f;
|
||||
[SerializeField] private float unplugBreakDistance = 0.55f;
|
||||
[SerializeField] private float unplugStickExponent = 2.4f;
|
||||
[SerializeField] private float unplugReleaseKick = 0.16f;
|
||||
|
||||
[Header("可选")]
|
||||
[SerializeField] private Transform reelCenter; // 出线方向参考中心,空则用 startTransform.parent
|
||||
[SerializeField] private Transform floorLimit; // 地面高度参考,空则不启用地面碰撞
|
||||
@@ -54,16 +68,16 @@ public class PhysicCable : MonoBehaviour
|
||||
// 线缆与插头(Tools/48)成对相邻,剪线等演出覆盖物(Tools/50+)仍可整体盖住两者
|
||||
[SerializeField] private string sortingLayerName = "Tools";
|
||||
[SerializeField] private int sortingOrder = 47;
|
||||
// 三档色阶(描边/主体/顶光),压暗去饱和贴合场景像素环境;原型的第四档高光去掉
|
||||
[SerializeField] private Color shadowColor = new(0.05f, 0.05f, 0.07f, 0.2f);
|
||||
// 7/7 PixelCableLine step 色阶: Edge → Band Dark → Band Light
|
||||
[SerializeField] private Color shadowColor = new(0f, 0.08f, 0.16f, 0.22f);
|
||||
[SerializeField] private float shadowWidth = 0.295f;
|
||||
[SerializeField] private float shadowDrop = 0.12f; // 原型:向下平移 7px
|
||||
[SerializeField] private Color darkColor = new(0.09f, 0.11f, 0.18f, 1f); // 深色描边
|
||||
[SerializeField] private Color darkColor = new(0f, 0.08f, 0.16f, 1f); // Edge Outline
|
||||
[SerializeField] private float darkWidth = 0.33f; // 原型:w+4
|
||||
[SerializeField] private Color bodyColor = new(0.24f, 0.30f, 0.44f, 1f); // 主体(低饱和板岩蓝)
|
||||
[SerializeField] private Color bodyColor = new(0f, 0.19f, 0.34f, 1f); // Band Dark
|
||||
[SerializeField] private float bodyWidth = 0.26f; // 原型:w=15px ≈ 0.26wu
|
||||
[SerializeField] private Color midColor = new(0.38f, 0.45f, 0.60f, 1f); // 顶光
|
||||
[SerializeField] private float midWidth = 0.143f; // 原型:0.55w
|
||||
[SerializeField] private Color midColor = new(0f, 0.41f, 0.57f, 1f); // Band Light
|
||||
[SerializeField] private float midWidth = 0.1f; // 原型:0.55w
|
||||
[SerializeField] private float midLift = 0.036f; // 原型:上移 0.14w
|
||||
|
||||
[Header("端部收窄(默认关闭:整根线宽与插头/孔匹配)")]
|
||||
@@ -96,6 +110,14 @@ public class PhysicCable : MonoBehaviour
|
||||
private Transform _dock; // 当前插入的插孔
|
||||
private Vector3 _plugSnapEnd; // 插接吸附目标:渐进收敛到孔心后末端硬钉,保证完全对齐堵住孔
|
||||
private float _plugAngle = -Mathf.PI / 2f; // 插头朝向(弧度,初始朝下)
|
||||
private Transform _dragAssistDock;
|
||||
private float _dragAssistRadius;
|
||||
private float _dragAssistBlend;
|
||||
private bool _unplugResisting;
|
||||
private Vector3 _unplugAnchor;
|
||||
private float _insertImpactTimer;
|
||||
private Vector3 _insertImpactDir;
|
||||
private Vector3 _insertImpactNormal;
|
||||
|
||||
private LineRenderer _line; // 遗留组件,仅保持禁用(旧场景/CableSystem 仍会引用)
|
||||
private CableMeshRenderer _cableMesh;
|
||||
@@ -118,6 +140,9 @@ public class PhysicCable : MonoBehaviour
|
||||
{
|
||||
State = CableState.Hidden;
|
||||
_dock = null;
|
||||
ClearDragAssistTarget();
|
||||
_unplugResisting = false;
|
||||
_insertImpactTimer = 0f;
|
||||
SetCableRenderersEnabled(false);
|
||||
}
|
||||
|
||||
@@ -127,6 +152,8 @@ public class PhysicCable : MonoBehaviour
|
||||
EnsureInit();
|
||||
if (resetShape) ResetHangingShape();
|
||||
_dock = null;
|
||||
ClearDragAssistTarget();
|
||||
_unplugResisting = false;
|
||||
State = CableState.Free;
|
||||
SetCableRenderersEnabled(true);
|
||||
}
|
||||
@@ -138,13 +165,15 @@ public class PhysicCable : MonoBehaviour
|
||||
_dock = null;
|
||||
_pointer = FlattenZ(pointerWorld);
|
||||
_dragTarget = _pts[_n - 1].pos;
|
||||
_unplugResisting = false;
|
||||
_insertImpactTimer = 0f;
|
||||
State = CableState.Dragging;
|
||||
SetCableRenderersEnabled(true);
|
||||
}
|
||||
|
||||
public void SetPointer(Vector3 pointerWorld)
|
||||
{
|
||||
_pointer = FlattenZ(pointerWorld);
|
||||
_pointer = ApplyUnplugResistance(FlattenZ(pointerWorld));
|
||||
}
|
||||
|
||||
public void EndDrag()
|
||||
@@ -152,14 +181,49 @@ public class PhysicCable : MonoBehaviour
|
||||
if (State == CableState.Dragging)
|
||||
{
|
||||
State = CableState.Free;
|
||||
ClearDragAssistTarget();
|
||||
_unplugResisting = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetDragAssistTarget(Transform dock, float radius)
|
||||
{
|
||||
if (!enableSocketAssist || dock == null)
|
||||
{
|
||||
ClearDragAssistTarget();
|
||||
return;
|
||||
}
|
||||
|
||||
_dragAssistDock = dock;
|
||||
_dragAssistRadius = Mathf.Max(0.01f, radius > 0f ? radius : defaultAssistRadius);
|
||||
}
|
||||
|
||||
public void ClearDragAssistTarget()
|
||||
{
|
||||
_dragAssistDock = null;
|
||||
_dragAssistBlend = 0f;
|
||||
}
|
||||
|
||||
public void StartUnplugResistance(Transform socket)
|
||||
{
|
||||
if (socket == null || _pts == null) return;
|
||||
|
||||
_unplugAnchor = FlattenZ(socket.position);
|
||||
_pointer = _unplugAnchor;
|
||||
_dragTarget = _unplugAnchor;
|
||||
_pts[_n - 1].pos = _unplugAnchor;
|
||||
_pts[_n - 1].prev = _unplugAnchor;
|
||||
_unplugResisting = unplugBreakDistance > 0.01f;
|
||||
ClearDragAssistTarget();
|
||||
}
|
||||
|
||||
/// <summary>末端钉到插孔;snapStraight 用于读档,把绳直接摆成出线口到插孔的直线。</summary>
|
||||
public void PlugInto(Transform dock, bool snapStraight = false)
|
||||
{
|
||||
EnsureInit();
|
||||
_dock = dock;
|
||||
ClearDragAssistTarget();
|
||||
_unplugResisting = false;
|
||||
State = CableState.Plugged;
|
||||
SetCableRenderersEnabled(true);
|
||||
if (snapStraight && dock != null)
|
||||
@@ -168,6 +232,14 @@ public class PhysicCable : MonoBehaviour
|
||||
}
|
||||
// 吸附目标从当前末端出发,向孔心渐进收敛(保留吸入动画,最终严格钉在孔心)
|
||||
_plugSnapEnd = _pts[_n - 1].pos;
|
||||
if (snapStraight)
|
||||
{
|
||||
_insertImpactTimer = 0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
BeginInsertImpact(dock);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------ 插头姿态 ------------------------------
|
||||
@@ -273,9 +345,10 @@ public class PhysicCable : MonoBehaviour
|
||||
}
|
||||
|
||||
// --- 拖拽目标:重 lerp + 按当前绳长钳制(拽满时目标跟着绳长走,消除抖动)
|
||||
Vector3 dragPointer = dragging ? GetAssistedPointer(_pointer) : _pointer;
|
||||
if (dragging)
|
||||
{
|
||||
_dragTarget += (_pointer - _dragTarget) * 0.3f;
|
||||
_dragTarget += (dragPointer - _dragTarget) * 0.3f;
|
||||
Vector3 offset = _dragTarget - anchor;
|
||||
float dist = offset.magnitude;
|
||||
float maxRadius = Mathf.Min(lMax, _length) * 0.99f;
|
||||
@@ -286,7 +359,7 @@ public class PhysicCable : MonoBehaviour
|
||||
}
|
||||
|
||||
// --- 送线 / 回收(长度由原始指针驱动,而非被钳制后的目标)
|
||||
Vector3 endTarget = dragging ? _pointer : (plugged ? dock : _pts[_n - 1].pos);
|
||||
Vector3 endTarget = dragging ? dragPointer : (plugged ? dock : _pts[_n - 1].pos);
|
||||
float needed = Vector3.Distance(endTarget, anchor) * 1.04f + lengthMargin;
|
||||
float targetLength = pinnedEnd ? Mathf.Clamp(needed, idleLength, lMax) : idleLength;
|
||||
float rate = targetLength > _length ? feedRate : (pinnedEnd ? holdRate : retractRate);
|
||||
@@ -315,7 +388,8 @@ public class PhysicCable : MonoBehaviour
|
||||
else if (plugged)
|
||||
{
|
||||
// 吸附目标渐进收敛到孔心后,末端硬钉——软拉会被重力/张力持续拽偏,堵不严孔
|
||||
_plugSnapEnd += (dock - _plugSnapEnd) * 0.5f;
|
||||
Vector3 dockTarget = GetImpactDockTarget(dock, dt);
|
||||
_plugSnapEnd += (dockTarget - _plugSnapEnd) * plugSnapRate;
|
||||
if ((_plugSnapEnd - dock).sqrMagnitude < 1e-4f) _plugSnapEnd = dock;
|
||||
_pts[_n - 1].pos = _plugSnapEnd;
|
||||
_pts[_n - 1].prev = _plugSnapEnd;
|
||||
@@ -388,6 +462,95 @@ public class PhysicCable : MonoBehaviour
|
||||
UpdatePlugAngle(anchor, dragging, plugged);
|
||||
}
|
||||
|
||||
private Vector3 ApplyUnplugResistance(Vector3 rawPointer)
|
||||
{
|
||||
if (!_unplugResisting) return rawPointer;
|
||||
|
||||
Vector3 pull = rawPointer - _unplugAnchor;
|
||||
pull.z = 0;
|
||||
float dist = pull.magnitude;
|
||||
if (dist >= unplugBreakDistance)
|
||||
{
|
||||
_unplugResisting = false;
|
||||
if (dist > 1e-4f)
|
||||
{
|
||||
KickTail(pull / dist * unplugReleaseKick);
|
||||
}
|
||||
return rawPointer;
|
||||
}
|
||||
|
||||
float t = Mathf.Clamp01(dist / Mathf.Max(0.001f, unplugBreakDistance));
|
||||
float eased = Mathf.Pow(t, Mathf.Max(1f, unplugStickExponent));
|
||||
return Vector3.Lerp(_unplugAnchor, rawPointer, eased);
|
||||
}
|
||||
|
||||
private Vector3 GetAssistedPointer(Vector3 pointer)
|
||||
{
|
||||
if (!enableSocketAssist || _dragAssistDock == null || State != CableState.Dragging)
|
||||
{
|
||||
_dragAssistBlend = Mathf.MoveTowards(_dragAssistBlend, 0f, 0.18f);
|
||||
return pointer;
|
||||
}
|
||||
|
||||
Vector3 dock = FlattenZ(_dragAssistDock.position);
|
||||
float dist = Vector3.Distance(pointer, dock);
|
||||
float targetBlend = Mathf.Clamp01(1f - dist / Mathf.Max(0.01f, _dragAssistRadius));
|
||||
targetBlend = targetBlend * targetBlend * (3f - 2f * targetBlend);
|
||||
targetBlend *= socketAssistStrength;
|
||||
_dragAssistBlend = Mathf.MoveTowards(_dragAssistBlend, targetBlend, 0.14f);
|
||||
return Vector3.Lerp(pointer, dock, _dragAssistBlend);
|
||||
}
|
||||
|
||||
private void BeginInsertImpact(Transform dock)
|
||||
{
|
||||
if (dock == null || IsInsertImpactDisabled()) return;
|
||||
|
||||
Vector3 dockPos = FlattenZ(dock.position);
|
||||
Vector3 dir = dockPos - _pts[_n - 1].pos;
|
||||
if (dir.sqrMagnitude < 1e-4f)
|
||||
{
|
||||
dir = _pts[_n - 1].pos - _pts[Mathf.Max(0, _n - 3)].pos;
|
||||
}
|
||||
dir.z = 0;
|
||||
if (dir.sqrMagnitude < 1e-4f) dir = Vector3.right;
|
||||
_insertImpactDir = dir.normalized;
|
||||
_insertImpactNormal = new Vector3(-_insertImpactDir.y, _insertImpactDir.x, 0f);
|
||||
_insertImpactTimer = Mathf.Max(0f, insertImpactDuration);
|
||||
KickTail(_insertImpactDir * insertTailKick);
|
||||
}
|
||||
|
||||
private bool IsInsertImpactDisabled()
|
||||
{
|
||||
return insertImpactDuration <= 0f
|
||||
&& insertTailKick <= 0f
|
||||
&& insertShoveDistance <= 0f
|
||||
&& insertShakeAmplitude <= 0f;
|
||||
}
|
||||
|
||||
private Vector3 GetImpactDockTarget(Vector3 dock, float dt)
|
||||
{
|
||||
if (_insertImpactTimer <= 0f) return dock;
|
||||
|
||||
_insertImpactTimer = Mathf.Max(0f, _insertImpactTimer - dt);
|
||||
float life = Mathf.Clamp01(_insertImpactTimer / Mathf.Max(0.001f, insertImpactDuration));
|
||||
float elapsed = insertImpactDuration - _insertImpactTimer;
|
||||
float shove = insertShoveDistance * life * life;
|
||||
float shake = Mathf.Sin(elapsed * insertShakeFrequency) * insertShakeAmplitude * life;
|
||||
return dock + _insertImpactDir * shove + _insertImpactNormal * shake;
|
||||
}
|
||||
|
||||
private void KickTail(Vector3 impulse)
|
||||
{
|
||||
if (_pts == null) return;
|
||||
|
||||
int start = Mathf.Max(1, _n - 5);
|
||||
for (int i = start; i < _n; i++)
|
||||
{
|
||||
float t = (i - start + 1f) / (_n - start + 1f);
|
||||
_pts[i].prev -= impulse * t;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyOutletStub(Vector3 anchor, Vector3 dir)
|
||||
{
|
||||
// P1:横向收紧 0.6,且沿出线方向至少伸出 stubMinOffset
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.Kit;
|
||||
using AibisDream.Utility;
|
||||
using DG.Tweening;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
@@ -45,6 +46,23 @@ namespace AibisDream.FixSystem
|
||||
public event Action OnPlugPointerDown;
|
||||
|
||||
private bool _isDragging;
|
||||
private bool _isInserting;
|
||||
private Tween _insertTween;
|
||||
private Tween _unplugTween;
|
||||
private Vector3 _feedbackOffset;
|
||||
|
||||
[Header("插入手感")]
|
||||
[SerializeField] private float insertWindupDistance = 0.08f;
|
||||
[SerializeField] private float insertWindupDuration = 0.045f;
|
||||
[SerializeField] private float insertSnapDuration = 0.11f;
|
||||
[SerializeField] private float insertSnapOvershoot = 1.45f;
|
||||
[SerializeField] private float insertPunchScale = 0.045f;
|
||||
|
||||
[Header("拔出手感")]
|
||||
[SerializeField] private float unplugPopDistance = 0.13f;
|
||||
[SerializeField] private float unplugPopDuration = 0.07f;
|
||||
[SerializeField] private float unplugReturnDuration = 0.12f;
|
||||
[SerializeField] private float unplugPunchScale = 0.065f;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
@@ -80,14 +98,42 @@ namespace AibisDream.FixSystem
|
||||
/// <param name="targetSocket">目标socket</param>
|
||||
private void InsertSocket(ISocket targetSocket)
|
||||
{
|
||||
// 修改插头位置
|
||||
transform.position = targetSocket.GetSocketPos().position;
|
||||
if (_isInserting) return;
|
||||
_isInserting = true;
|
||||
|
||||
// 处理插槽插入
|
||||
targetSocket.PlugIn();
|
||||
_sprite.enabled = false;
|
||||
cablePanel.ClearDragHighlight();
|
||||
ClearFeedbackOffset();
|
||||
|
||||
OnInsertSocket?.Invoke(targetSocket);
|
||||
var targetPos = targetSocket.GetSocketPos().position;
|
||||
_insertTween?.Kill();
|
||||
transform.DOKill(false);
|
||||
transform.localScale = Vector3.one;
|
||||
|
||||
Vector3 insertDir = targetPos - transform.position;
|
||||
insertDir.z = 0;
|
||||
if (insertDir.sqrMagnitude < 1e-4f) insertDir = transform.right;
|
||||
insertDir.Normalize();
|
||||
|
||||
var sequence = DOTween.Sequence();
|
||||
if (insertWindupDistance > 0f && insertWindupDuration > 0f)
|
||||
{
|
||||
sequence.Append(transform.DOMove(transform.position - insertDir * insertWindupDistance, insertWindupDuration)
|
||||
.SetEase(Ease.OutSine));
|
||||
}
|
||||
|
||||
sequence.Append(transform.DOMove(targetPos, insertSnapDuration)
|
||||
.SetEase(Ease.OutBack, insertSnapOvershoot))
|
||||
.Insert(0f, transform.DOPunchScale(Vector3.one * insertPunchScale,
|
||||
insertWindupDuration + insertSnapDuration, 7, 0.45f))
|
||||
.OnUpdate(() => cablePanel.UpdateDragPointer(transform.position))
|
||||
.OnComplete(() =>
|
||||
{
|
||||
targetSocket.PlugIn();
|
||||
_sprite.enabled = false;
|
||||
_isInserting = false;
|
||||
OnInsertSocket?.Invoke(targetSocket);
|
||||
});
|
||||
_insertTween = sequence;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -95,15 +141,23 @@ namespace AibisDream.FixSystem
|
||||
/// </summary>
|
||||
public void PullUpSocket()
|
||||
{
|
||||
_insertTween?.Kill();
|
||||
_isInserting = false;
|
||||
|
||||
_sprite.enabled = true;
|
||||
ApplyNormalSorting();
|
||||
|
||||
transform.DOKill(false);
|
||||
transform.localScale = Vector3.one;
|
||||
PlayUnplugFeedback();
|
||||
|
||||
OnPullUpSocket?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>读档/静默:仅恢复插头精灵可见,不触发 CablePanel 事件。</summary>
|
||||
public void PullUpSocketSilent()
|
||||
{
|
||||
ClearFeedbackOffset();
|
||||
_sprite.enabled = true;
|
||||
ApplyNormalSorting();
|
||||
}
|
||||
@@ -118,6 +172,7 @@ namespace AibisDream.FixSystem
|
||||
/// <summary>读档:无动画回到初始位。</summary>
|
||||
public void RestoreAtStartPositionImmediate()
|
||||
{
|
||||
ClearFeedbackOffset();
|
||||
transform.rotation = _defaultRotation;
|
||||
transform.position = _startPos;
|
||||
}
|
||||
@@ -126,6 +181,7 @@ namespace AibisDream.FixSystem
|
||||
public void RestoreInsertedAt(Transform socketPos)
|
||||
{
|
||||
if (socketPos == null) return;
|
||||
ClearFeedbackOffset();
|
||||
transform.position = socketPos.position;
|
||||
_sprite.enabled = false;
|
||||
}
|
||||
@@ -135,14 +191,84 @@ namespace AibisDream.FixSystem
|
||||
_sprite.enabled = visible;
|
||||
}
|
||||
|
||||
public void ApplyFeedbackOffset()
|
||||
{
|
||||
if (_feedbackOffset.sqrMagnitude <= 1e-6f) return;
|
||||
transform.position += _feedbackOffset;
|
||||
}
|
||||
|
||||
private void PlayUnplugFeedback()
|
||||
{
|
||||
_unplugTween?.Kill();
|
||||
_feedbackOffset = Vector3.zero;
|
||||
|
||||
Vector3 releaseDir = GetUnplugReleaseDirection();
|
||||
Vector3 popOffset = releaseDir * Mathf.Max(0f, unplugPopDistance);
|
||||
var sequence = DOTween.Sequence();
|
||||
|
||||
if (popOffset.sqrMagnitude > 1e-6f && unplugPopDuration > 0f)
|
||||
{
|
||||
sequence.Append(DOTween.To(() => _feedbackOffset, value => _feedbackOffset = value,
|
||||
popOffset, unplugPopDuration)
|
||||
.SetEase(Ease.OutCubic));
|
||||
}
|
||||
|
||||
if (unplugReturnDuration > 0f)
|
||||
{
|
||||
sequence.Append(DOTween.To(() => _feedbackOffset, value => _feedbackOffset = value,
|
||||
Vector3.zero, unplugReturnDuration)
|
||||
.SetEase(Ease.OutBack, 1.25f));
|
||||
}
|
||||
else
|
||||
{
|
||||
sequence.AppendCallback(() => _feedbackOffset = Vector3.zero);
|
||||
}
|
||||
|
||||
if (unplugPunchScale > 0f)
|
||||
{
|
||||
sequence.Insert(0f, transform.DOPunchScale(Vector3.one * unplugPunchScale,
|
||||
unplugPopDuration + unplugReturnDuration, 7, 0.42f));
|
||||
}
|
||||
|
||||
sequence.OnKill(() => _feedbackOffset = Vector3.zero)
|
||||
.OnComplete(() => _feedbackOffset = Vector3.zero);
|
||||
_unplugTween = sequence;
|
||||
}
|
||||
|
||||
private Vector3 GetUnplugReleaseDirection()
|
||||
{
|
||||
Vector3 releaseDir = Vector3.zero;
|
||||
if (cablePanel != null && cablePanel.CableRootPos != null)
|
||||
{
|
||||
releaseDir = cablePanel.CableRootPos.position - transform.position;
|
||||
}
|
||||
|
||||
releaseDir.z = 0;
|
||||
if (releaseDir.sqrMagnitude < 1e-4f)
|
||||
{
|
||||
releaseDir = -transform.right;
|
||||
releaseDir.z = 0;
|
||||
}
|
||||
|
||||
return releaseDir.sqrMagnitude > 1e-4f ? releaseDir.normalized : Vector3.left;
|
||||
}
|
||||
|
||||
private void ClearFeedbackOffset()
|
||||
{
|
||||
_unplugTween?.Kill();
|
||||
_feedbackOffset = Vector3.zero;
|
||||
transform.localScale = Vector3.one;
|
||||
}
|
||||
|
||||
private void OnDrag(BaseEventData eventData)
|
||||
{
|
||||
if (!_isDragging) return;
|
||||
|
||||
if (eventData is PointerEventData pointerData)
|
||||
{
|
||||
// 指针只驱动线缆送线与末端钉住目标,插头本体由绳末端带动(CablePanel.LateUpdate)
|
||||
cablePanel.UpdateDragPointer(CameraKit.GetMouseWorldPos(pointerData.position));
|
||||
var pointerWorldPos = CameraKit.GetMouseWorldPos(pointerData.position);
|
||||
cablePanel.UpdateDragPointer(pointerWorldPos);
|
||||
cablePanel.UpdateDragProximity(pointerWorldPos);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,9 @@ namespace AibisDream.FixSystem
|
||||
[Header("灯光系统")]
|
||||
[SerializeField] private CablePanelLight panelLight;
|
||||
|
||||
[Header("拖拽手感")]
|
||||
[SerializeField] private float dragAssistRangeMultiplier = 1.65f;
|
||||
|
||||
public Plug PlugRef => plugRef;
|
||||
public Cable CableRef => cableRef;
|
||||
public PhysicCable PhysicCableRef => physicCableRef;
|
||||
@@ -195,6 +198,7 @@ namespace AibisDream.FixSystem
|
||||
if (state == PhysicCable.CableState.Free || state == PhysicCable.CableState.Dragging)
|
||||
{
|
||||
physicCableRef.ApplyPlugPose(plugRef.transform, plugRef.PlugRoot);
|
||||
plugRef.ApplyFeedbackOffset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,11 +249,17 @@ namespace AibisDream.FixSystem
|
||||
|
||||
private void OnPlugPoinerDown()
|
||||
{
|
||||
if (IsPlugInSocket()) PlugRef.PullUpSocket();
|
||||
Transform unplugSocket = curSocket?.GetSocketPos();
|
||||
bool wasPlugged = IsPlugInSocket();
|
||||
if (wasPlugged) PlugRef.PullUpSocket();
|
||||
|
||||
// 末端钉到拖拽目标,按需送线;指针位置随 OnDrag 持续更新。
|
||||
// 排序不随拖拽切换:线缆与插头常驻 FixTop 高位(原型画序固定不变)
|
||||
physicCableRef.BeginDrag(PlugRef.transform.position);
|
||||
if (wasPlugged)
|
||||
{
|
||||
physicCableRef.StartUnplugResistance(unplugSocket);
|
||||
}
|
||||
|
||||
// 打开插孔
|
||||
BodyModuleSystem.OpenModuleSlots();
|
||||
@@ -261,9 +271,37 @@ namespace AibisDream.FixSystem
|
||||
physicCableRef.SetPointer(pointerWorldPos);
|
||||
}
|
||||
|
||||
public void UpdateDragProximity(Vector3 pointerWorldPos)
|
||||
{
|
||||
if (physicCableRef == null || BodyModuleSystem?.BodyModules == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float snapRange = BodyModuleSystem.plugSnappingRange;
|
||||
float assistRange = Mathf.Max(snapRange, snapRange * dragAssistRangeMultiplier);
|
||||
if (TryFindClosestModuleWithin(pointerWorldPos, assistRange, out var targetModule))
|
||||
{
|
||||
targetModule.OpenPlugSlot();
|
||||
BodyModuleSystem.CloseModuleSlots(targetModule);
|
||||
physicCableRef.SetDragAssistTarget(targetModule.GetSocketPos(), assistRange);
|
||||
}
|
||||
else
|
||||
{
|
||||
ClearDragHighlight();
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearDragHighlight()
|
||||
{
|
||||
physicCableRef?.ClearDragAssistTarget();
|
||||
BodyModuleSystem?.CloseModuleSlots(null);
|
||||
}
|
||||
|
||||
/// <summary>松手且未插中插孔:解除钉住,线缆自动回收,插头挂回绳末端。</summary>
|
||||
public void ReleasePlug()
|
||||
{
|
||||
ClearDragHighlight();
|
||||
physicCableRef.EndDrag();
|
||||
}
|
||||
|
||||
@@ -288,6 +326,30 @@ namespace AibisDream.FixSystem
|
||||
return res;
|
||||
}
|
||||
|
||||
private bool TryFindClosestModuleWithin(Vector3 sourcePos, float range, out BodyModule targetModule)
|
||||
{
|
||||
float closestDistance = Mathf.Infinity;
|
||||
BodyModule closestModule = null;
|
||||
|
||||
foreach (var bodyModule in BodyModuleSystem.BodyModules)
|
||||
{
|
||||
if (bodyModule == null || !bodyModule.gameObject.activeInHierarchy || !bodyModule.IsAvailable())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
float distance = Vector3.Distance(sourcePos, bodyModule.GetSocketPos().position);
|
||||
if (distance < closestDistance)
|
||||
{
|
||||
closestDistance = distance;
|
||||
closestModule = bodyModule;
|
||||
}
|
||||
}
|
||||
|
||||
targetModule = closestDistance <= range ? closestModule : null;
|
||||
return targetModule != null;
|
||||
}
|
||||
|
||||
public void PullUpCable()
|
||||
{
|
||||
if (isCableRetracted) return;
|
||||
|
||||
Reference in New Issue
Block a user