引入 DOTween 来实现部分动画,具体有:

- 鼠标与 Part 交互时的缩放
- Part 放置失败回到原位的位移
This commit is contained in:
SepComet
2026-02-08 18:02:11 +08:00
parent 8763955323
commit 6b8982f0cd
50 changed files with 6201 additions and 6 deletions
@@ -1,4 +1,5 @@
using CustomComponent;
using DG.Tweening;
using Definition.Enum;
using UnityEngine;
using UnityEngine.EventSystems;
@@ -10,7 +11,7 @@ namespace UI
/// </summary>
[RequireComponent(typeof(RectTransform))]
[DisallowMultipleComponent]
public class CombineDraggablePart : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
public class CombineDraggablePart : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler, IPointerEnterHandler, IPointerExitHandler
{
#region Inspector Config
@@ -26,6 +27,18 @@ namespace UI
[SerializeField] private CanvasGroup _canvasGroup;
[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
@@ -54,6 +67,14 @@ namespace UI
private bool _isLocked;
private bool _isDragging;
private Vector3 _originalLocalScale = Vector3.one;
private Tween _scaleTween;
private Tween _returnTween;
#endregion
#region Public Query
@@ -64,6 +85,32 @@ namespace UI
#endregion
#region Unity Lifecycle
private void Awake()
{
if (_rectTransform == null)
{
_rectTransform = transform as RectTransform;
}
if (_canvasGroup == null)
{
_canvasGroup = gameObject.GetOrAddComponent<CanvasGroup>();
}
_originalLocalScale = _rectTransform.localScale;
ApplyScaleImmediate(_defaultScaleMultiplier);
}
private void OnDestroy()
{
KillScaleTween();
KillReturnTween();
}
#endregion
#region Setup
/// <summary>
@@ -111,6 +158,7 @@ namespace UI
public void ResetToSpawn()
{
ClearSlotOccupancy();
_isDragging = false;
_isPlaced = false;
_isLocked = false;
ReturnToSpawn();
@@ -130,6 +178,8 @@ namespace UI
return;
}
KillReturnTween();
_isDragging = true;
_canvasGroup.blocksRaycasts = false;
MoveToDragRoot();
_rectTransform.SetAsLastSibling();
@@ -153,6 +203,8 @@ namespace UI
/// </summary>
public void OnEndDrag(PointerEventData eventData)
{
_isDragging = false;
if (_isLocked)
{
return;
@@ -162,10 +214,30 @@ namespace UI
if (!_isPlaced)
{
ReturnToSpawn();
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);
}
/// <summary>
/// 校验是否允许开始拖拽。
/// </summary>
@@ -208,6 +280,8 @@ namespace UI
/// </summary>
public void ReturnToSpawn()
{
KillReturnTween();
if (_spawnParent == null)
{
return;
@@ -225,6 +299,9 @@ namespace UI
/// </summary>
internal void PlaceToSlot(CombineSlot slot)
{
KillScaleTween();
KillReturnTween();
_isDragging = false;
_currentSlot = slot;
_isPlaced = true;
_isLocked = _lockAfterPlaced;
@@ -249,6 +326,66 @@ namespace UI
}
}
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);
}
private 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
}
}
}
@@ -39,6 +39,14 @@ namespace UI
[SerializeField] private float _partVerticalSpacing = 120f;
[SerializeField] [Range(0f, 1f)] private float _partSpawnMinXNormalized = 0.55f;
[SerializeField] [Range(0f, 1f)] private float _partSpawnMaxXNormalized = 0.95f;
[SerializeField] [Range(0f, 1f)] private float _partSpawnMinYNormalized = 0.1f;
[SerializeField] [Range(0f, 1f)] private float _partSpawnMaxYNormalized = 0.9f;
private readonly List<GameObject> _runtimeNodes = new();
#endregion
@@ -194,14 +202,65 @@ namespace UI
_runtimeNodes.Add(slot.gameObject);
}
foreach (var partData in context.Parts)
List<CombinePartContext> partContexts = BuildPartContextList(context);
for (int i = 0; i < partContexts.Count; i++)
{
CombinePartContext partData = partContexts[i];
CombineDraggablePart part = Instantiate(_partPrefab, _partRoot, false);
part.Initialize(partData);
SetPartSpawnPosition(part, i);
_runtimeNodes.Add(part.gameObject);
}
}
private List<CombinePartContext> BuildPartContextList(CombineFormContext context)
{
if (context.Parts != null && context.Parts.Count > 0)
{
return context.Parts;
}
List<CombinePartContext> fallbackParts = new List<CombinePartContext>(context.Slots.Count);
for (int i = 0; i < context.Slots.Count; i++)
{
CombineSlotContext slot = context.Slots[i];
fallbackParts.Add(new CombinePartContext
{
PartType = slot.RequiredPartType,
PartDisplayName = slot.RequiredPartType.ToString(),
MechanicsExplanation = slot.MechanicsExplanation,
LockAfterPlaced = true
});
}
return fallbackParts;
}
private void SetPartSpawnPosition(CombineDraggablePart part, int index)
{
RectTransform partRect = part.transform as RectTransform;
if (partRect == null || _partRoot == null)
{
return;
}
Rect rootRect = _partRoot.rect;
if (rootRect.width <= 0f || rootRect.height <= 0f)
{
partRect.anchoredPosition = _partStartAnchoredPosition + Vector2.down * (_partVerticalSpacing * index);
return;
}
float minX = Mathf.Lerp(rootRect.xMin, rootRect.xMax, Mathf.Min(_partSpawnMinXNormalized, _partSpawnMaxXNormalized));
float maxX = Mathf.Lerp(rootRect.xMin, rootRect.xMax, Mathf.Max(_partSpawnMinXNormalized, _partSpawnMaxXNormalized));
float minY = Mathf.Lerp(rootRect.yMin, rootRect.yMax, Mathf.Min(_partSpawnMinYNormalized, _partSpawnMaxYNormalized));
float maxY = Mathf.Lerp(rootRect.yMin, rootRect.yMax, Mathf.Max(_partSpawnMinYNormalized, _partSpawnMaxYNormalized));
partRect.anchoredPosition = new Vector2(
Random.Range(minX, maxX),
Random.Range(minY, maxY));
}
#endregion
#region Event Handlers
@@ -244,4 +303,4 @@ namespace UI
#endregion
}
}
}
@@ -118,6 +118,13 @@ namespace UI
_requireStrictOrder = data.RequireStrictOrder;
_mechanicsExplanation = data.MechanicsExplanation ?? string.Empty;
_mismatchHint = data.MismatchHint ?? string.Empty;
RectTransform rectTransform = transform as RectTransform;
if (rectTransform != null)
{
rectTransform.anchoredPosition = data.AnchoredPosition;
rectTransform.sizeDelta = data.SizeDelta;
}
}
/// <summary>
@@ -192,4 +199,4 @@ namespace UI
#endregion
}
}
}