Files

286 lines
9.9 KiB
C#
Raw Permalink Normal View History

2026-07-06 16:27:14 +08:00
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<RectTransform>();
rootCanvas = GetRootCanvas(transform);
scrollRect = GameObject.Find("ScrollView")?.GetComponent<ScrollRect>();
// 必须存在: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<Image>();
}
void OnDestroy()
{
GameDispatcher.Instance.RemoveListener(GameMsg.PuzzleIn, PlayHighlight);
}
private Canvas GetRootCanvas(Transform t)
{
Canvas c = t.GetComponentInParent<Canvas>();
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>();
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<RectTransform>();
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;
}