using CustomComponent; using DG.Tweening; using Definition.Enum; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; namespace UI { /// /// 可拖拽拼装部件:负责拖拽交互、出生点回退与放置到槽位。 /// [RequireComponent(typeof(RectTransform))] public class CombineDraggablePart : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler, IPointerEnterHandler, IPointerExitHandler { #region Inspector Config [SerializeField] private CombinePartType _partType = CombinePartType.Dou; [SerializeField] private bool _lockAfterPlaced = true; [SerializeField] private RectTransform _rectTransform; [SerializeField] private CanvasGroup _canvasGroup; [SerializeField] private Image _partImage; [SerializeField] private float _defaultScaleMultiplier = 0.8f; [SerializeField] private float _hoverScaleMultiplier = 1f; [SerializeField] private float _hoverTweenDuration = 0.15f; [SerializeField] private Ease _hoverTweenEase = Ease.OutQuad; [SerializeField] private float _returnToSpawnDuration = 0.4f; [SerializeField] private Ease _returnToSpawnEase = Ease.OutCubic; #endregion #region Spawn State private bool _spawnStateCached; private Transform _spawnParent; private Vector3 _spawnWorldPosition = Vector3.zero; private Quaternion _spawnWorldRotation = Quaternion.identity; private Vector3 _spawnWorldScale = Vector3.one; private int _spawnSiblingIndex; #endregion #region Runtime State private CombineComponent _controller; private CombineSlot _currentSlot; private bool _isPlaced; private bool _isLocked; private bool _isDragging; private Vector3 _originalLocalScale = Vector3.one; private Tween _scaleTween; private Tween _returnTween; private Vector2 _dragPointerOffset = Vector2.zero; #endregion #region Public Query public CombinePartType PartType => _partType; #endregion #region Unity Lifecycle private void Awake() { if (_rectTransform == null) { _rectTransform = transform as RectTransform; } if (_canvasGroup == null) { _canvasGroup = gameObject.GetOrAddComponent(); } _originalLocalScale = _rectTransform.localScale; ApplyScaleImmediate(_defaultScaleMultiplier); } private void OnDestroy() { KillScaleTween(); KillReturnTween(); } #endregion #region Setup /// /// 绑定玩法控制器。 /// public void BindController(CombineComponent controller) { _controller = controller; } public void ConfigureRuntime(CombinePartType partType, Sprite sprite) { _partType = partType; if (_partImage == null) { _partImage = GetComponentInChildren(true); } if (_partImage != null) { _partImage.sprite = sprite; if (sprite != null) { _partImage.SetNativeSize(); } } } /// /// 缓存出生点状态(仅首次缓存)。 /// public void CacheSpawnState() { if (_spawnStateCached) { return; } _spawnStateCached = true; _spawnParent = _rectTransform.parent; _spawnWorldPosition = _rectTransform.position; _spawnWorldRotation = _rectTransform.rotation; _spawnWorldScale = _rectTransform.localScale; _spawnSiblingIndex = _rectTransform.GetSiblingIndex(); } /// /// 重置部件到出生点,并清理槽位占用状态。 /// public void ResetToSpawn() { ClearSlotOccupancy(); _isDragging = false; _isPlaced = false; _isLocked = false; ReturnToSpawn(); } #endregion #region Drag Flow /// /// 开始拖拽:关闭射线阻挡并切到拖拽层级。 /// public void OnBeginDrag(PointerEventData eventData) { if (!CanStartDrag()) { return; } KillReturnTween(); _isDragging = true; _canvasGroup.blocksRaycasts = false; MoveToDragRoot(); _rectTransform.SetAsLastSibling(); CacheDragOffset(eventData); } /// /// 拖拽中:按屏幕坐标映射到父节点局部坐标更新位置。 /// public void OnDrag(PointerEventData eventData) { if (!CanDrag(eventData)) { return; } if (TryGetDragParent(out RectTransform dragParent) && TryGetPointerLocalPosition(eventData, dragParent, out Vector2 pointerLocalPosition)) { _rectTransform.anchoredPosition = pointerLocalPosition + _dragPointerOffset; return; } _rectTransform.anchoredPosition += eventData.delta; } /// /// 结束拖拽:恢复射线阻挡;未成功放置则回到出生点。 /// public void OnEndDrag(PointerEventData eventData) { _isDragging = false; if (_isLocked) { return; } _canvasGroup.blocksRaycasts = true; if (!_isPlaced) { if (ShouldKeepDroppedInPartArea(eventData)) { return; } ReturnToSpawnAnimated(); } } public void OnPointerEnter(PointerEventData eventData) { if (_isLocked || _isPlaced || _isDragging) { return; } PlayScaleTween(_hoverScaleMultiplier); } public void OnPointerExit(PointerEventData eventData) { if (_isLocked || _isPlaced || _isDragging) { return; } PlayScaleTween(_defaultScaleMultiplier); } /// /// 校验是否允许开始拖拽。 /// private bool CanStartDrag() { return !_isLocked && !_isPlaced; } /// /// 校验拖拽中是否可更新位置。 /// private bool CanDrag(PointerEventData eventData) { return !_isLocked && !_isPlaced && eventData != null; } /// /// 将部件切到控制器提供的拖拽根节点。 /// private void MoveToDragRoot() { if (_controller == null) { return; } Transform dragRoot = _controller.GetDragRoot(); if (dragRoot != null) { _rectTransform.SetParent(dragRoot, true); } } private void CacheDragOffset(PointerEventData eventData) { _dragPointerOffset = Vector2.zero; if (!TryGetDragParent(out RectTransform dragParent)) { return; } if (TryGetPointerLocalPosition(eventData, dragParent, out Vector2 pointerLocalPosition)) { _dragPointerOffset = _rectTransform.anchoredPosition - pointerLocalPosition; } } private bool TryGetDragParent(out RectTransform dragParent) { dragParent = _rectTransform.parent as RectTransform; return dragParent != null; } private bool TryGetPointerLocalPosition(PointerEventData eventData, RectTransform targetRect, out Vector2 localPosition) { localPosition = Vector2.zero; if (eventData == null || targetRect == null) { return false; } Camera eventCamera = ResolveEventCamera(eventData); return RectTransformUtility.ScreenPointToLocalPointInRectangle(targetRect, eventData.position, eventCamera, out localPosition); } private Camera ResolveEventCamera(PointerEventData eventData) { if (eventData != null) { if (eventData.pressEventCamera != null) { return eventData.pressEventCamera; } if (eventData.enterEventCamera != null) { return eventData.enterEventCamera; } } Canvas canvas = GetComponentInParent(); if (canvas != null && canvas.renderMode != RenderMode.ScreenSpaceOverlay) { return canvas.worldCamera; } return null; } private bool ShouldKeepDroppedInPartArea(PointerEventData eventData) { if (eventData == null) { return false; } if (!TryGetDragArea(out RectTransform dragArea)) { return false; } Camera eventCamera = ResolveEventCamera(eventData); if (!RectTransformUtility.RectangleContainsScreenPoint(dragArea, eventData.position, eventCamera)) { return false; } ClampInsideDragArea(dragArea); return true; } private bool TryGetDragArea(out RectTransform dragArea) { dragArea = null; if (_controller == null) { return false; } dragArea = _controller.GetDragRoot() as RectTransform; return dragArea != null; } private void ClampInsideDragArea(RectTransform dragArea) { if (dragArea == null) { return; } Vector2 current = _rectTransform.anchoredPosition; Rect areaRect = dragArea.rect; Rect partRect = _rectTransform.rect; Vector2 pivot = _rectTransform.pivot; Vector3 scale = _rectTransform.localScale; float width = Mathf.Abs(partRect.width * scale.x); float height = Mathf.Abs(partRect.height * scale.y); float minX = areaRect.xMin + width * Mathf.Clamp01(pivot.x); float maxX = areaRect.xMax - width * (1f - Mathf.Clamp01(pivot.x)); float minY = areaRect.yMin + height * Mathf.Clamp01(pivot.y); float maxY = areaRect.yMax - height * (1f - Mathf.Clamp01(pivot.y)); if (minX > maxX) { float centerX = (areaRect.xMin + areaRect.xMax) * 0.5f; minX = centerX; maxX = centerX; } if (minY > maxY) { float centerY = (areaRect.yMin + areaRect.yMax) * 0.5f; minY = centerY; maxY = centerY; } current.x = Mathf.Clamp(current.x, minX, maxX); current.y = Mathf.Clamp(current.y, minY, maxY); _rectTransform.anchoredPosition = current; } #endregion #region Placement /// /// 将部件恢复到出生点状态。 /// public void ReturnToSpawn() { KillReturnTween(); if (_spawnParent == null) { return; } _rectTransform.SetParent(_spawnParent, true); _rectTransform.position = _spawnWorldPosition; _rectTransform.rotation = _spawnWorldRotation; _rectTransform.localScale = _spawnWorldScale; _rectTransform.SetSiblingIndex(_spawnSiblingIndex); } /// /// 应用成功放置到槽位后的表现与状态。 /// internal void PlaceToSlot(CombineSlot slot) { KillScaleTween(); KillReturnTween(); _isDragging = false; _currentSlot = slot; _isPlaced = true; _isLocked = _lockAfterPlaced; _canvasGroup.blocksRaycasts = false; } /// /// 清理当前槽位占用记录。 /// private void ClearSlotOccupancy() { if (_currentSlot != null) { _currentSlot.ClearOccupiedPart(this); _currentSlot = null; } } private void ApplyScaleImmediate(float multiplier) { _rectTransform.localScale = _originalLocalScale * Mathf.Max(0f, multiplier); } private void PlayScaleTween(float targetMultiplier) { KillScaleTween(); _scaleTween = _rectTransform .DOScale(_originalLocalScale * Mathf.Max(0f, targetMultiplier), _hoverTweenDuration) .SetEase(_hoverTweenEase) .SetUpdate(true); } public void ReturnToSpawnAnimated() { if (_spawnParent == null) { return; } if (_returnToSpawnDuration <= 0f) { ReturnToSpawn(); return; } KillReturnTween(); _rectTransform.SetParent(_spawnParent, true); _rectTransform.SetSiblingIndex(_spawnSiblingIndex); _returnTween = DOTween.Sequence() .Append(_rectTransform.DOMove(_spawnWorldPosition, _returnToSpawnDuration)) .Join(_rectTransform.DORotateQuaternion(_spawnWorldRotation, _returnToSpawnDuration)) .Join(_rectTransform.DOScale(_spawnWorldScale, _returnToSpawnDuration)) .SetEase(_returnToSpawnEase) .SetUpdate(true) .OnKill(() => _returnTween = null); } private void KillScaleTween() { if (_scaleTween != null && _scaleTween.IsActive()) { _scaleTween.Kill(); } _scaleTween = null; } private void KillReturnTween() { if (_returnTween != null && _returnTween.IsActive()) { _returnTween.Kill(); } _returnTween = null; } #endregion } }