diff --git a/Assets/Editor/Inspector/ShapeDraggerEditor.cs b/Assets/Editor/Inspector/ShapeDraggerEditor.cs index 2a0b69f0c..fe4dc493c 100644 --- a/Assets/Editor/Inspector/ShapeDraggerEditor.cs +++ b/Assets/Editor/Inspector/ShapeDraggerEditor.cs @@ -6,10 +6,10 @@ namespace AibisDream.SystemEditor [CustomEditor(typeof(ShapeDragger))] public class ShapeDraggerEditor : Editor { - private SerializedProperty isDraggableProperty; + private SerializedProperty isInteractableProperty; + private SerializedProperty interactionModeProperty; private SerializedProperty sortingLayerNameProperty; private SerializedProperty dragLayerOrderProperty; - private SerializedProperty idleLayerOrderProperty; private SerializedProperty pressLayerOrderProperty; private SerializedProperty longPressDurationProperty; @@ -20,10 +20,10 @@ namespace AibisDream.SystemEditor private void OnEnable() { - isDraggableProperty = serializedObject.FindProperty("isDraggable"); + isInteractableProperty = serializedObject.FindProperty("isInteractable"); + interactionModeProperty = serializedObject.FindProperty("interactionMode"); sortingLayerNameProperty = serializedObject.FindProperty("sortingLayerId"); dragLayerOrderProperty = serializedObject.FindProperty("dragLayerOrder"); - idleLayerOrderProperty = serializedObject.FindProperty("idleLayerOrder"); pressLayerOrderProperty = serializedObject.FindProperty("pressLayerOrder"); longPressDurationProperty = serializedObject.FindProperty("longPressDuration"); @@ -39,7 +39,8 @@ namespace AibisDream.SystemEditor EditorGUILayout.LabelField("拖拽设置", EditorStyles.boldLabel); - EditorGUILayout.PropertyField(isDraggableProperty, new GUIContent("是否可拖拽")); + EditorGUILayout.PropertyField(isInteractableProperty, new GUIContent("是否可交互")); + EditorGUILayout.PropertyField(interactionModeProperty, new GUIContent("交互模式")); EditorGUILayout.Space(); EditorGUILayout.LabelField("排序层设置", EditorStyles.boldLabel); @@ -48,8 +49,8 @@ namespace AibisDream.SystemEditor EditorKit.SortingLayerField(new GUIContent("Sorting Layer"), sortingLayerNameProperty); // 使用 SerializedProperty 绘制 SortingOrder EditorGUILayout.PropertyField(dragLayerOrderProperty, new GUIContent("拖拽时 Order")); - EditorGUILayout.PropertyField(idleLayerOrderProperty, new GUIContent("闲置时 Order")); - EditorGUILayout.PropertyField(pressLayerOrderProperty, new GUIContent("长按时 Order")); + EditorGUILayout.PropertyField(pressLayerOrderProperty, new GUIContent("点击高亮时 Order")); + EditorGUILayout.HelpBox("闲置时 Order 取自 Visual 子物体的 SortingGroup,请在 BlockShape 的 Visual Prefab 中配置。", MessageType.Info); EditorGUILayout.Space(); EditorGUILayout.LabelField("长按设置", EditorStyles.boldLabel); diff --git a/Assets/Editor/MenuItem/BlockPuzzleMenuItemCreator.cs b/Assets/Editor/MenuItem/BlockPuzzleMenuItemCreator.cs index 75a8570d0..b7372eeea 100644 --- a/Assets/Editor/MenuItem/BlockPuzzleMenuItemCreator.cs +++ b/Assets/Editor/MenuItem/BlockPuzzleMenuItemCreator.cs @@ -1,4 +1,4 @@ -using UnityEditor; +using UnityEditor; using UnityEngine; namespace AibisDream.SystemEditor @@ -19,7 +19,7 @@ namespace AibisDream.SystemEditor } var instantiatedPrefab = (GameObject)PrefabUtility.InstantiatePrefab(prefabAsset); - PrefabUtility.UnpackPrefabInstance(instantiatedPrefab, PrefabUnpackMode.Completely, InteractionMode.UserAction); + PrefabUtility.UnpackPrefabInstance(instantiatedPrefab, PrefabUnpackMode.Completely, UnityEditor.InteractionMode.UserAction); return instantiatedPrefab.GetComponent(); } } diff --git a/Assets/Prefabs/BlockPuzzle/BlockShape.prefab b/Assets/Prefabs/BlockPuzzle/BlockShape.prefab index 2ba770b43..8bab739b3 100644 --- a/Assets/Prefabs/BlockPuzzle/BlockShape.prefab +++ b/Assets/Prefabs/BlockPuzzle/BlockShape.prefab @@ -174,7 +174,8 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 62aeab6b39ab40949ab3e66462c5e295, type: 3} m_Name: m_EditorClassIdentifier: - isDraggable: 1 + isInteractable: 1 + interactionMode: 0 sortingLayerId: 1134468525 dragLayerOrder: 25 idleLayerOrder: 11 @@ -182,3 +183,4 @@ MonoBehaviour: longPressDuration: 0.65 longPressScale: 1.1 shakeIntensity: 0.1 + dragFollowSpeed: 15 diff --git a/Assets/Scripts/MiniGame/BlockPuzzle/Class/BlockPuzzleKit.cs b/Assets/Scripts/MiniGame/BlockPuzzle/Class/BlockPuzzleKit.cs index 33cb717ee..72bcb426d 100644 --- a/Assets/Scripts/MiniGame/BlockPuzzle/Class/BlockPuzzleKit.cs +++ b/Assets/Scripts/MiniGame/BlockPuzzle/Class/BlockPuzzleKit.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using Newtonsoft.Json; @@ -590,7 +590,7 @@ namespace AibisDream if (BlockPuzzleKit.TryLoadBlockPuzzleData(puzzleName, out var blockPuzzleData)) { BlockPuzzleSystem.Instance.Initialize(blockPuzzleData); - BlockPuzzleSystem.Instance.SetLocked(true); + // BlockPuzzleSystem.Instance.SetLocked(true); } else { @@ -616,6 +616,12 @@ namespace AibisDream BlockPuzzleSystem.Instance.SetLocked(false); } + [YarnCommand("enable_drag")] + public static void EnableDrag() + { + BlockPuzzleSystem.Instance.EnableDrag(); + } + [YarnCommand("clear_block_puzzle")] public static void ClearBlockPuzzle() { @@ -643,7 +649,6 @@ namespace AibisDream public string shapeKey; public Vector2Int rootCell; public RotationAngle rotationAngle; - public bool isDraggable; } [Serializable] diff --git a/Assets/Scripts/MiniGame/BlockPuzzle/Class/BlockPuzzleSystem.cs b/Assets/Scripts/MiniGame/BlockPuzzle/Class/BlockPuzzleSystem.cs index 94be93a96..3a5097648 100644 --- a/Assets/Scripts/MiniGame/BlockPuzzle/Class/BlockPuzzleSystem.cs +++ b/Assets/Scripts/MiniGame/BlockPuzzle/Class/BlockPuzzleSystem.cs @@ -1,4 +1,4 @@ -using UnityEngine; +using UnityEngine; using Cinemachine; using AibisDream.Kit; using AibisDream.Utility; @@ -27,6 +27,9 @@ namespace AibisDream // 锁定状态 public bool IsLocked { get; private set; } + // 缓存当前的吸附结果(由DragUpdate更新) + private SnapResult _currentSnapResult; + private void Start() { if (initializeOnStart) @@ -70,7 +73,7 @@ namespace AibisDream var shape = shapeFactory.CreateBlockShape(blockShapeData); shape.Rotate(shapeData.rotationAngle); grid.TrySnapToGrid(shape, shapeData.rootCell); - shape.ShapeDragger.IsDraggable = shapeData.isDraggable; + shape.ShapeDragger.InteractionMode = InteractionMode.Click; } } grid.GridVisualizer.UpdateAllCellsVisual(); @@ -142,7 +145,7 @@ namespace AibisDream // 更新所有形状的拖拽状态 if (shapeFactory != null) { - shapeFactory.SetAllShapesDraggable(!IsLocked); + shapeFactory.SetAllShapesInteractable(!IsLocked); } // 更新按钮的交互状态 @@ -151,7 +154,7 @@ namespace AibisDream var panel = blockPuzzlePanel.Panel; if (panel.LockButton != null) { - panel.LockButton.interactable = !IsLocked; + panel.LockButton.GetComponent().SetLockedState(IsLocked); } if (panel.PopUpButton != null) { @@ -160,12 +163,15 @@ namespace AibisDream } } + public void EnableDrag() + { + shapeFactory.SetAllShapesInteractionMode(InteractionMode.Drag); + } + public void PlayShapeAnimation(string triggerName) { // 播放Timeline TimelineCenter.Instance.PlayTimeline("维修面板"); - - shapeFactory.PlayShapeAnimation(triggerName); } public void LoadShapeRack() @@ -187,10 +193,11 @@ namespace AibisDream blockPuzzlePanel.Panel.OnSubmit.AddListener(SubmitGameResult); blockPuzzlePanel.Panel.OnPopUp.AddListener(PopUpShape); - // 由Shape触发 - shapeFactory.OnDragStart += OnShapeDragStart; - shapeFactory.OnDragEnd += OnShapeDragEnd; - shapeFactory.OnDragUpdate += OnShapeDragUpdate; + // 由Shape触发 - 使用新的事件系统 + shapeFactory.OnDragStartDetailed += OnShapeDragStartDetailed; + shapeFactory.OnDragEndDetailed += OnShapeDragEndDetailed; + shapeFactory.OnDragUpdateDetailed += OnShapeDragUpdateDetailed; + shapeFactory.OnClick += OnShapeClick; // 由系统触发 validator.OnValidate.AddListener(blockPuzzlePanel.Panel.SetVaildState); @@ -204,14 +211,34 @@ namespace AibisDream blockPuzzlePanel.ShowShapeInScreen(shapeData); } - public void OnShapeDragStart(IBlockShape blockShape) + // 旧的事件处理方法已迁移到新的详细事件处理器 + // OnShapeDragStart -> OnShapeDragStartDetailed + // OnShapeDragEnd -> OnShapeDragEndDetailed + // OnShapeDragUpdate -> OnShapeDragUpdateDetailed + + public void OnShapeClick(BlockShape blockShape) { + // 点击形状 + blockPuzzlePanel.ShowShapeInScreen(blockShape.BlockShapeData.blockShapeInfo); + } + + #region 新的事件驱动处理器 + + private void OnShapeDragStartDetailed(object sender, DragStartEventArgs args) + { + var blockShape = args.Shape; + + // 如果原本就在网格中,则先移除 + grid.TryRemoveShapeFromGrid(blockShape); + grid.ResetPreviewGrid(); + + // 显示网格 grid.GridVisualizer.SetGridVisible(true); // 显示Info - if (blockShape is BlockShape shape && shape.BlockShapeData.blockShapeInfo.shapeName != null) + if (blockShape?.BlockShapeData.blockShapeInfo.shapeName != null) { - blockPuzzlePanel.ShowShapeInScreen(shape.BlockShapeData.blockShapeInfo); + blockPuzzlePanel.ShowShapeInScreen(blockShape.BlockShapeData.blockShapeInfo); EventSystem.current.SetSelectedGameObject(null); selectedShapeName = null; } @@ -224,22 +251,125 @@ namespace AibisDream validator.Validate(grid.GetShapes()); } - public void OnShapeDragEnd(IBlockShape blockShape, bool isPlaced) + private void OnShapeDragUpdateDetailed(object sender, DragUpdateEventArgs args) { - grid.GridVisualizer.SetGridVisible(false); + var blockShape = args.Shape; - // 如果放置成功,则预先检查一次 - if (isPlaced) - { - validator.Validate(grid.GetShapes()); - } - } + // 进行Snap检查并缓存结果 + _currentSnapResult = grid.CheckSnapToGrid(blockShape); + args.CurrentSnapResult = _currentSnapResult; + + // 在网格显示预览 + grid.ShowSnapPreview(_currentSnapResult); - public void OnShapeDragUpdate(IBlockShape blockShape) - { // 更新回收槽状态 shapeRecycleSlot.UpdateSlotState(blockShape); } + + private void OnShapeDragEndDetailed(object sender, DragEndEventArgs args) + { + var blockShape = args.Shape; + + // 隐藏网格 + grid.GridVisualizer.SetGridVisible(false); + + // 1. 首先尝试回收 + if (TryHandleRecycle(args)) + { + return; + } + + // 2. 然后尝试吸附到网格 + if (TryHandleGridSnap(args)) + { + return; + } + + // 3. 都未处理,保持未处理状态 + args.IsHandled = false; + args.ResultType = DragEndResultType.Dropped; + + // 重置预览网格 + grid.ResetPreviewGrid(); + grid.GridVisualizer.UpdateAllCellsVisual(); + } + + /// + /// 处理回收逻辑 + /// + private bool TryHandleRecycle(DragEndEventArgs args) + { + if (shapeRecycleSlot.CurrentState != PositionState.Open) + { + return false; + } + + // 执行回收 + shapeRecycleSlot.RecycleShape(args.Shape); + + // 清空屏幕并切换回LogScreen + blockPuzzlePanel.ClearShapeInScreen(); + blockPuzzlePanel.SwitchToLogScreen(); + + // 向面板添加回收的形状 + blockPuzzlePanel.Panel.OnDeviceRecycle(args.Shape); + + // 更新事件参数 + args.IsHandled = true; + args.FinalState = DragState.Idle; + args.ResultType = DragEndResultType.Recycled; + + // 延时后进行销毁 + ActionKit.Delay(1.0f, () => { + shapeFactory.RecycleBlockShape(args.Shape); + }).Start(this); + + return true; + } + + /// + /// 处理网格吸附逻辑 + /// + private bool TryHandleGridSnap(DragEndEventArgs args) + { + // 使用缓存的snap结果或重新检测 + var snapResult = _currentSnapResult; + if (!snapResult.canSnap) + { + snapResult = grid.CheckSnapToGrid(args.Shape); + } + + if (!snapResult.canSnap) + { + return false; + } + + // 执行网格吸附 + grid.ResetPreviewGrid(); + + // 设置形状位置和状态 + args.Shape.transform.position = grid.GridToWorld(snapResult.RootCell.x, snapResult.RootCell.y); + args.Shape.transform.localScale = Vector3.one; + + // 更新网格状态 + grid.TrySnapToGrid(args.Shape, snapResult.RootCell); + + // 更新事件参数 + args.IsHandled = true; + args.FinalState = DragState.Placed; + args.ResultType = DragEndResultType.SnappedToGrid; + + // 验证放置 + validator.Validate(grid.GetShapes()); + + // 处理Cursor + var eventTrigger = args.Shape.GetComponent(); + eventTrigger?.OnPointerUp(null); + + return true; + } + + #endregion public void PopUpShape() { @@ -264,28 +394,6 @@ namespace AibisDream selectedShapeName = null; } - // --------------------- 吸附校验 --------------------- - - public bool TrySnapToRecycleSlot(BlockShape blockShape) - { - // 先判断是否吸附到回收槽 - if (shapeRecycleSlot.CurrentState == PositionState.Open) - { - shapeRecycleSlot.RecycleShape(blockShape); - // 清空屏幕并切换回LogScreen - blockPuzzlePanel.ClearShapeInScreen(); - blockPuzzlePanel.SwitchToLogScreen(); - // 向面板添加回收的形状 - blockPuzzlePanel.Panel.OnDeviceRecycle(blockShape); - // 延时后进行销毁 - ActionKit.Delay(1.0f, () => { - shapeFactory.RecycleBlockShape(blockShape); - }).Start(this); - return true; - } - return false; - } - // ---------------- 编辑器状态逻辑 ---------------- /// diff --git a/Assets/Scripts/MiniGame/BlockPuzzle/Class/BlockShape.cs b/Assets/Scripts/MiniGame/BlockPuzzle/Class/BlockShape.cs index 014fda1d1..6dccdb47c 100644 --- a/Assets/Scripts/MiniGame/BlockPuzzle/Class/BlockShape.cs +++ b/Assets/Scripts/MiniGame/BlockPuzzle/Class/BlockShape.cs @@ -58,6 +58,7 @@ namespace AibisDream _blockShapeData = blockShapeData; gameObject.name = blockShapeData.shapeName; LoadVisualPrefab(); + ShapeDragger.IdleLayerOrder = _sortingGroup != null ? _sortingGroup.sortingOrder : 0; // 更新碰撞体 UpdatePolygonCollider(); } @@ -263,15 +264,6 @@ namespace AibisDream UpdateVisualRotation(); } - public void PlayAnimation(string triggerName) - { - if (_visualInstance != null && _visualInstance.TryGetComponent(out Animator animator)) - { - animator.SetTrigger(triggerName); - // animator.ResetTrigger(triggerName); - } - } - /// /// 获取当前旋转后的宽度 /// @@ -437,6 +429,7 @@ namespace AibisDream } LoadVisualPrefab(); + ShapeDragger.IdleLayerOrder = _sortingGroup != null ? _sortingGroup.sortingOrder : 0; // 更新碰撞体形状 UpdatePolygonCollider(); } diff --git a/Assets/Scripts/MiniGame/BlockPuzzle/Class/PlacementValidator.cs b/Assets/Scripts/MiniGame/BlockPuzzle/Class/PlacementValidator.cs index 4c168fc2f..53ded36aa 100644 --- a/Assets/Scripts/MiniGame/BlockPuzzle/Class/PlacementValidator.cs +++ b/Assets/Scripts/MiniGame/BlockPuzzle/Class/PlacementValidator.cs @@ -1,4 +1,4 @@ -using UnityEngine; +using UnityEngine; namespace AibisDream { @@ -133,8 +133,7 @@ namespace AibisDream var gridShapeData = new GridShapeData { shapeKey = shape.ShapeName, rootCell = rootCell, - rotationAngle = shape.CurrentRotation, - isDraggable = shape.GetComponent().IsDraggable + rotationAngle = shape.CurrentRotation }; grid.ShapeDict.Add(shape, gridShapeData); } diff --git a/Assets/Scripts/MiniGame/BlockPuzzle/Class/ShapeDragger.cs b/Assets/Scripts/MiniGame/BlockPuzzle/Class/ShapeDragger.cs index d4eca3c66..74ded733d 100644 --- a/Assets/Scripts/MiniGame/BlockPuzzle/Class/ShapeDragger.cs +++ b/Assets/Scripts/MiniGame/BlockPuzzle/Class/ShapeDragger.cs @@ -1,4 +1,4 @@ -using System; +using System; using AibisDream.Framework; using DG.Tweening; using UnityEngine; @@ -6,15 +6,22 @@ using UnityEngine.EventSystems; namespace AibisDream { - public class ShapeDragger : MonoBehaviour, IBlockShapeDragger, IInteraction + public class ShapeDragger : MonoBehaviour, IInteraction { #region 编辑器字段 - [SerializeField] private bool isDraggable = true; + [SerializeField] private bool isInteractable = true; + + [Header("交互模式(由外界赋值控制)")] + [SerializeField] private InteractionMode interactionMode = InteractionMode.Drag; [SerializeField] private int sortingLayerId; [SerializeField] private int dragLayerOrder; - [SerializeField] private int idleLayerOrder; [SerializeField] private int pressLayerOrder; + + /// + /// 闲置时层级,由 BlockShape 在 LoadVisualPrefab 后从 Visual 的 SortingGroup 赋值 + /// + public int IdleLayerOrder { get; set; } [Header("长按设置")] [SerializeField] private float longPressDuration = 0.65f; @@ -38,7 +45,6 @@ namespace AibisDream _currentState = value; } } - private SnapResult _snapResult; // 长按相关状态 private bool _isPressing; @@ -52,14 +58,39 @@ namespace AibisDream private BlockShape _blockShape; private EventTriggerEx _eventTrigger; - public bool IsDraggable { get => isDraggable; set => isDraggable = value; } + 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; - // 事件 - public event Action OnDragStart; - public event Action OnDragUpdate; - public event Action OnDragEnd; + #region 事件定义 + + /// + /// 拖拽开始事件 - 携带详细信息 + /// + public event EventHandler OnDragStartDetailed; + + /// + /// 拖拽更新事件 - 携带详细信息 + /// + public event EventHandler OnDragUpdateDetailed; + + /// + /// 拖拽结束事件 - 携带详细信息,由订阅者处理具体逻辑 + /// + public event EventHandler OnDragEndDetailed; + + /// + /// 点击事件 + /// + public event Action OnClick; + + // 保留原有简单事件以保持向后兼容 + public event Action OnDragStart; + public event Action OnDragUpdate; + public event Action OnDragEnd; + + #endregion private void Awake() { @@ -77,6 +108,7 @@ namespace AibisDream // 清理 DOTween 动画 _longPressSequence?.Kill(); + transform.DOKill(); } private void RegisterEvent() @@ -101,20 +133,29 @@ namespace AibisDream return; } - if (!isDraggable) return; + if (!isInteractable) return; // 如果当前正在拖拽中,标记可以通过点击结束 if (CurrentState == DragState.Dragging) { _canEndDragOnClick = true; } - // 只有在静止状态才能开始长按 - else if (CurrentState == DragState.Idle || CurrentState == DragState.Placed) + // 只有在静止状态才能开始拖拽(点击模式不启动拖拽) + else if (interactionMode == InteractionMode.Drag) { - _isPressing = true; - _isLongPressTriggered = false; - _canEndDragOnClick = false; // 长按开始时不允许点击结束 - StartLongPressAnimation(); + if (CurrentState == DragState.Placed) + { + // Placed状态:需要长按才能拖拽(防止误触) + _isPressing = true; + _isLongPressTriggered = false; + _canEndDragOnClick = false; // 长按开始时不允许点击结束 + StartLongPressAnimation(); + } + else if (CurrentState == DragState.Idle) + { + // Idle状态:点击立即开始拖拽(快速取用) + StartDrag(); + } } } @@ -139,7 +180,15 @@ namespace AibisDream return; } - // 只有在拖拽中且允许点击结束时才结束拖拽 + if (interactionMode == InteractionMode.Click) + { + if (!isInteractable) return; + OnClick?.Invoke(_blockShape); + PlayClickFeedback(); + return; + } + + // 拖拽模式:只有在拖拽中且允许点击结束时才结束拖拽 if (CurrentState == DragState.Dragging && _canEndDragOnClick) { EndDrag(); @@ -149,14 +198,24 @@ namespace AibisDream public void StartDrag() { - if (!isDraggable) return; + if (!isInteractable) return; - // 如果原本就在网格中,则先移除 - TargetGrid?.TryRemoveShapeFromGrid(_blockShape); - TargetGrid?.ResetPreviewGrid(); + // 记录原始位置 + _originalPosition = transform.position; CurrentState = DragState.Dragging; + // 创建事件参数 + var args = new DragStartEventArgs + { + Shape = _blockShape, + StartPosition = transform.position + }; + + // 触发详细事件 + OnDragStartDetailed?.Invoke(this, args); + + // 触发兼容事件 OnDragStart?.Invoke(_blockShape); } @@ -164,38 +223,65 @@ namespace AibisDream { // 清理长按状态 CancelLongPress(); - - // 先吸附到回收槽 - var recycled = BlockPuzzleSystem.Instance.TrySnapToRecycleSlot(_blockShape); - if (recycled) + + // 创建事件参数,由订阅者处理具体逻辑 + var args = new DragEndEventArgs { - CurrentState = DragState.Idle; - _snapResult = SnapResult.RecycleResult; - TargetGrid.ShowSnapPreview(_snapResult); - OnDragEnd?.Invoke(_blockShape, CurrentState == DragState.Placed); - return; - } + Shape = _blockShape, + EndPosition = transform.position, + FinalState = DragState.Idle, // 默认状态 + IsHandled = false, + ResultType = DragEndResultType.None + }; - // 再吸附到网格 - TargetGrid?.ResetPreviewGrid(); - if (_snapResult.canSnap) + // 触发事件,让订阅者处理回收、吸附等逻辑 + OnDragEndDetailed?.Invoke(this, args); + + // 根据事件处理结果更新状态 + if (args.IsHandled) { - CurrentState = DragState.Placed; - transform.localScale = Vector3.one; + CurrentState = args.FinalState; - TargetGrid.TrySnapToGrid(_blockShape, _snapResult.RootCell); - - // 处理Cursor - _eventTrigger.OnPointerUp(null); - OnDragEnd?.Invoke(_blockShape, CurrentState == DragState.Placed); + // 应用视觉状态 + ApplyVisualState(args.FinalState); } else { - // 都没吸附到,恢复原状态和缩放 + // 未被处理,恢复原状 + CurrentState = DragState.Idle; transform.localScale = Vector3.one; + + // 可选:播放未吸附的动画(如弹回) + PlayDropAnimation(); } - TargetGrid.GridVisualizer.UpdateAllCellsVisual(); + // 触发兼容事件 + OnDragEnd?.Invoke(_blockShape, CurrentState == DragState.Placed); + } + + /// + /// 应用视觉状态 + /// + private void ApplyVisualState(DragState state) + { + switch (state) + { + case DragState.Placed: + transform.localScale = Vector3.one; + break; + case DragState.Idle: + transform.localScale = Vector3.one; + break; + } + } + + /// + /// 播放放下动画(未吸附时) + /// + private void PlayDropAnimation() + { + // 可以在这里实现弹回动画或其他视觉反馈 + transform.localScale = Vector3.one; } public void UpdateDrag() @@ -204,17 +290,27 @@ namespace AibisDream 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); - // 进行Snap检查 - _snapResult = TargetGrid.CheckSnapToGrid(_blockShape); - // 在网格显示预览 - TargetGrid.ShowSnapPreview(_snapResult); + // 创建事件参数 + var args = new DragUpdateEventArgs + { + Shape = _blockShape, + CurrentPosition = transform.position + }; + // 触发详细事件 - 由订阅者处理 Snap 检查和预览显示 + OnDragUpdateDetailed?.Invoke(this, args); + + // 触发兼容事件 OnDragUpdate?.Invoke(_blockShape); } @@ -231,7 +327,7 @@ namespace AibisDream sortingGroup.sortingOrder = dragLayerOrder; break; case DragState.Placed: - sortingGroup.sortingOrder = idleLayerOrder; + sortingGroup.sortingOrder = IdleLayerOrder; break; } } @@ -246,7 +342,15 @@ namespace AibisDream { _originalScale = transform.localScale; _originalPosition = transform.localPosition; - + + // 开始抖动时提升层级 + var sortingGroup = _blockShape?.SortingGroup; + if (sortingGroup != null) + { + sortingGroup.sortingLayerID = sortingLayerId; + sortingGroup.sortingOrder = dragLayerOrder; + } + // 杀掉之前的动画 _longPressSequence?.Kill(); @@ -290,20 +394,51 @@ namespace AibisDream _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; } + /// + /// 恢复点击反馈状态(缩放与层级),供外部在切换选中或切换模式时调用 + /// + 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; diff --git a/Assets/Scripts/MiniGame/BlockPuzzle/Class/ShapeFactory.cs b/Assets/Scripts/MiniGame/BlockPuzzle/Class/ShapeFactory.cs index 04c38c544..4d9e600a3 100644 --- a/Assets/Scripts/MiniGame/BlockPuzzle/Class/ShapeFactory.cs +++ b/Assets/Scripts/MiniGame/BlockPuzzle/Class/ShapeFactory.cs @@ -13,9 +13,16 @@ namespace AibisDream private List _blockShapeList = new List(); - public event Action OnDragStart; - public event Action OnDragEnd; - public event Action OnDragUpdate; + // 新的事件定义 + public event EventHandler OnDragStartDetailed; + public event EventHandler OnDragEndDetailed; + public event EventHandler OnDragUpdateDetailed; + public event Action OnClick; + + // 保留原有事件以保持兼容 + public event Action OnDragStart; + public event Action OnDragEnd; + public event Action OnDragUpdate; public ShapeFactory(Transform shapeRoot) { @@ -32,6 +39,14 @@ namespace AibisDream // 注册事件 var dragger = blockShape.GetComponent(); + + // 注册新的事件(详细事件) + dragger.OnDragStartDetailed += OnShapeDragStartDetailed; + dragger.OnDragEndDetailed += OnShapeDragEndDetailed; + dragger.OnDragUpdateDetailed += OnShapeDragUpdateDetailed; + dragger.OnClick += OnShapeClick; + + // 注册旧的事件(兼容事件) dragger.OnDragStart += OnShapeDragStart; dragger.OnDragEnd += OnShapeDragEnd; dragger.OnDragUpdate += OnShapeDragUpdate; @@ -39,22 +54,68 @@ namespace AibisDream return blockShape; } - private void OnShapeDragStart(IBlockShape blockShape) + private void OnShapeDragStart(BlockShape blockShape) { blockShape.ShapeDragger.transform.SetParent(_shapeRoot); OnDragStart?.Invoke(blockShape); } - private void OnShapeDragEnd(IBlockShape blockShape, bool isPlaced) + private void OnShapeDragEnd(BlockShape blockShape, bool isPlaced) { OnDragEnd?.Invoke(blockShape, isPlaced); } - private void OnShapeDragUpdate(IBlockShape blockShape) + private void OnShapeDragUpdate(BlockShape blockShape) { OnDragUpdate?.Invoke(blockShape); } + private void OnShapeClick(BlockShape blockShape) + { + ResetAllShapesClickFeedback(); + OnClick?.Invoke(blockShape); + } + + #region 新的事件处理方法 + + private void OnShapeDragStartDetailed(object sender, DragStartEventArgs args) + { + args.Shape.ShapeDragger.transform.SetParent(_shapeRoot); + OnDragStartDetailed?.Invoke(this, args); + // 触发兼容事件 + OnDragStart?.Invoke(args.Shape); + } + + private void OnShapeDragEndDetailed(object sender, DragEndEventArgs args) + { + OnDragEndDetailed?.Invoke(this, args); + // 触发兼容事件 + OnDragEnd?.Invoke(args.Shape, args.FinalState == DragState.Placed); + } + + private void OnShapeDragUpdateDetailed(object sender, DragUpdateEventArgs args) + { + OnDragUpdateDetailed?.Invoke(this, args); + // 触发兼容事件 + OnDragUpdate?.Invoke(args.Shape); + } + + #endregion + + /// + /// 重置所有形状的点击反馈状态(缩放与层级) + /// + private void ResetAllShapesClickFeedback() + { + foreach (var shape in _blockShapeList) + { + if (shape != null && shape.ShapeDragger != null) + { + shape.ShapeDragger.ResetClickFeedback(); + } + } + } + public BlockShape CreateBlockShape(string key) { if (string.IsNullOrEmpty(key)) @@ -77,31 +138,32 @@ namespace AibisDream } /// - /// 批量设置所有形状的可拖拽状态 + /// 批量设置所有形状的可交互状态(false 表示锁定) /// - /// 是否可拖拽 - public void SetAllShapesDraggable(bool draggable) + /// 是否可交互 + public void SetAllShapesInteractable(bool interactable) { foreach (var shape in _blockShapeList) { if (shape != null && shape.ShapeDragger != null) { - shape.ShapeDragger.IsDraggable = draggable; + shape.ShapeDragger.IsInteractable = interactable; } } } /// - /// 播放形状动画 + /// 批量设置所有形状的交互模式 /// - /// 动画触发器名称 - public void PlayShapeAnimation(string triggerName) + /// 交互模式 + public void SetAllShapesInteractionMode(InteractionMode interactionMode) { + ResetAllShapesClickFeedback(); foreach (var shape in _blockShapeList) { - if (shape != null) + if (shape != null && shape.ShapeDragger != null) { - shape.PlayAnimation(triggerName); + shape.ShapeDragger.InteractionMode = interactionMode; } } } diff --git a/Assets/Scripts/MiniGame/BlockPuzzle/Class/UIHander/BlockSubmitHander.cs b/Assets/Scripts/MiniGame/BlockPuzzle/Class/UIHander/BlockSubmitHander.cs index 8250e306b..5e74e9e3f 100644 --- a/Assets/Scripts/MiniGame/BlockPuzzle/Class/UIHander/BlockSubmitHander.cs +++ b/Assets/Scripts/MiniGame/BlockPuzzle/Class/UIHander/BlockSubmitHander.cs @@ -1,4 +1,4 @@ -using UnityEngine; +using UnityEngine; using AibisDream.UI; using UnityEngine.Events; using UnityEngine.UI; @@ -11,11 +11,14 @@ namespace AibisDream [SerializeField] private SpriteTint validAsset; [SerializeField] private SpriteTint invalidAsset; - private bool _validState = false; private BaseButton submitButton; private Image submitButtonImage; public UnityEvent OnClick => submitButton.onClick; + // 状态管理 + private bool isValid = false; + private bool isLocked = false; + private void Awake() { submitButtonImage = GetComponent(); @@ -23,31 +26,60 @@ namespace AibisDream submitButton.onStateTransition.AddListener(OnStateTransition); } + /// + /// 设置验证状态(决定按钮外观) + /// + public void SetValidState(bool valid) + { + isValid = valid; + UpdateButtonVisual(); + UpdateInteractableState(); + } + + /// + /// 设置锁定状态(决定能否交互,不改变外观) + /// + public void SetLockedState(bool locked) + { + isLocked = locked; + UpdateInteractableState(); + } + + private void UpdateInteractableState() + { + if (submitButton == null) return; + submitButton.interactable = isValid && !isLocked; + } + + private void UpdateButtonVisual() + { + if (submitButtonImage == null) return; + var asset = isValid ? validAsset : invalidAsset; + submitButtonImage.sprite = asset.normalSprite; + } + private void OnStateTransition(SelectionType stateTransition, bool instant) { + var asset = isValid ? validAsset : invalidAsset; + switch (stateTransition) { case SelectionType.Normal: - submitButtonImage.sprite = _validState ? validAsset.normalSprite : invalidAsset.normalSprite; + submitButtonImage.sprite = asset.normalSprite; break; case SelectionType.Highlighted: - submitButtonImage.sprite = _validState ? validAsset.highlightedSprite : invalidAsset.highlightedSprite; + submitButtonImage.sprite = asset.highlightedSprite; break; case SelectionType.Pressed: - submitButtonImage.sprite = _validState ? validAsset.pressedSprite : invalidAsset.pressedSprite; + submitButtonImage.sprite = asset.pressedSprite; break; case SelectionType.Selected: - submitButtonImage.sprite = _validState ? validAsset.selectedSprite : invalidAsset.selectedSprite; + submitButtonImage.sprite = asset.selectedSprite; break; case SelectionType.Disabled: - submitButtonImage.sprite = _validState ? validAsset.disabledSprite : invalidAsset.disabledSprite; + submitButtonImage.sprite = asset.disabledSprite; break; } } - - public void SetValidState(bool valid) - { - _validState = valid; - } } } \ No newline at end of file diff --git a/Assets/Scripts/MiniGame/BlockPuzzle/Interface/IBlockShapeDragger.cs b/Assets/Scripts/MiniGame/BlockPuzzle/Interface/IBlockShapeDragger.cs index ce6796eb7..843218783 100644 --- a/Assets/Scripts/MiniGame/BlockPuzzle/Interface/IBlockShapeDragger.cs +++ b/Assets/Scripts/MiniGame/BlockPuzzle/Interface/IBlockShapeDragger.cs @@ -1,4 +1,4 @@ -using UnityEngine; +using UnityEngine; using System; namespace AibisDream @@ -14,76 +14,78 @@ namespace AibisDream } /// - /// 形状拖拽接口 - /// 负责处理形状的拖拽、吸附和放置逻辑 + /// 交互模式:由外界赋值控制每个 shape 是拖拽还是点击状态 /// - public interface IBlockShapeDragger + public enum InteractionMode + { + Drag, // 长按拿起、点击放下(当前逻辑) + Click // 仅点击,触发 OnClick + 反馈 + } + + /// + /// 拖拽结束结果类型 + /// + public enum DragEndResultType + { + None, // 未处理 + Recycled, // 已回收 + SnappedToGrid, // 已吸附到网格 + Dropped, // 直接放下(未吸附) + Cancelled // 取消拖拽 + } + + /// + /// 拖拽结束事件参数 + /// + public class DragEndEventArgs : EventArgs { /// - /// 当前拖拽状态 + /// 被拖拽的形状 /// - DragState CurrentState { get; } + public BlockShape Shape { get; set; } /// - /// 是否可拖拽 + /// 拖拽结束时的世界坐标位置 /// - bool IsDraggable { get; set; } + public Vector3 EndPosition { get; set; } /// - /// 目标网格 + /// 最终的拖拽状态(由处理器设置) /// - IBlockPuzzleGrid TargetGrid { get; } + public DragState FinalState { get; set; } /// - /// 获取形状 + /// 是否已被处理(如回收、吸附等) /// - IBlockShape BlockShape { get; } + public bool IsHandled { get; set; } /// - /// 拖拽开始事件 + /// 处理结果类型 /// - event Action OnDragStart; + public DragEndResultType ResultType { get; set; } + } + + /// + /// 拖拽开始事件参数 + /// + public class DragStartEventArgs : EventArgs + { + public BlockShape Shape { get; set; } + public Vector3 StartPosition { get; set; } + } + + /// + /// 拖拽更新事件参数 + /// + public class DragUpdateEventArgs : EventArgs + { + public BlockShape Shape { get; set; } + public Vector3 CurrentPosition { get; set; } /// - /// 拖拽更新事件 + /// 当前的吸附检测结果(由Grid提供) /// - event Action OnDragUpdate; - - /// - /// 拖拽结束事件 - /// - event Action OnDragEnd; // bool表示是否成功放置 - - /// - /// 开始拖拽 - /// - void StartDrag(); - - /// - /// 更新拖拽位置 - /// - void UpdateDrag(); - - /// - /// 结束拖拽 - /// - void EndDrag(); - - /// - /// 检查是否可以放置到指定位置 - /// - /// 网格X坐标 - /// 网格Y坐标 - /// 是否可以放置 - // bool CanPlaceAt(int gridX, int gridY); - - /// - /// 获取当前吸附的网格坐标 - /// - /// 输出的网格X坐标 - /// 输出的网格Y坐标 - /// 是否正在吸附 - // bool GetSnapGridPosition(out int gridX, out int gridY); + public SnapResult CurrentSnapResult { get; set; } } } diff --git a/Assets/Scripts/MiniGame/BlockPuzzle/Test/BlockPuzzleTest.cs b/Assets/Scripts/MiniGame/BlockPuzzle/Test/BlockPuzzleTest.cs index 61bd1f5cb..3072c9848 100644 --- a/Assets/Scripts/MiniGame/BlockPuzzle/Test/BlockPuzzleTest.cs +++ b/Assets/Scripts/MiniGame/BlockPuzzle/Test/BlockPuzzleTest.cs @@ -18,14 +18,21 @@ namespace AibisDream.Test public void UnlockBlockPuzzle() { ActionKit.Sequence() - .Coroutine(() => { + .Coroutine(() => + { return TimelineCenter.Instance.PlayTimelineAsync("卡扣/卡扣解锁"); }) - .Callback(() => { + .Callback(() => + { BlockPuzzleYarnCommand.UnlockSystem(); }) .Start(this); } + + public void EnableDrag() + { + BlockPuzzleYarnCommand.EnableDrag(); + } public void LoadShapeRack() { diff --git a/Assets/StreamingAssets/LevelData/BlockPuzzle/BlockPuzzleData.json b/Assets/StreamingAssets/LevelData/BlockPuzzle/BlockPuzzleData.json index f95a3823d..b421480b8 100644 --- a/Assets/StreamingAssets/LevelData/BlockPuzzle/BlockPuzzleData.json +++ b/Assets/StreamingAssets/LevelData/BlockPuzzle/BlockPuzzleData.json @@ -1 +1,85 @@ -{"Game2":{"puzzleName":"Game2","gridWidth":6,"gridHeight":7,"cellSize":1.17,"gridShapeDataInfos":[{"shapeKey":"共鸣腔","rootCell":{"x":4,"y":5,"magnitude":6.40312433,"sqrMagnitude":41},"rotationAngle":0,"isDraggable":true},{"shapeKey":"动平衡飞轮组","rootCell":{"x":3,"y":2,"magnitude":3.60555124,"sqrMagnitude":13},"rotationAngle":0,"isDraggable":true},{"shapeKey":"惯性稳压器","rootCell":{"x":0,"y":0,"magnitude":0.0,"sqrMagnitude":0},"rotationAngle":0,"isDraggable":true},{"shapeKey":"进气系统","rootCell":{"x":2,"y":0,"magnitude":2.0,"sqrMagnitude":4},"rotationAngle":0,"isDraggable":true},{"shapeKey":"辅助涡轮","rootCell":{"x":4,"y":0,"magnitude":4.0,"sqrMagnitude":16},"rotationAngle":0,"isDraggable":true},{"shapeKey":"散热系统","rootCell":{"x":0,"y":2,"magnitude":2.0,"sqrMagnitude":4},"rotationAngle":0,"isDraggable":true},{"shapeKey":"旧动力核心","rootCell":{"x":0,"y":4,"magnitude":4.0,"sqrMagnitude":16},"rotationAngle":0,"isDraggable":true}],"shapeInRack":["新动力核心","散热条"],"validatorName":"BlockPuzzleValidtor/引擎室规则.asset"}} \ No newline at end of file +{ + "Game2": { + "puzzleName": "Game2", + "gridWidth": 6, + "gridHeight": 7, + "cellSize": 1.17, + "gridShapeDataInfos": [ + { + "shapeKey": "共鸣腔", + "rootCell": { + "x": 4, + "y": 5, + "magnitude": 6.40312433, + "sqrMagnitude": 41 + }, + "rotationAngle": 0 + }, + { + "shapeKey": "动平衡飞轮组", + "rootCell": { + "x": 3, + "y": 2, + "magnitude": 3.60555124, + "sqrMagnitude": 13 + }, + "rotationAngle": 0 + }, + { + "shapeKey": "惯性稳压器", + "rootCell": { + "x": 0, + "y": 0, + "magnitude": 0.0, + "sqrMagnitude": 0 + }, + "rotationAngle": 0 + }, + { + "shapeKey": "进气系统", + "rootCell": { + "x": 2, + "y": 0, + "magnitude": 2.0, + "sqrMagnitude": 4 + }, + "rotationAngle": 0 + }, + { + "shapeKey": "辅助涡轮", + "rootCell": { + "x": 4, + "y": 0, + "magnitude": 4.0, + "sqrMagnitude": 16 + }, + "rotationAngle": 0 + }, + { + "shapeKey": "散热系统", + "rootCell": { + "x": 0, + "y": 2, + "magnitude": 2.0, + "sqrMagnitude": 4 + }, + "rotationAngle": 0 + }, + { + "shapeKey": "旧动力核心", + "rootCell": { + "x": 0, + "y": 4, + "magnitude": 4.0, + "sqrMagnitude": 16 + }, + "rotationAngle": 0 + } + ], + "shapeInRack": [ + "新动力核心", + "散热条" + ], + "validatorName": "BlockPuzzleValidtor/引擎室规则.asset" + } +} \ No newline at end of file