449 lines
15 KiB
C#
449 lines
15 KiB
C#
using System;
|
||
using AibisDream.Framework;
|
||
using DG.Tweening;
|
||
using UnityEngine;
|
||
using UnityEngine.EventSystems;
|
||
|
||
namespace AibisDream
|
||
{
|
||
public class ShapeDragger : MonoBehaviour, IInteraction
|
||
{
|
||
#region 编辑器字段
|
||
[SerializeField] private bool isInteractable = true;
|
||
|
||
[Header("交互模式(由外界赋值控制)")]
|
||
[SerializeField] private InteractionMode interactionMode = InteractionMode.Drag;
|
||
|
||
[SerializeField] private int sortingLayerId;
|
||
[SerializeField] private int dragLayerOrder;
|
||
[SerializeField] private int pressLayerOrder;
|
||
|
||
/// <summary>
|
||
/// 闲置时层级,由 BlockShape 在 LoadVisualPrefab 后从 Visual 的 SortingGroup 赋值
|
||
/// </summary>
|
||
public int IdleLayerOrder { get; set; }
|
||
|
||
[Header("长按设置")]
|
||
[SerializeField] private float longPressDuration = 0.65f;
|
||
[SerializeField] private float longPressScale = 1.1f;
|
||
[SerializeField] private float shakeIntensity = 1f;
|
||
|
||
[Header("拖拽跟随")]
|
||
[SerializeField] private float dragFollowSpeed = 15f;
|
||
#endregion
|
||
|
||
// 拖拽状态
|
||
private DragState _currentState = DragState.Placed;
|
||
public DragState CurrentState
|
||
{
|
||
get => _currentState;
|
||
set
|
||
{
|
||
if (value == _currentState) return;
|
||
// 处理状态切换
|
||
HandleStateChange(value);
|
||
_currentState = value;
|
||
}
|
||
}
|
||
|
||
// 长按相关状态
|
||
private bool _isPressing;
|
||
private bool _isLongPressTriggered;
|
||
private bool _canEndDragOnClick; // 标记是否可以通过点击结束拖拽
|
||
private Sequence _longPressSequence;
|
||
private Vector3 _originalScale;
|
||
private Vector3 _originalPosition;
|
||
|
||
// 组件引用
|
||
private BlockShape _blockShape;
|
||
private EventTriggerEx _eventTrigger;
|
||
|
||
public bool IsInteractable { get => isInteractable; set => isInteractable = value; }
|
||
public InteractionMode InteractionMode { get => interactionMode; set => interactionMode = value; }
|
||
public IBlockPuzzleGrid TargetGrid => BlockPuzzleSystem.Instance.grid;
|
||
public IBlockShape BlockShape => _blockShape;
|
||
|
||
#region 事件定义
|
||
|
||
/// <summary>
|
||
/// 拖拽开始事件 - 携带详细信息
|
||
/// </summary>
|
||
public event EventHandler<DragStartEventArgs> OnDragStartDetailed;
|
||
|
||
/// <summary>
|
||
/// 拖拽更新事件 - 携带详细信息
|
||
/// </summary>
|
||
public event EventHandler<DragUpdateEventArgs> OnDragUpdateDetailed;
|
||
|
||
/// <summary>
|
||
/// 拖拽结束事件 - 携带详细信息,由订阅者处理具体逻辑
|
||
/// </summary>
|
||
public event EventHandler<DragEndEventArgs> OnDragEndDetailed;
|
||
|
||
/// <summary>
|
||
/// 点击事件
|
||
/// </summary>
|
||
public event Action<BlockShape> OnClick;
|
||
|
||
// 保留原有简单事件以保持向后兼容
|
||
public event Action<BlockShape> OnDragStart;
|
||
public event Action<BlockShape> OnDragUpdate;
|
||
public event Action<BlockShape, bool> OnDragEnd;
|
||
|
||
#endregion
|
||
|
||
private void Awake()
|
||
{
|
||
_blockShape = GetComponent<BlockShape>();
|
||
_eventTrigger = GetComponent<EventTriggerEx>();
|
||
|
||
RegisterEvent();
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
_eventTrigger.UnRegister(EventTriggerType.PointerDown, OnPointerDown);
|
||
_eventTrigger.UnRegister(EventTriggerType.PointerUp, OnPointerUp);
|
||
_eventTrigger.UnRegister(EventTriggerType.PointerClick, OnPointerClick);
|
||
|
||
// 清理 DOTween 动画
|
||
_longPressSequence?.Kill();
|
||
transform.DOKill();
|
||
}
|
||
|
||
private void RegisterEvent()
|
||
{
|
||
_eventTrigger.Register(EventTriggerType.PointerDown, OnPointerDown);
|
||
_eventTrigger.Register(EventTriggerType.PointerUp, OnPointerUp);
|
||
_eventTrigger.Register(EventTriggerType.PointerClick, OnPointerClick);
|
||
}
|
||
|
||
private void Update()
|
||
{
|
||
if (CurrentState == DragState.Dragging)
|
||
{
|
||
UpdateDrag();
|
||
}
|
||
}
|
||
|
||
private void OnPointerDown(BaseEventData eventData)
|
||
{
|
||
if (eventData is not PointerEventData { button: PointerEventData.InputButton.Left })
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (!isInteractable) return;
|
||
|
||
// 如果当前正在拖拽中,标记可以通过点击结束
|
||
if (CurrentState == DragState.Dragging)
|
||
{
|
||
_canEndDragOnClick = true;
|
||
}
|
||
// 只有在静止状态才能开始拖拽(点击模式不启动拖拽)
|
||
else if (interactionMode == InteractionMode.Drag)
|
||
{
|
||
if (CurrentState == DragState.Placed)
|
||
{
|
||
// Placed状态:需要长按才能拖拽(防止误触)
|
||
_isPressing = true;
|
||
_isLongPressTriggered = false;
|
||
_canEndDragOnClick = false; // 长按开始时不允许点击结束
|
||
StartLongPressAnimation();
|
||
}
|
||
else if (CurrentState == DragState.Idle)
|
||
{
|
||
// Idle状态:点击立即开始拖拽(快速取用)
|
||
StartDrag();
|
||
}
|
||
}
|
||
}
|
||
|
||
private void OnPointerUp(BaseEventData eventData)
|
||
{
|
||
if (eventData is not PointerEventData { button: PointerEventData.InputButton.Left })
|
||
{
|
||
return;
|
||
}
|
||
|
||
// 如果还在长按过程中(未完成),则取消
|
||
if (_isPressing && !_isLongPressTriggered)
|
||
{
|
||
CancelLongPress();
|
||
}
|
||
}
|
||
|
||
private void OnPointerClick(BaseEventData eventData)
|
||
{
|
||
if (eventData is not PointerEventData { button: PointerEventData.InputButton.Left })
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (interactionMode == InteractionMode.Click)
|
||
{
|
||
if (!isInteractable) return;
|
||
OnClick?.Invoke(_blockShape);
|
||
PlayClickFeedback();
|
||
return;
|
||
}
|
||
|
||
// 拖拽模式:只有在拖拽中且允许点击结束时才结束拖拽
|
||
if (CurrentState == DragState.Dragging && _canEndDragOnClick)
|
||
{
|
||
EndDrag();
|
||
_canEndDragOnClick = false; // 重置标记
|
||
}
|
||
}
|
||
|
||
public void StartDrag()
|
||
{
|
||
if (!isInteractable) return;
|
||
|
||
// 记录原始位置
|
||
_originalPosition = transform.position;
|
||
|
||
CurrentState = DragState.Dragging;
|
||
|
||
// 创建事件参数
|
||
var args = new DragStartEventArgs
|
||
{
|
||
Shape = _blockShape,
|
||
StartPosition = transform.position
|
||
};
|
||
|
||
// 触发详细事件
|
||
OnDragStartDetailed?.Invoke(this, args);
|
||
|
||
// 触发兼容事件
|
||
OnDragStart?.Invoke(_blockShape);
|
||
}
|
||
|
||
public void EndDrag()
|
||
{
|
||
// 清理长按状态
|
||
CancelLongPress();
|
||
|
||
// 创建事件参数,由订阅者处理具体逻辑
|
||
var args = new DragEndEventArgs
|
||
{
|
||
Shape = _blockShape,
|
||
EndPosition = transform.position,
|
||
FinalState = DragState.Idle, // 默认状态
|
||
IsHandled = false,
|
||
ResultType = DragEndResultType.None
|
||
};
|
||
|
||
// 触发事件,让订阅者处理回收、吸附等逻辑
|
||
OnDragEndDetailed?.Invoke(this, args);
|
||
|
||
// 根据事件处理结果更新状态
|
||
if (args.IsHandled)
|
||
{
|
||
CurrentState = args.FinalState;
|
||
|
||
// 应用视觉状态
|
||
ApplyVisualState(args.FinalState);
|
||
}
|
||
else
|
||
{
|
||
// 未被处理,恢复原状
|
||
CurrentState = DragState.Idle;
|
||
transform.localScale = Vector3.one;
|
||
|
||
// 可选:播放未吸附的动画(如弹回)
|
||
PlayDropAnimation();
|
||
}
|
||
|
||
// 触发兼容事件
|
||
OnDragEnd?.Invoke(_blockShape, CurrentState == DragState.Placed);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 应用视觉状态
|
||
/// </summary>
|
||
private void ApplyVisualState(DragState state)
|
||
{
|
||
switch (state)
|
||
{
|
||
case DragState.Placed:
|
||
transform.localScale = Vector3.one;
|
||
break;
|
||
case DragState.Idle:
|
||
transform.localScale = Vector3.one;
|
||
break;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 播放放下动画(未吸附时)
|
||
/// </summary>
|
||
private void PlayDropAnimation()
|
||
{
|
||
// 可以在这里实现弹回动画或其他视觉反馈
|
||
transform.localScale = Vector3.one;
|
||
}
|
||
|
||
public void UpdateDrag()
|
||
{
|
||
// 检测右键点击进行旋转(仅在拖动中)
|
||
if (Input.GetMouseButtonDown(1)) // 1 = 右键
|
||
{
|
||
_blockShape.RotateClockwise();
|
||
|
||
// 旋转后立即重置位置,避免 Lerp 造成的延迟感
|
||
var snappedPos = GetMouseWorldPosition() - _blockShape.GetCenterPosition();
|
||
transform.position = snappedPos;
|
||
}
|
||
|
||
// 更新拖拽位置(带轻微滞后感)
|
||
var targetPos = GetMouseWorldPosition() - _blockShape.GetCenterPosition();
|
||
transform.position = Vector3.Lerp(transform.position, targetPos, Time.deltaTime * dragFollowSpeed);
|
||
|
||
// 创建事件参数
|
||
var args = new DragUpdateEventArgs
|
||
{
|
||
Shape = _blockShape,
|
||
CurrentPosition = transform.position
|
||
};
|
||
|
||
// 触发详细事件 - 由订阅者处理 Snap 检查和预览显示
|
||
OnDragUpdateDetailed?.Invoke(this, args);
|
||
|
||
// 触发兼容事件
|
||
OnDragUpdate?.Invoke(_blockShape);
|
||
}
|
||
|
||
private void HandleStateChange(DragState newState)
|
||
{
|
||
var sortingGroup = _blockShape?.SortingGroup;
|
||
if (sortingGroup == null) return;
|
||
|
||
sortingGroup.sortingLayerID = sortingLayerId;
|
||
switch (newState)
|
||
{
|
||
case DragState.Idle:
|
||
case DragState.Dragging:
|
||
sortingGroup.sortingOrder = dragLayerOrder;
|
||
break;
|
||
case DragState.Placed:
|
||
sortingGroup.sortingOrder = IdleLayerOrder;
|
||
break;
|
||
}
|
||
}
|
||
|
||
private Vector3 GetMouseWorldPosition()
|
||
{
|
||
var mouseWorldPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
|
||
return new Vector3(mouseWorldPos.x, mouseWorldPos.y, 0);
|
||
}
|
||
|
||
private void StartLongPressAnimation()
|
||
{
|
||
_originalScale = transform.localScale;
|
||
_originalPosition = transform.localPosition;
|
||
|
||
// 开始抖动时提升层级
|
||
var sortingGroup = _blockShape?.SortingGroup;
|
||
if (sortingGroup != null)
|
||
{
|
||
sortingGroup.sortingLayerID = sortingLayerId;
|
||
sortingGroup.sortingOrder = dragLayerOrder;
|
||
}
|
||
|
||
// 杀掉之前的动画
|
||
_longPressSequence?.Kill();
|
||
|
||
// 创建 DOTween 序列
|
||
_longPressSequence = DOTween.Sequence();
|
||
|
||
// 放大效果(使用 EaseOutQuad)
|
||
_longPressSequence.Append(
|
||
transform.DOScale(_originalScale * longPressScale, longPressDuration)
|
||
.SetEase(Ease.OutQuad)
|
||
);
|
||
|
||
// 抖动效果(使用 DOShakePosition,与放大同时进行)
|
||
_longPressSequence.Join(
|
||
transform.DOShakePosition(
|
||
longPressDuration-0.35f,
|
||
new Vector3(shakeIntensity, shakeIntensity, 0), // 只在 X 轴抖动
|
||
60, // 振动频率
|
||
90, // 随机性
|
||
false, // 不淡出
|
||
true // 使用局部坐标
|
||
)
|
||
.SetEase(Ease.OutCubic)
|
||
);
|
||
|
||
// 完成时的回调
|
||
_longPressSequence.OnComplete(() =>
|
||
{
|
||
_isLongPressTriggered = true;
|
||
StartDrag();
|
||
});
|
||
|
||
// 设置为不自动销毁
|
||
_longPressSequence.SetAutoKill(true);
|
||
}
|
||
|
||
private void CancelLongPress()
|
||
{
|
||
// 杀掉 DOTween 动画
|
||
if (_longPressSequence != null && _longPressSequence.IsActive())
|
||
{
|
||
_longPressSequence.Kill();
|
||
_longPressSequence = null;
|
||
}
|
||
|
||
// 恢复原始状态
|
||
if (_isPressing && !_isLongPressTriggered)
|
||
{
|
||
transform.DOKill(); // 确保所有动画都停止
|
||
transform.localScale = _originalScale;
|
||
transform.localPosition = _originalPosition;
|
||
|
||
// 抖动但没拿起来,降回原层级
|
||
var sortingGroup = _blockShape?.SortingGroup;
|
||
if (sortingGroup != null)
|
||
{
|
||
sortingGroup.sortingOrder = IdleLayerOrder;
|
||
}
|
||
}
|
||
|
||
_isPressing = false;
|
||
_isLongPressTriggered = false;
|
||
_canEndDragOnClick = false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 恢复点击反馈状态(缩放与层级),供外部在切换选中或切换模式时调用
|
||
/// </summary>
|
||
public void ResetClickFeedback()
|
||
{
|
||
transform.DOKill();
|
||
transform.localScale = Vector3.one;
|
||
var sortingGroup = _blockShape?.SortingGroup;
|
||
if (sortingGroup != null)
|
||
{
|
||
sortingGroup.sortingOrder = IdleLayerOrder;
|
||
}
|
||
}
|
||
|
||
private void PlayClickFeedback()
|
||
{
|
||
transform.DOScale(1.05f, 0.15f).SetEase(Ease.OutQuad);
|
||
var sortingGroup = _blockShape?.SortingGroup;
|
||
if (sortingGroup != null)
|
||
{
|
||
sortingGroup.sortingOrder = pressLayerOrder;
|
||
}
|
||
}
|
||
|
||
// 实现IInteraction接口
|
||
public bool IsActive => true;
|
||
public bool IsAvailable => true;
|
||
public GameObject GetGameObject() => gameObject;
|
||
}
|
||
}
|