石头手感调整

This commit is contained in:
2026-02-11 21:04:26 +08:00
parent 3de32fa91c
commit 7273a37e76
17 changed files with 1055 additions and 649 deletions
@@ -1,182 +0,0 @@
---
name: 长按拖拽交互改进
overview: 将现有的点击拖拽改为长按拖拽,增加长按反馈(轻微抖动和缓慢放大),松开鼠标时结束拖拽并放置形状。
todos:
- id: event_system
content: 改造事件系统:移除 OnPointerClick,启用 PointerDown/Up
status: completed
- id: long_press_fields
content: 添加长按相关字段和状态标记
status: completed
- id: long_press_coroutine
content: 实现长按协程:抖动 + 放大反馈动画
status: completed
- id: pointer_handlers
content: 实现 OnPointerDown 和 OnPointerUp 方法
status: completed
- id: drag_end_logic
content: 调整 EndDrag() 逻辑,适配松开鼠标结束拖拽
status: completed
- id: cleanup
content: 添加协程清理逻辑到 OnDestroy()
status: completed
isProject: false
---
# 长按拖拽交互改进方案
## 核心改动
`[ShapeDragger.cs](Assets/Scripts/MiniGame/BlockPuzzle/Class/ShapeDragger.cs)` 从点击切换拖拽改为长按开始、松开结束的拖拽方式。
## 实现方案
### 1. 事件系统改造
**移除**
- `OnPointerClick` 事件注册(第64行)
**新增**
- 启用 `PointerDown``PointerUp` 事件(第65-66行已有注释代码)
- 实现 `OnPointerDown``OnPointerUp` 方法
### 2. 长按检测机制
添加字段:
```csharp
[SerializeField] private float longPressDuration = 0.65f; // 可调整的长按时长
private float _pressStartTime;
private bool _isPressing;
private bool _isLongPressTriggered;
private Coroutine _longPressCoroutine;
```
实现逻辑:
- **PointerDown 时**:开始计时,启动长按协程,触发反馈动画
- **长按时间到达**:自动进入拖拽状态,停止反馈动画
- **PointerUp 时(长按完成前)**:取消操作,恢复原状
- **PointerUp 时(拖拽中)**:结束拖拽并放置
### 3. 长按反馈动画
创建协程 `LongPressCoroutine()`
**阶段一:蓄力反馈(0 → longPressDuration**
- **抖动效果**:使用 `Mathf.Sin` 配合时间,产生轻微的左右抖动
- 抖动幅度:`Vector3.right * Mathf.Sin(time * 30f) * 0.01f`
- 频率:30Hz(柔和)
- 振幅递增:随着时间增加,从 0 递增到 0.01 单位
- **放大效果**:使用 `Mathf.Lerp` 平滑缩放
-`Vector3.one` (1.0) 到 `Vector3.one * 1.15` (1.15倍)
- 使用 `EaseOutQuad` 缓动曲线,让放大更柔和
**阶段二:完成时刻**
- 停止抖动
- 保持放大状态 1.15 倍
- 触发 `StartDrag()`
### 4. 状态管理优化
新增状态标记:
- `LongPressing`(长按中)- 显示反馈,未进入拖拽
- `Dragging`(拖拽中)- 已有状态,保持不变
状态转换流程:
```
Idle/Placed
→ [按下鼠标]
→ LongPressing (反馈动画)
→ [达到时长]
→ Dragging
→ [松开鼠标]
→ Placed/Idle
```
取消流程:
```
LongPressing
→ [提前松开鼠标]
→ 恢复到 Idle/Placed(无操作)
```
### 5. Update 方法调整
当前 `Update()` 中仅在 `Dragging` 状态更新位置。需要保持此逻辑,确保长按反馈期间**不移动**物体位置,只在真正进入拖拽后才跟随鼠标。
### 6. 拖拽结束逻辑
修改 `EndDrag()` 方法:
- 去除原来需要再次点击才结束的逻辑
- 松开鼠标(PointerUp)时直接调用 `EndDrag()`
- 如果有有效吸附目标则放置,否则恢复原位(TODO 第137行)
## 技术细节
### 协程结构示例
```csharp
private IEnumerator LongPressCoroutine()
{
float elapsed = 0f;
Vector3 originalScale = transform.localScale;
Vector3 originalPosition = transform.localPosition;
while (elapsed < longPressDuration)
{
elapsed += Time.deltaTime;
float progress = elapsed / longPressDuration;
// 抖动(轻微)
float shake = Mathf.Sin(elapsed * 30f) * 0.01f * progress;
transform.localPosition = originalPosition + Vector3.right * shake;
// 放大(缓慢 EaseOut
float scale = Mathf.Lerp(1f, 1.15f, EaseOutQuad(progress));
transform.localScale = originalScale * scale;
yield return null;
}
// 完成长按
_isLongPressTriggered = true;
StartDrag();
}
private float EaseOutQuad(float t) => t * (2f - t);
```
### 清理逻辑
`OnDestroy()` 中添加:
```csharp
if (_longPressCoroutine != null)
{
StopCoroutine(_longPressCoroutine);
}
```
## 可调参数
通过 Inspector 可调整:
- `longPressDuration`:长按时长(默认 0.65 秒)
- 可选扩展:抖动强度、放大倍数等
## 优势
1. **手感提升**:长按提供明确的操作意图,避免误触
2. **视觉反馈**:抖动和放大让用户清楚知道系统正在响应
3. **直观操作**:松开即放置,符合常见拖拽操作习惯
4. **可取消**:未完成长按前松开可取消,容错性高
@@ -0,0 +1,64 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: bfda56da833e2384a9677cd3c976a436, type: 3}
m_Name: "\u5361\u6263\u89E3\u9501"
m_EditorClassIdentifier:
m_Version: 0
m_Tracks:
- {fileID: 7240691139934070323}
m_FixedDuration: 0
m_EditorSettings:
m_Framerate: 60
m_ScenePreview: 1
m_DurationMode: 0
m_MarkerTrack: {fileID: 0}
--- !u!114 &7240691139934070323
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: d21dcc2386d650c4597f3633c75a1f98, type: 3}
m_Name: Animation Track
m_EditorClassIdentifier:
m_Version: 3
m_AnimClip: {fileID: 0}
m_Locked: 0
m_Muted: 0
m_CustomPlayableFullTypename:
m_Curves: {fileID: 0}
m_Parent: {fileID: 11400000}
m_Children: []
m_Clips: []
m_Markers:
m_Objects: []
m_InfiniteClipPreExtrapolation: 0
m_InfiniteClipPostExtrapolation: 0
m_InfiniteClipOffsetPosition: {x: 0, y: 0, z: 0}
m_InfiniteClipOffsetEulerAngles: {x: 0, y: 0, z: 0}
m_InfiniteClipTimeOffset: 0
m_InfiniteClipRemoveOffset: 0
m_InfiniteClipApplyFootIK: 1
mInfiniteClipLoop: 0
m_MatchTargetFields: 63
m_Position: {x: 0, y: 0, z: 0}
m_EulerAngles: {x: 0, y: 0, z: 0}
m_AvatarMask: {fileID: 0}
m_ApplyAvatarMask: 1
m_TrackOffset: 0
m_InfiniteClip: {fileID: 0}
m_OpenClipOffsetRotation: {x: 0, y: 0, z: 0, w: 1}
m_Rotation: {x: 0, y: 0, z: 0, w: 1}
m_ApplyOffsets: 0
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3bd393f446c1ad24bb37d23570d508fd
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
+30 -7
View File
@@ -7,18 +7,30 @@ namespace AibisDream.SystemEditor
public class ShapeDraggerEditor : Editor
{
private SerializedProperty isDraggableProperty;
private SerializedProperty dragLayerNameProperty;
private SerializedProperty sortingLayerNameProperty;
private SerializedProperty dragLayerOrderProperty;
private SerializedProperty idleLayerNameProperty;
private SerializedProperty idleLayerOrderProperty;
private SerializedProperty pressLayerOrderProperty;
private SerializedProperty longPressDurationProperty;
private SerializedProperty longPressScaleProperty;
private SerializedProperty shakeIntensityProperty;
private SerializedProperty dragFollowSpeedProperty;
private void OnEnable()
{
isDraggableProperty = serializedObject.FindProperty("isDraggable");
dragLayerNameProperty = serializedObject.FindProperty("dragLayerId");
sortingLayerNameProperty = serializedObject.FindProperty("sortingLayerId");
dragLayerOrderProperty = serializedObject.FindProperty("dragLayerOrder");
idleLayerNameProperty = serializedObject.FindProperty("idleLayerId");
idleLayerOrderProperty = serializedObject.FindProperty("idleLayerOrder");
pressLayerOrderProperty = serializedObject.FindProperty("pressLayerOrder");
longPressDurationProperty = serializedObject.FindProperty("longPressDuration");
longPressScaleProperty = serializedObject.FindProperty("longPressScale");
shakeIntensityProperty = serializedObject.FindProperty("shakeIntensity");
dragFollowSpeedProperty = serializedObject.FindProperty("dragFollowSpeed");
}
public override void OnInspectorGUI()
@@ -33,12 +45,23 @@ namespace AibisDream.SystemEditor
EditorGUILayout.LabelField("排序层设置", EditorStyles.boldLabel);
// 使用 EditorKit 的公共函数绘制 SortingLayer
EditorKit.SortingLayerField(new GUIContent("拖拽时 Layer"), dragLayerNameProperty);
EditorKit.SortingLayerField(new GUIContent("Sorting Layer"), sortingLayerNameProperty);
// 使用 SerializedProperty 绘制 SortingOrder
EditorGUILayout.PropertyField(dragLayerOrderProperty, new GUIContent("拖拽时 Order"));
EditorKit.SortingLayerField(new GUIContent("闲置时 Layer"), idleLayerNameProperty);
EditorGUILayout.PropertyField(idleLayerOrderProperty, new GUIContent("闲置时 Order"));
EditorGUILayout.PropertyField(pressLayerOrderProperty, new GUIContent("长按时 Order"));
EditorGUILayout.Space();
EditorGUILayout.LabelField("长按设置", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(longPressDurationProperty, new GUIContent("长按时长"));
EditorGUILayout.PropertyField(longPressScaleProperty, new GUIContent("长按放大倍数"));
EditorGUILayout.PropertyField(shakeIntensityProperty, new GUIContent("抖动强度"));
EditorGUILayout.Space();
EditorGUILayout.LabelField("拖拽跟随", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(dragFollowSpeedProperty, new GUIContent("跟随速度", "越大跟得越紧,越小滞后越明显"));
serializedObject.ApplyModifiedProperties();
}
File diff suppressed because it is too large Load Diff
+5 -2
View File
@@ -175,7 +175,10 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
isDraggable: 1
dragLayerId: 1134468525
sortingLayerId: 1134468525
dragLayerOrder: 25
idleLayerId: 1134468525
idleLayerOrder: 11
pressLayerOrder: 13
longPressDuration: 0.65
longPressScale: 1.1
shakeIntensity: 0.1
@@ -378,16 +378,16 @@ MonoBehaviour:
grid: {fileID: 2843654655404132764}
showGridLines: 1
showCellSprites: 1
emptyCellColor: {r: 0.9, g: 0.9, b: 0.9, a: 0.3}
occupiedCellColor: {r: 0.2, g: 0.6, b: 1, a: 1}
previewValidCellColor: {r: 0.2, g: 1, b: 0.2, a: 0.5019608}
previewInvalidCellColor: {r: 1, g: 0.2, b: 0.2, a: 0.5019608}
emptyCellColor: {r: 0.9, g: 0.9, b: 0.9, a: 0.09803922}
occupiedCellColor: {r: 0.9019608, g: 0.9019608, b: 0.9019608, a: 0.09803922}
previewValidCellColor: {r: 0.45911944, g: 1, b: 0.45911944, a: 0.19607843}
previewInvalidCellColor: {r: 1, g: 0.37421376, b: 0.37421376, a: 0.19607843}
gridLineColor: {r: 0.5, g: 0.5, b: 0.5, a: 0.5}
gridLineWidth: 0.02
cellSprite: {fileID: 0}
cellMaterial: {fileID: 0}
sortingLayer: 0
sortingOrder: 0
sortingLayer: 1134468525
sortingOrder: 9
--- !u!1 &3053584644542009837
GameObject:
m_ObjectHideFlags: 0
@@ -697,7 +697,7 @@ Transform:
m_GameObject: {fileID: 4770854772182749078}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: -3.24, y: 0, z: 0}
m_LocalPosition: {x: -2.89, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
@@ -781,7 +781,7 @@ Transform:
m_GameObject: {fileID: 4791328254598961559}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: -6.07, y: 0.25, z: 0}
m_LocalPosition: {x: -5.13, y: 0.25, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
@@ -1076,7 +1076,7 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
width: 3.5
height: 5
height: 3
anchorPoint: 4
--- !u!1 &7489342787601982844
GameObject:
+441 -5
View File
@@ -5472,6 +5472,11 @@ MonoBehaviour:
- {fileID: 1397299356}
imageFields:
- {fileID: 1105741520}
--- !u!4 &1695625215 stripped
Transform:
m_CorrespondingSourceObject: {fileID: 1133554645891563439, guid: 5b2e098922156b645a82b7bcdb436ac9, type: 3}
m_PrefabInstance: {fileID: 4973841588922437151}
m_PrefabAsset: {fileID: 0}
--- !u!1 &1746796215
GameObject:
m_ObjectHideFlags: 0
@@ -6182,6 +6187,23 @@ MonoBehaviour:
logScreen: {fileID: 1532984432}
blockPuzzleScreen: {fileID: 1650896848}
blockPuzzlePart: {fileID: 599612780}
--- !u!1 &88715379554727043
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8155498365026185797}
- component: {fileID: 7745659672237331472}
m_Layer: 0
m_Name: "\u8986\u76D6\u7269B"
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &138611741520530880
Transform:
m_ObjectHideFlags: 0
@@ -6312,6 +6334,21 @@ GameObject:
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1846953664189554641
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8104346266387621237}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: -4.6976748, y: -0.034883976, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 8004407481828689236}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1918799646539294568
GameObject:
m_ObjectHideFlags: 0
@@ -6383,6 +6420,58 @@ GameObject:
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!212 &2732533432465196629
SpriteRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3335711713898395525}
m_Enabled: 1
m_CastShadows: 0
m_ReceiveShadows: 0
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: a97c105638bdf8b4a8650670310a4cd3, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 0
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 1134468525
m_SortingLayer: 10
m_SortingOrder: 7
m_Sprite: {fileID: 2872243242998036087, guid: eb7bc07dc232e7d4dbe28cdc16612c13, type: 3}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_FlipX: 0
m_FlipY: 0
m_DrawMode: 0
m_Size: {x: 7.9302325, y: 4.883721}
m_AdaptiveModeThreshold: 0.5
m_SpriteTileMode: 0
m_WasSpriteAssigned: 1
m_MaskInteraction: 0
m_SpriteSortPoint: 0
--- !u!212 &2733352662725789488
SpriteRenderer:
m_ObjectHideFlags: 0
@@ -6450,6 +6539,23 @@ Transform:
m_Children: []
m_Father: {fileID: 375453185567817599}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &3335711713898395525
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 9028183325311737200}
- component: {fileID: 2732533432465196629}
m_Layer: 0
m_Name: "\u87BA\u4E1D"
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!1 &3714885012444554289
GameObject:
m_ObjectHideFlags: 0
@@ -6638,17 +6744,20 @@ PrefabInstance:
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4770854772182749078, guid: 5b2e098922156b645a82b7bcdb436ac9, type: 3}
propertyPath: m_IsActive
value: 1
objectReference: {fileID: 0}
- target: {fileID: 5832289988495609530, guid: 5b2e098922156b645a82b7bcdb436ac9, type: 3}
propertyPath: m_Name
value: Block Puzzle Game
objectReference: {fileID: 0}
- target: {fileID: 6006157550530242493, guid: 5b2e098922156b645a82b7bcdb436ac9, type: 3}
propertyPath: m_LocalPosition.x
value: -2.89
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
m_AddedGameObjects:
- targetCorrespondingSourceObject: {fileID: 1133554645891563439, guid: 5b2e098922156b645a82b7bcdb436ac9, type: 3}
insertIndex: 4
addedObject: {fileID: 8004407481828689236}
m_AddedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: 5b2e098922156b645a82b7bcdb436ac9, type: 3}
--- !u!4 &5112960018860681732
@@ -6708,6 +6817,23 @@ GameObject:
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!1 &5556180071846380836
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7892565753077922110}
- component: {fileID: 5776859355242800320}
m_Layer: 0
m_Name: "\u8986\u76D6\u7269AO"
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!212 &5642937755487522085
SpriteRenderer:
m_ObjectHideFlags: 0
@@ -6775,6 +6901,58 @@ Transform:
m_Children: []
m_Father: {fileID: 5112960018860681732}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!212 &5776859355242800320
SpriteRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5556180071846380836}
m_Enabled: 1
m_CastShadows: 0
m_ReceiveShadows: 0
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: a97c105638bdf8b4a8650670310a4cd3, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 0
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 1134468525
m_SortingLayer: 10
m_SortingOrder: 4
m_Sprite: {fileID: 3321254178428531932, guid: eb7bc07dc232e7d4dbe28cdc16612c13, type: 3}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_FlipX: 0
m_FlipY: 0
m_DrawMode: 0
m_Size: {x: 8.046512, y: 8.302325}
m_AdaptiveModeThreshold: 0.5
m_SpriteTileMode: 0
m_WasSpriteAssigned: 1
m_MaskInteraction: 0
m_SpriteSortPoint: 0
--- !u!1 &6454654521631228249
GameObject:
m_ObjectHideFlags: 0
@@ -6878,6 +7056,124 @@ Transform:
m_Children: []
m_Father: {fileID: 375453185567817599}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!212 &7745659672237331472
SpriteRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 88715379554727043}
m_Enabled: 1
m_CastShadows: 0
m_ReceiveShadows: 0
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: a97c105638bdf8b4a8650670310a4cd3, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 0
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 1134468525
m_SortingLayer: 10
m_SortingOrder: 6
m_Sprite: {fileID: -5733857998535015527, guid: eb7bc07dc232e7d4dbe28cdc16612c13, type: 3}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_FlipX: 0
m_FlipY: 0
m_DrawMode: 0
m_Size: {x: 4.255814, y: 9.4651165}
m_AdaptiveModeThreshold: 0.5
m_SpriteTileMode: 0
m_WasSpriteAssigned: 1
m_MaskInteraction: 0
m_SpriteSortPoint: 0
--- !u!4 &7892565753077922110
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5556180071846380836}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: -2.6279073, y: -0.034883976, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 8004407481828689236}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!4 &8004407481828689236
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 9213599469519215927}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 9028183325311737200}
- {fileID: 8155498365026185797}
- {fileID: 1846953664189554641}
- {fileID: 7892565753077922110}
m_Father: {fileID: 1695625215}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &8104346266387621237
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1846953664189554641}
- component: {fileID: 8473700633921473907}
m_Layer: 0
m_Name: "\u8986\u76D6\u7269A"
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &8155498365026185797
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 88715379554727043}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: -0.4534893, y: -0.29069757, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 8004407481828689236}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!212 &8175606826540433908
SpriteRenderer:
m_ObjectHideFlags: 0
@@ -6981,6 +7277,58 @@ PlayableDirector:
m_SceneBindings: []
m_ExposedReferences:
m_References: []
--- !u!212 &8473700633921473907
SpriteRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8104346266387621237}
m_Enabled: 1
m_CastShadows: 0
m_ReceiveShadows: 0
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: a97c105638bdf8b4a8650670310a4cd3, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 0
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 1134468525
m_SortingLayer: 10
m_SortingOrder: 5
m_Sprite: {fileID: 8321270285523688510, guid: eb7bc07dc232e7d4dbe28cdc16612c13, type: 3}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_FlipX: 0
m_FlipY: 0
m_DrawMode: 0
m_Size: {x: 5.4883723, y: 8.953488}
m_AdaptiveModeThreshold: 0.5
m_SpriteTileMode: 0
m_WasSpriteAssigned: 1
m_MaskInteraction: 0
m_SpriteSortPoint: 0
--- !u!212 &8496249939351218120
SpriteRenderer:
m_ObjectHideFlags: 0
@@ -7114,6 +7462,21 @@ SortingGroup:
m_SortingLayer: 11
m_SortingOrder: 0
m_SortAtRoot: 0
--- !u!4 &9028183325311737200
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3335711713898395525}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: -3.3604655, y: -2.488372, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 8004407481828689236}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!212 &9153415064469407048
SpriteRenderer:
m_ObjectHideFlags: 0
@@ -7166,6 +7529,79 @@ SpriteRenderer:
m_WasSpriteAssigned: 1
m_MaskInteraction: 0
m_SpriteSortPoint: 0
--- !u!1 &9213599469519215927
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8004407481828689236}
- component: {fileID: 9213599469519215928}
- component: {fileID: 9213599469519215930}
- component: {fileID: 9213599469519215929}
m_Layer: 0
m_Name: "\u5361\u6263"
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!95 &9213599469519215928
Animator:
serializedVersion: 5
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 9213599469519215927}
m_Enabled: 1
m_Avatar: {fileID: 0}
m_Controller: {fileID: 0}
m_CullingMode: 0
m_UpdateMode: 0
m_ApplyRootMotion: 0
m_LinearVelocityBlending: 0
m_StabilizeFeet: 0
m_WarningMessage:
m_HasTransformHierarchy: 1
m_AllowConstantClipSamplingOptimization: 1
m_KeepAnimatorStateOnDisable: 0
m_WriteDefaultValuesOnDisable: 0
--- !u!114 &9213599469519215929
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 9213599469519215927}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 07d657d6b80509b4eb04f59afaa9aa2d, type: 3}
m_Name:
m_EditorClassIdentifier:
timelineName: "\u5361\u6263\u5F00\u542F"
director: {fileID: 9213599469519215930}
--- !u!320 &9213599469519215930
PlayableDirector:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 9213599469519215927}
m_Enabled: 1
serializedVersion: 3
m_PlayableAsset: {fileID: 11400000, guid: 3bd393f446c1ad24bb37d23570d508fd, type: 2}
m_InitialState: 1
m_WrapMode: 2
m_DirectorUpdateMode: 1
m_InitialTime: 0
m_SceneBindings:
- key: {fileID: 7240691139934070323, guid: 3bd393f446c1ad24bb37d23570d508fd, type: 2}
value: {fileID: 9213599469519215928}
m_ExposedReferences:
m_References: []
--- !u!1660057539 &9223372036854775807
SceneRoots:
m_ObjectHideFlags: 0
@@ -27,7 +27,7 @@ namespace AibisDream
blockPuzzleScreen.gameObject.SetActive(true);
// 在屏幕上显示对应信息
blockPuzzleScreen.SetImage("设备缩略图", shape.GetButtonImage());
blockPuzzleScreen.SetImage("设备缩略图", shape.GetDetailImage());
blockPuzzleScreen.SetText("设备名称", shape.shapeName);
blockPuzzleScreen.SetText("设备描述", shape.shapeDescription);
@@ -1,4 +1,3 @@
using System;
using UnityEngine;
using Cinemachine;
using AibisDream.Kit;
@@ -207,6 +206,8 @@ namespace AibisDream
public void OnShapeDragStart(IBlockShape blockShape)
{
grid.GridVisualizer.SetGridVisible(true);
// 显示Info
if (blockShape is BlockShape shape && shape.BlockShapeData.blockShapeInfo.shapeName != null)
{
@@ -225,6 +226,8 @@ namespace AibisDream
public void OnShapeDragEnd(IBlockShape blockShape, bool isPlaced)
{
grid.GridVisualizer.SetGridVisible(false);
// 如果放置成功,则预先检查一次
if (isPlaced)
{
@@ -56,6 +56,18 @@ namespace AibisDream
_grid = grid;
ClearVisualization();
CreateVisualization();
SetGridVisible(false);
}
/// <summary>
/// 设置网格显示/隐藏
/// </summary>
public void SetGridVisible(bool visible)
{
if (_gridLinesParent != null)
_gridLinesParent.SetActive(visible);
if (_cellsParent != null)
_cellsParent.SetActive(visible);
}
/// <summary>
@@ -1,6 +1,6 @@
using System;
using System.Collections;
using AibisDream.Framework;
using DG.Tweening;
using UnityEngine;
using UnityEngine.EventSystems;
@@ -11,16 +11,18 @@ namespace AibisDream
#region
[SerializeField] private bool isDraggable = true;
[SerializeField] private int dragLayerId;
[SerializeField] private int sortingLayerId;
[SerializeField] private int dragLayerOrder;
[SerializeField] private int idleLayerId;
[SerializeField] private int idleLayerOrder;
[SerializeField] private int pressLayerOrder;
[Header("长按设置")]
[SerializeField] private float longPressDuration = 0.65f;
[SerializeField] private float longPressScale = 1.1f;
[SerializeField] private float shakeIntensity = 0.1f;
[SerializeField] private float shakeIntensity = 1f;
[Header("拖拽跟随")]
[SerializeField] private float dragFollowSpeed = 15f;
#endregion
// 拖拽状态
@@ -41,7 +43,8 @@ namespace AibisDream
// 长按相关状态
private bool _isPressing;
private bool _isLongPressTriggered;
private Coroutine _longPressCoroutine;
private bool _canEndDragOnClick; // 标记是否可以通过点击结束拖拽
private Sequence _longPressSequence;
private Vector3 _originalScale;
private Vector3 _originalPosition;
@@ -72,12 +75,8 @@ namespace AibisDream
_eventTrigger.UnRegister(EventTriggerType.PointerUp, OnPointerUp);
_eventTrigger.UnRegister(EventTriggerType.PointerClick, OnPointerClick);
// 清理协程
if (_longPressCoroutine != null)
{
StopCoroutine(_longPressCoroutine);
_longPressCoroutine = null;
}
// 清理 DOTween 动画
_longPressSequence?.Kill();
}
private void RegisterEvent()
@@ -104,12 +103,18 @@ namespace AibisDream
if (!isDraggable) return;
// 如果当前正在拖拽中,标记可以通过点击结束
if (CurrentState == DragState.Dragging)
{
_canEndDragOnClick = true;
}
// 只有在静止状态才能开始长按
if (CurrentState == DragState.Idle || CurrentState == DragState.Placed)
else if (CurrentState == DragState.Idle || CurrentState == DragState.Placed)
{
_isPressing = true;
_isLongPressTriggered = false;
_longPressCoroutine = StartCoroutine(LongPressCoroutine());
_canEndDragOnClick = false; // 长按开始时不允许点击结束
StartLongPressAnimation();
}
}
@@ -134,10 +139,11 @@ namespace AibisDream
return;
}
// 只有在拖拽中点击才结束拖拽
if (CurrentState == DragState.Dragging && _isLongPressTriggered)
// 只有在拖拽中且允许点击结束时才结束拖拽
if (CurrentState == DragState.Dragging && _canEndDragOnClick)
{
EndDrag();
_canEndDragOnClick = false; // 重置标记
}
}
@@ -200,8 +206,9 @@ namespace AibisDream
_blockShape.RotateClockwise();
}
// 更新拖拽位置
transform.position = GetMouseWorldPosition() - _blockShape.GetCenterPosition();
// 更新拖拽位置(带轻微滞后感)
var targetPos = GetMouseWorldPosition() - _blockShape.GetCenterPosition();
transform.position = Vector3.Lerp(transform.position, targetPos, Time.deltaTime * dragFollowSpeed);
// 进行Snap检查
_snapResult = TargetGrid.CheckSnapToGrid(_blockShape);
@@ -215,17 +222,16 @@ namespace AibisDream
{
var sortingGroup = _blockShape?.SortingGroup;
if (sortingGroup == null) return;
sortingGroup.sortingLayerID = sortingLayerId;
switch (newState)
{
case DragState.Idle:
case DragState.Dragging:
sortingGroup.sortingOrder = dragLayerOrder;
sortingGroup.sortingLayerID = dragLayerId;
break;
case DragState.Placed:
sortingGroup.sortingOrder = idleLayerOrder;
sortingGroup.sortingLayerID = idleLayerId;
break;
}
}
@@ -236,55 +242,66 @@ namespace AibisDream
return new Vector3(mouseWorldPos.x, mouseWorldPos.y, 0);
}
private IEnumerator LongPressCoroutine()
private void StartLongPressAnimation()
{
float elapsed = 0f;
_originalScale = transform.localScale;
_originalPosition = transform.localPosition;
while (elapsed < longPressDuration)
{
elapsed += Time.deltaTime;
float progress = elapsed / longPressDuration;
// 抖动效果(轻微)
float shake = Mathf.Sin(elapsed * 30f) * shakeIntensity * progress;
transform.localPosition = _originalPosition + Vector3.right * shake;
// 放大效果(缓慢 EaseOut
float scale = Mathf.Lerp(1f, longPressScale, EaseOutQuad(progress));
transform.localScale = _originalScale * scale;
yield return null;
}
// 杀掉之前的动画
_longPressSequence?.Kill();
// 完成长按,开始拖拽
_isLongPressTriggered = true;
StartDrag();
}
private float EaseOutQuad(float t)
{
return t * (2f - t);
// 创建 DOTween 序列
_longPressSequence = DOTween.Sequence();
// 放大效果(使用 EaseOutQuad
_longPressSequence.Append(
transform.DOScale(_originalScale * longPressScale, longPressDuration)
.SetEase(Ease.OutQuad)
);
// 抖动效果(使用 DOShakePosition,与放大同时进行)
_longPressSequence.Join(
transform.DOShakePosition(
longPressDuration,
new Vector3(shakeIntensity, shakeIntensity, 0), // 只在 X 轴抖动
60, // 振动频率
90, // 随机性
false, // 不淡出
true // 使用局部坐标
)
);
// 完成时的回调
_longPressSequence.OnComplete(() =>
{
_isLongPressTriggered = true;
StartDrag();
});
// 设置为不自动销毁
_longPressSequence.SetAutoKill(true);
}
private void CancelLongPress()
{
if (_longPressCoroutine != null)
// 杀掉 DOTween 动画
if (_longPressSequence != null && _longPressSequence.IsActive())
{
StopCoroutine(_longPressCoroutine);
_longPressCoroutine = null;
_longPressSequence.Kill();
_longPressSequence = null;
}
// 恢复原始状态
if (_isPressing && !_isLongPressTriggered)
{
transform.DOKill(); // 确保所有动画都停止
transform.localScale = _originalScale;
transform.localPosition = _originalPosition;
}
_isPressing = false;
_isLongPressTriggered = false;
_canEndDragOnClick = false;
}
// 实现IInteraction接口
@@ -12,6 +12,19 @@ namespace AibisDream
private Action _onCompleteCallback;
#if UNITY_EDITOR
/// <summary>
/// 在 Editor 中,当组件创建或 Inspector 变化时自动查找同物体上的 PlayableDirector
/// </summary>
private void OnValidate()
{
if (director == null)
{
director = GetComponent<PlayableDirector>();
}
}
#endif
private void Awake()
{
if (director == null)
@@ -11,7 +11,8 @@
"正常",
"故障"
],
"buttonImagePath": "BlockPuzzle/按钮缩略图[共鸣腔]"
"buttonImagePath": "BlockPuzzle/按钮缩略图[共鸣腔]",
"detailImagePath": "BlockPuzzle/详情缩略图[共鸣腔]"
},
"动平衡飞轮组": {
"shapeName": "动平衡飞轮组",
@@ -25,7 +26,8 @@
"正常",
"故障"
],
"buttonImagePath": "BlockPuzzle/按钮缩略图[动平衡飞轮组]"
"buttonImagePath": "BlockPuzzle/按钮缩略图[动平衡飞轮组]",
"detailImagePath": "BlockPuzzle/详情缩略图[动平衡飞轮组]"
},
"惯性稳压器": {
"shapeName": "惯性稳压器",
@@ -39,7 +41,8 @@
"正常",
"故障"
],
"buttonImagePath": "BlockPuzzle/按钮缩略图[惯性稳压器]"
"buttonImagePath": "BlockPuzzle/按钮缩略图[惯性稳压器]",
"detailImagePath": "BlockPuzzle/详情缩略图[惯性稳压器]"
},
"进气系统": {
"shapeName": "进气系统",
@@ -53,7 +56,8 @@
"正常",
"故障"
],
"buttonImagePath": "BlockPuzzle/按钮缩略图[进气系统]"
"buttonImagePath": "BlockPuzzle/按钮缩略图[进气系统]",
"detailImagePath": "BlockPuzzle/详情缩略图[进气系统]"
},
"辅助涡轮": {
"shapeName": "辅助涡轮",
@@ -67,7 +71,8 @@
"正常",
"故障"
],
"buttonImagePath": "BlockPuzzle/按钮缩略图[辅助涡轮]"
"buttonImagePath": "BlockPuzzle/按钮缩略图[辅助涡轮]",
"detailImagePath": "BlockPuzzle/详情缩略图[辅助涡轮]"
},
"散热条": {
"shapeName": "散热条",
@@ -81,7 +86,8 @@
"正常",
"故障"
],
"buttonImagePath": "BlockPuzzle/按钮缩略图[散热条]"
"buttonImagePath": "BlockPuzzle/按钮缩略图[散热条]",
"detailImagePath": "BlockPuzzle/详情缩略图[散热条]"
},
"散热系统": {
"shapeName": "散热系统",
@@ -95,7 +101,8 @@
"正常",
"故障"
],
"buttonImagePath": "BlockPuzzle/按钮缩略图[散热系统]"
"buttonImagePath": "BlockPuzzle/按钮缩略图[散热系统]",
"detailImagePath": "BlockPuzzle/详情缩略图[散热系统]"
},
"旧动力核心": {
"shapeName": "旧动力核心",
@@ -109,7 +116,8 @@
"正常",
"故障"
],
"buttonImagePath": "BlockPuzzle/按钮缩略图[旧动力核心]"
"buttonImagePath": "BlockPuzzle/按钮缩略图[旧动力核心]",
"detailImagePath": "BlockPuzzle/详情缩略图[旧动力核心]"
},
"新动力核心": {
"shapeName": "新动力核心",
@@ -123,6 +131,7 @@
"正常",
"故障"
],
"buttonImagePath": "BlockPuzzle/按钮缩略图[新动力核心]"
"buttonImagePath": "BlockPuzzle/按钮缩略图[新动力核心]",
"detailImagePath": "BlockPuzzle/详情缩略图[新动力核心]"
}
}