using System; using AibisDream.Framework; using AibisDream.Kit; using AibisDream.Utility; using DG.Tweening; using UnityEngine; using UnityEngine.EventSystems; namespace AibisDream.FixSystem { [RequireComponent(typeof(EventTriggerEx))] public class Plug : MonoBehaviour, IInteraction { private static readonly int ShineFade = Shader.PropertyToID("_ShineFade"); // 插头常态排序:与线缆(Tools/47)同层且在其上,拖拽不再切层—— // 原型里插头永远画在线之上、画序不随交互变化;整套线缆系统被外部元素 // 遮挡时也必须整体一起被盖,不能插头浮出而线身沉底。 // Tools 整层高于 FixTop(屏幕 UI Canvas 在 FixTop/100),Tools/48 是插头 // 历史沿用且验证过不被面板遮挡的位置;剪线演出(Tools/50+)仍可盖住整套线缆 private const string NormalSortingLayer = "Tools"; private const int NormalSortingOrder = 48; #region 插头当前信息 [SerializeField] private Vector3 startPosOffset = new(0.3f, -2f, 0); private Vector3 _startPos; private readonly Quaternion _defaultRotation = Quaternion.Euler(0, 0, 180); #endregion #region 索引 private CablePanel cablePanel; private SpriteRenderer _sprite; private Transform _plugRootPos; private EventTriggerEx _trigger; public Transform PlugRoot => _plugRootPos; #endregion public event Action OnInsertSocket; public event Action OnPullUpSocket; 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() { InitComponentRef(); EventRegister(); _startPos = transform.position + startPosOffset; // 预制体里序列化的还是旧的 Tools 层,启动时归一化;收起态由 CablePanel 之后覆盖 ApplyNormalSorting(); } private void InitComponentRef() { cablePanel = transform.GetComponentInParent(true); _sprite = GetComponent(); _plugRootPos = transform.Find("Plug Root Pos"); _trigger = GetComponent(); // 处理插头高亮 _sprite.material.SetFloat(ShineFade, GlobalVariableKit.IsPlugHighLight ? 1 : 0); } private void EventRegister() { _trigger.Register(EventTriggerType.Drag, OnDrag); _trigger.Register(EventTriggerType.PointerDown, OnPointerDown); _trigger.Register(EventTriggerType.PointerUp, OnPointerUp); } /// /// 插入某个socket /// /// 目标socket private void InsertSocket(ISocket targetSocket) { if (_isInserting) return; _isInserting = true; cablePanel.ClearDragHighlight(targetSocket as BodyModule); ClearFeedbackOffset(); 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; } /// /// 从某个Socket拔出来 /// public void PullUpSocket() { _insertTween?.Kill(); _isInserting = false; _sprite.enabled = true; ApplyNormalSorting(); transform.DOKill(false); transform.localScale = Vector3.one; PlayUnplugFeedback(); OnPullUpSocket?.Invoke(); } /// 读档/静默:仅恢复插头精灵可见,不触发 CablePanel 事件。 public void PullUpSocketSilent() { ClearFeedbackOffset(); _sprite.enabled = true; ApplyNormalSorting(); } /// 恢复插头常态排序(收起态会被 CablePanel 压到线盘下方,放下时调回)。 public void ApplyNormalSorting() { _sprite.sortingLayerName = NormalSortingLayer; _sprite.sortingOrder = NormalSortingOrder; } /// 读档:无动画回到初始位。 public void RestoreAtStartPositionImmediate() { ClearFeedbackOffset(); transform.rotation = _defaultRotation; transform.position = _startPos; } /// 读档:无动画插入指定 socket 的视觉终态。 public void RestoreInsertedAt(Transform socketPos) { if (socketPos == null) return; ClearFeedbackOffset(); transform.position = socketPos.position; _sprite.enabled = false; } public void SetSpriteVisible(bool visible) { _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; } /// 强制中断拔出反馈动画(挤压 punch scale 等),立即恢复到干净的常态缩放。 public void CancelFeedbackTween() { ClearFeedbackOffset(); } private void OnDrag(BaseEventData eventData) { if (!_isDragging) return; if (eventData is PointerEventData pointerData) { var pointerWorldPos = CameraKit.GetMouseWorldPos(pointerData.position); cablePanel.UpdateDragPointer(pointerWorldPos); } } private void OnPointerDown(BaseEventData eventData) { if (EventSystemEx.Instance.isLocked) return; if (eventData is PointerEventData { button: PointerEventData.InputButton.Right }) return; ApplyNormalSorting(); OnPlugPointerDown?.Invoke(); TryCloseHighlight(); _isDragging = true; AudioManager.Instance.PlaySfx("event:/FollowInput/plugpick"); } private void TryCloseHighlight() { if (!GlobalVariableKit.IsPlugHighLight) return; GlobalVariableKit.IsPlugHighLight = false; _sprite.material.SetFloat(ShineFade, 0); } private void OnPointerUp(BaseEventData eventData) { if (EventSystemEx.Instance.isLocked) { _isDragging = false; return; } if (eventData is PointerEventData { button: PointerEventData.InputButton.Right }) { return; } // 停止拖拽 _isDragging = false; // 寻找可吸附的Module if (cablePanel.TryFindClosestModule(transform.position, out var targetModule)) { InsertSocket(targetModule); } else { ReturnToStartPosition(); } } public void ReturnToStartPosition() { // 解除末端钉住,线缆自动回收到最短长度,插头挂在绳末端跟着回去 cablePanel.ReleasePlug(); } void OnDrawGizmosSelected() { Gizmos.color = Color.yellow; Gizmos.DrawWireSphere(transform.position, 0.5f); } public bool IsActive => true; public bool IsAvailable => !cablePanel.isCableRetracted; public GameObject GetGameObject() { return gameObject; } public Vector3 GetStartPos() { return _startPos; } } }