提交小游戏项目
This commit is contained in:
@@ -0,0 +1,285 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user