406 lines
15 KiB
C#
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();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|