using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; using System.Collections; using TowerClimberChronicles; using DG.Tweening; [RequireComponent(typeof(RectTransform))] public class DragPuzzle : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler, IPointerDownHandler { [Header("Snap config")] public Vector2 snapTarget; public float snapDistance; private bool lockOnSnap = true; [Header("State")] public bool locked = false; // 默认可交互,若要初始不可拖动改为 true private RectTransform rect; private Canvas rootCanvas; private Camera uiCamera; private ScrollRect scrollRect; private bool isFlying = false; private Vector2 pointerOffset; // 在当前父Rect空间的偏移 private Vector3 cachedLocalScale; private Image overlay; void Awake() { rect = GetComponent(); rootCanvas = GetRootCanvas(transform); scrollRect = GameObject.Find("ScrollView")?.GetComponent(); // 必须存在:EventSystem + Canvas(含GraphicRaycaster) + 本物体或子物体上有Graphic且RaycastTarget启用 uiCamera = rootCanvas && rootCanvas.renderMode == RenderMode.ScreenSpaceOverlay ? null : (rootCanvas ? rootCanvas.worldCamera : null); cachedLocalScale = rect.localScale; GameDispatcher.Instance.AddListener(GameMsg.PuzzleIn, PlayHighlight); overlay = transform.Find("mask_obj/light_obj").GetComponent(); } void OnDestroy() { GameDispatcher.Instance.RemoveListener(GameMsg.PuzzleIn, PlayHighlight); } private Canvas GetRootCanvas(Transform t) { Canvas c = t.GetComponentInParent(); return c ? c.rootCanvas : null; } public void OnPointerDown(PointerEventData eventData) { if (locked || isFlying) return; // 不在点击阶段切换父物体,避免点击瞬间跳位/消失 // 仅计算当前父Rect空间的指针偏移 Vector2 parentLocalPoint; RectTransformUtility.ScreenPointToLocalPointInRectangle( rect.parent as RectTransform, eventData.position, uiCamera, out parentLocalPoint ); pointerOffset = parentLocalPoint - rect.anchoredPosition; } // 替换你当前的 OnBeginDrag(保留核心逻辑,补回“中心锚点”) public void OnBeginDrag(PointerEventData eventData) { if (locked || isFlying) return; GameDispatcher.Instance.Dispatch(GameMsg.PuzzleTimeStart); // 提升到根 Canvas,保持世界坐标 if (rootCanvas && rect.parent != rootCanvas.transform) { Vector3 worldPos = rect.position; rect.SetParent(rootCanvas.transform, true); rect.position = worldPos; rect.localScale = cachedLocalScale; // 关键:开始拖拽时把锚点/枢轴改为中心,但保持世界位置不变 SetAnchorPivotCenterKeepWorld(rect); // 重新计算偏移(父变成根 Canvas) Vector2 canvasLocalPoint; RectTransformUtility.ScreenPointToLocalPointInRectangle( rootCanvas.transform as RectTransform, eventData.position, uiCamera, out canvasLocalPoint ); pointerOffset = canvasLocalPoint - rect.anchoredPosition; } transform.SetAsLastSibling(); } public void OnDrag(PointerEventData eventData) { if (locked || isFlying) return; // 在当前父Rect空间移动(通常是根Canvas) RectTransform parentRect = rect.parent as RectTransform; Vector2 parentLocalPoint; RectTransformUtility.ScreenPointToLocalPointInRectangle( parentRect, eventData.position, uiCamera, out parentLocalPoint ); rect.anchoredPosition = parentLocalPoint - pointerOffset; } // 在类里新增一个辅助方法:将锚点/枢轴改为中心,但保持世界位置不变 private static void SetAnchorPivotCenterKeepWorld(RectTransform rt) { // 记录世界位置与旋转 Vector3 worldPos = rt.position; Quaternion worldRot = rt.rotation; // 改锚点与枢轴为中心 rt.anchorMin = new Vector2(0.5f, 0.5f); rt.anchorMax = new Vector2(0.5f, 0.5f); rt.pivot = new Vector2(0.5f, 0.5f); // 恢复世界位置与旋转(避免位置跳变) rt.position = worldPos; rt.rotation = worldRot; } public void PlayHighlight(object obj) { Vector2 vec2 = (Vector2)obj; float dist = Vector2.Distance(rect.position, vec2); if (!locked && (dist > 0.1f)) return; float delay = dist * 0.005f; DOVirtual.DelayedCall(delay, () => { // 清理旧动画 overlay.DOKill(true); rect.DOKill(true); // overlay 从透明开始 var c = overlay.color; overlay.color = new Color(c.r, c.g, c.b, 0f); // 高亮动画:淡入再淡出 overlay.DOFade(0.4f, 0.15f).SetEase(Ease.InOutSine) .OnComplete(() => { overlay.DOFade(0f, 0.15f).SetEase(Ease.InOutSine); }); // 缩放动画:先缩小再放大 rect.localScale = cachedLocalScale; rect.DOScale(cachedLocalScale * 0.99f, 0.3f).SetEase(Ease.InOutSine) .OnComplete(() => { rect.DOScale(cachedLocalScale, 0.3f).SetEase(Ease.InOutSine); }); }); } private ManualHorizontalList manualHorizontalList; private void RefereshList() { if (manualHorizontalList == null) manualHorizontalList = GameObject.Find("puzzle_parent").GetComponent(); manualHorizontalList.Refresh(); } public void OnEndDrag(PointerEventData eventData) { if (locked || isFlying) return; // 近吸附 float dist = Vector2.Distance(rect.anchoredPosition, snapTarget); if (dist <= snapDistance) { rect.anchoredPosition = snapTarget; Vector2 snapPos = rect.position; GameDispatcher.Instance.Dispatch(GameMsg.PuzzleIn, snapPos); rect.SetSiblingIndex(2); if (lockOnSnap) locked = true; AudioManager.Instance.PlayDynamicEffect(AudioConst.puzzle_click); RefereshList(); return; } // 若释放在 ScrollRect viewport 内,归还到 content 并插入排序 if (scrollRect) { RectTransform viewport = scrollRect.viewport ? scrollRect.viewport : scrollRect.GetComponent(); if (RectTransformUtility.RectangleContainsScreenPoint(viewport, eventData.position, eventData.pressEventCamera)) { RectTransform content = scrollRect.content; Vector3 worldPos = rect.position; rect.SetParent(content, true); // 保持世界坐标再转本地 Vector2 localPoint = content.InverseTransformPoint(worldPos); rect.anchoredPosition = localPoint; int insertIndex = content.childCount; for (int i = 0; i < content.childCount; i++) { RectTransform child = content.GetChild(i) as RectTransform; if (localPoint.x < child.localPosition.x) { insertIndex = i; break; } } rect.SetSiblingIndex(insertIndex); rect.localScale = cachedLocalScale; } RefereshList(); } } // ---------------- 飞行吸附(忽略锁定即返回) ---------------- public void FlyAndSnap(float duration = 0.5f) { if (locked || isFlying) return; isFlying = true; GameDispatcher.Instance.Dispatch(GameMsg.PuzzleTimeStart); FlyToSnapTarget(duration); } private void FlyToSnapTarget(float duration) { // 在根Canvas中移动 if (rootCanvas && rect.parent != rootCanvas.transform) { Vector3 worldPos = rect.position; rect.SetParent(rootCanvas.transform, true); rect.position = worldPos; rect.localScale = cachedLocalScale; } // 关键:自动飞行时也改锚点/枢轴为中心,但保持世界位置不变 Vector3 savedWorldPos = rect.position; Quaternion savedWorldRot = rect.rotation; rect.anchorMin = rect.anchorMax = new Vector2(0.5f, 0.5f); rect.pivot = new Vector2(0.5f, 0.5f); rect.position = savedWorldPos; rect.rotation = savedWorldRot; Vector2 startPos = rect.anchoredPosition; Vector2 target = snapTarget; // 如果不需要动画,直接完成 if (duration <= 0f || startPos == target) { rect.anchoredPosition = target; DoneFly(); return; } // 使用 DOTween 动画 rect.DOAnchorPos(target, duration) .SetEase(Ease.Linear) .OnComplete(() => { rect.anchoredPosition = target; DoneFly(); }); } private void DoneFly() { if (lockOnSnap) locked = true; float dist = Vector2.Distance(rect.anchoredPosition, snapTarget); rect.anchoredPosition = snapTarget; Vector2 snapPos = rect.position; GameDispatcher.Instance.Dispatch(GameMsg.PuzzleIn, snapPos); AudioManager.Instance.PlayDynamicEffect(AudioConst.puzzle_click); RefereshList(); isFlying = false; } // 外部可显式设置锁定 public void SetLocked(bool value) => locked = value; }