Files
aibis-dream/Assets/Scripts/MiniGame/BlockPuzzle/Class/BlockShapeDragger.cs
T
2025-11-27 18:14:24 +08:00

406 lines
15 KiB
C#

using System;
using AibisDream.Framework;
using UnityEngine;
namespace AibisDream
{
/// <summary>
/// 形状拖拽组件
/// 实现IBlockShapeDragger接口,处理形状的拖拽、吸附和放置逻辑
/// </summary>
[RequireComponent(typeof(BlockShape))]
[RequireComponent(typeof(EventTriggerEx))]
public class BlockShapeDragger : MonoBehaviour, IBlockShapeDragger
{
[Header("拖拽设置")]
[SerializeField] private bool isDraggable = true;
[SerializeField] private float snapDistance = 0.5f;
[SerializeField] private BlockPuzzleGrid targetGridComponent; // Unity组件引用
[Header("视觉反馈")]
[SerializeField] private Color snapColor = new Color(0f, 1f, 0f, 0.7f);
[SerializeField] private Color invalidColor = new Color(1f, 0f, 0f, 0.7f);
[SerializeField] private Color normalColor = Color.white;
[SerializeField] private float dragScale = 1.1f;
// 拖拽状态
private DragState _currentState = DragState.Idle;
private bool _isDragging = false;
private Vector3 _dragOffset;
private Vector3 _originalPosition;
private Vector3 _originalScale;
private int _originalSortingOrder;
private bool _isSnapping = false;
private int _snapGridX;
private int _snapGridY;
// 组件引用
private BlockShape _blockShape;
private SpriteRenderer _spriteRenderer;
private Collider2D _collider2D;
// 事件
public event Action<IBlockShape> OnDragStart;
public event Action<IBlockShape, Vector3> OnDragUpdate;
public event Action<IBlockShape, bool> OnDragEnd;
public event Action<IBlockShape, Vector3, int, int> OnSnap;
public event Action<IBlockShape> OnUnsnap;
// 接口实现
public DragState CurrentState => _currentState;
public bool IsDraggable { get => isDraggable; set => isDraggable = value; }
public IBlockPuzzleGrid TargetGrid
{
get => targetGridComponent;
set => targetGridComponent = value as BlockPuzzleGrid;
}
public float SnapDistance { get => snapDistance; set => snapDistance = value; }
// 内部使用的网格引用
private IBlockPuzzleGrid targetGrid => targetGridComponent;
private void Awake()
{
_blockShape = GetComponent<BlockShape>();
_spriteRenderer = GetComponent<SpriteRenderer>();
}
public void StartDrag()
{
if (!isDraggable || _isDragging) return;
_isDragging = true;
_currentState = DragState.Dragging;
_originalPosition = transform.position;
_originalScale = transform.localScale;
// 计算拖拽偏移量
Vector3 mouseWorldPos = GetMouseWorldPosition();
_dragOffset = transform.position - mouseWorldPos;
// 提升层级和缩放
if (_spriteRenderer != null)
{
_originalSortingOrder = _spriteRenderer.sortingOrder;
_spriteRenderer.sortingOrder = 100;
}
transform.localScale = _originalScale * dragScale;
OnDragStart?.Invoke(_blockShape);
}
public void UpdateDrag()
{
if (!_isDragging) return;
Vector3 mouseWorldPos = GetMouseWorldPosition();
Vector3 targetPosition = mouseWorldPos + _dragOffset;
// 检测是否靠近网格
if (targetGrid != null)
{
bool canSnap = CheckSnapToGrid(targetPosition, out Vector3 snapPos, out int gridX, out int gridY);
if (canSnap)
{
// 吸附到网格
transform.position = snapPos;
_isSnapping = true;
_snapGridX = gridX;
_snapGridY = gridY;
_currentState = DragState.Snapping;
// 重要:更新拖拽偏移量,使鼠标继续移动时能正确计算位置
_dragOffset = snapPos - mouseWorldPos;
// 检查是否可以放置
bool canPlace = CanPlaceAt(gridX, gridY);
ShowSnapPreview(true, canPlace);
OnSnap?.Invoke(_blockShape, snapPos, gridX, gridY);
}
else
{
// 自由拖拽
transform.position = targetPosition;
if (_isSnapping)
{
_isSnapping = false;
ShowSnapPreview(false, false);
OnUnsnap?.Invoke(_blockShape);
}
_currentState = DragState.Dragging;
}
}
else
{
// 没有网格,自由拖拽
transform.position = targetPosition;
_currentState = DragState.Dragging;
}
OnDragUpdate?.Invoke(_blockShape, transform.position);
}
public void EndDrag()
{
if (!_isDragging) return;
bool placed = false;
if (_isSnapping && targetGrid != null)
{
// 尝试放置到网格
if (CanPlaceAt(_snapGridX, _snapGridY))
{
if (TryPlaceOnGrid(_snapGridX, _snapGridY))
{
placed = true;
_currentState = DragState.Placed;
OnShapePlaced();
}
}
}
if (!placed)
{
// 返回原位置
transform.position = _originalPosition;
_currentState = DragState.Idle;
}
// 恢复原始状态
transform.localScale = _originalScale;
if (_spriteRenderer != null)
{
_spriteRenderer.sortingOrder = _originalSortingOrder;
}
ShowSnapPreview(false, false);
_isDragging = false;
_isSnapping = false;
OnDragEnd?.Invoke(_blockShape, placed);
}
public void CancelDrag()
{
if (!_isDragging) return;
transform.position = _originalPosition;
transform.localScale = _originalScale;
if (_spriteRenderer != null)
{
_spriteRenderer.sortingOrder = _originalSortingOrder;
}
ShowSnapPreview(false, false);
_isDragging = false;
_isSnapping = false;
_currentState = DragState.Idle;
OnDragEnd?.Invoke(_blockShape, false);
}
public bool CanPlaceAt(int gridX, int gridY)
{
if (targetGrid == null || _blockShape == null) return false;
Vector2Int[] blockPositions = _blockShape.GetBlockPositions();
if (blockPositions == null || blockPositions.Length == 0) return false;
// 形状的坐标系统:
// - GetBlockPositions() 返回的坐标中,(0, 0) 是形状的左上角
// - gridX, gridY 是形状左下角在网格中的位置
// - 需要将形状的相对坐标转换为网格坐标
foreach (var blockPos in blockPositions)
{
// 将形状的相对坐标转换为网格坐标(左下角为基准)
// 形状的 (0, 0) 对应网格的 (gridX, gridY + shape.Height - 1)
// 形状的 (i, j) 对应网格的 (gridX + i, gridY + shape.Height - 1 - j)
int checkX = gridX + blockPos.x;
int checkY = gridY + _blockShape.Height - 1 - blockPos.y;
if (!targetGrid.IsValidPosition(checkX, checkY) ||
!targetGrid.IsCellEmpty(checkX, checkY))
{
return false;
}
}
return true;
}
public bool GetSnapGridPosition(out int gridX, out int gridY)
{
if (_isSnapping)
{
gridX = _snapGridX;
gridY = _snapGridY;
return true;
}
gridX = 0;
gridY = 0;
return false;
}
/// <summary>
/// 检查是否可以吸附到网格
/// </summary>
private bool CheckSnapToGrid(Vector3 worldPos, out Vector3 snapPos, out int gridX, out int gridY)
{
snapPos = worldPos;
gridX = 0;
gridY = 0;
if (targetGrid == null) return false;
// 将世界坐标转换为网格坐标
// 注意:WorldToGrid 需要世界坐标在网格范围内才能返回true
// 但我们需要检查所有附近的网格单元格,不仅仅是当前所在的单元格
// 所以先尝试直接转换,如果失败则查找最近的网格单元格
int tempGridX, tempGridY;
bool inGrid = targetGrid.WorldToGrid(worldPos, out tempGridX, out tempGridY);
if (!inGrid)
{
// 如果不在网格内,尝试查找最近的网格单元格
// 计算最近的网格坐标
Vector3 localPos = worldPos - targetGrid.Origin;
tempGridX = Mathf.FloorToInt(localPos.x / targetGrid.CellSize);
tempGridY = Mathf.FloorToInt(localPos.y / targetGrid.CellSize);
// 检查是否在有效范围内
if (!targetGrid.IsValidPosition(tempGridX, tempGridY))
{
return false;
}
}
// 获取网格单元格中心的世界坐标
Vector3 gridCenter = targetGrid.GridToWorld(tempGridX, tempGridY);
// 计算距离
float distance = Vector3.Distance(worldPos, gridCenter);
if (distance <= snapDistance)
{
// 计算形状应该放置的网格位置
// 形状左下角对齐到网格单元格左下角
gridX = tempGridX;
gridY = tempGridY;
// 计算形状左下角的世界坐标
// 网格单元格左下角的世界坐标 = Origin + (gridX * CellSize, gridY * CellSize)
Vector3 gridBottomLeft = targetGrid.Origin + new Vector3(
gridX * targetGrid.CellSize,
gridY * targetGrid.CellSize,
0
);
// 计算形状中心相对于形状左下角的偏移
// 形状的 pattern 中,(0, 0) 是左上角,所以形状左下角在 pattern 中的相对位置是 (0, height-1)
// 在形状的本地坐标系中(假设形状中心在 (0, 0)),形状左下角的位置是:
// localX = -width * cellSize / 2
// localY = -height * cellSize / 2 + (height-1) * cellSize = (height-1) * cellSize - height * cellSize / 2
// 形状中心相对于形状左下角的偏移 = (0, 0) - (localX, localY) = (width * cellSize / 2, height * cellSize / 2 - (height-1) * cellSize)
// 为了简化,我们假设形状的中心在形状的几何中心
// 形状中心相对于形状左下角的偏移 = (width * cellSize / 2, height * cellSize / 2)
// 但考虑到形状的 pattern 中 (0, 0) 是左上角,形状左下角在 pattern 中的位置是 (0, height-1)
// 所以形状中心相对于形状左下角的偏移 = (width * cellSize / 2, (height-1) * cellSize / 2)
// 实际上,我们需要根据形状的实际布局计算形状中心的位置
// 但为了简化,我们直接使用网格单元格左下角作为形状左下角的位置
// 然后根据形状的实际布局计算形状中心的位置
// 形状左下角对齐到网格单元格左下角
// 形状中心的世界坐标 = 网格单元格左下角的世界坐标 + 形状中心相对于形状左下角的偏移
// 使用网格的 CellSize,因为形状的 cellSize 应该与网格的 CellSize 相同
float shapeCenterOffsetX = _blockShape.Width * targetGrid.CellSize / 2f;
float shapeCenterOffsetY = (_blockShape.Height - 1) * targetGrid.CellSize / 2f;
snapPos = gridBottomLeft + new Vector3(shapeCenterOffsetX, shapeCenterOffsetY, 0);
return true;
}
return false;
}
/// <summary>
/// 尝试放置到网格
/// </summary>
/// <param name="gridX">网格X坐标(形状左下角位置)</param>
/// <param name="gridY">网格Y坐标(形状左下角位置)</param>
private bool TryPlaceOnGrid(int gridX, int gridY)
{
if (!CanPlaceAt(gridX, gridY)) return false;
Vector2Int[] blockPositions = _blockShape.GetBlockPositions();
if (blockPositions == null) return false;
// 形状的坐标系统:
// - GetBlockPositions() 返回的坐标中,(0, 0) 是形状的左上角
// - gridX, gridY 是形状左下角在网格中的位置
// - 需要将形状的相对坐标转换为网格坐标
foreach (var blockPos in blockPositions)
{
// 将形状的相对坐标转换为网格坐标(左下角为基准)
// 形状的 (0, 0) 对应网格的 (gridX, gridY + shape.Height - 1)
// 形状的 (i, j) 对应网格的 (gridX + i, gridY + shape.Height - 1 - j)
int x = gridX + blockPos.x;
int y = gridY + _blockShape.Height - 1 - blockPos.y;
targetGrid.SetCellState(x, y, GridCellState.Occupied);
}
return true;
}
/// <summary>
/// 显示吸附预览效果
/// </summary>
private void ShowSnapPreview(bool show, bool isValid)
{
Color previewColor = show ? (isValid ? snapColor : invalidColor) : normalColor;
// 更新主SpriteRenderer
if (_spriteRenderer != null)
{
_spriteRenderer.color = previewColor;
}
}
/// <summary>
/// 形状放置完成
/// </summary>
private void OnShapePlaced()
{
// 可以在这里添加放置后的逻辑
// 例如:禁用拖拽、播放动画等
isDraggable = false;
}
/// <summary>
/// 获取鼠标世界坐标
/// </summary>
private Vector3 GetMouseWorldPosition()
{
return CameraKit.GetMouseWorldPos(Input.mousePosition);
}
private void OnDisable()
{
if (_isDragging)
{
CancelDrag();
}
}
}
}