Skip to content

Commit d984fda

Browse files
committed
fix: snapping tool
1 parent d27ea08 commit d984fda

File tree

17 files changed

+321
-178
lines changed

17 files changed

+321
-178
lines changed

Documentation/Tools.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Snapping Tool (Unity Editor)
2+
3+
A lightweight editor tool for snapping modular objects together via **SnappingPoints**.
4+
5+
---
6+
7+
## Overview
8+
The **Snapping Tool** helps align modular objects in the Unity SceneView by snapping compatible points together with visual feedback.
9+
10+
- Toggle tool with **`S`** key.
11+
- Drag **SnappingPoints** to nearby compatible points.
12+
- Bezier lines show possible snap targets (white = near, green = snap-ready).
13+
- Release **LMB** to snap and align automatically.
14+
15+
---
16+
17+
## Setup
18+
19+
1. **Add Components**
20+
![SnapPoint.png](../Images/SnapPoint.png)
21+
- Attach `SnappingObject` to your modular prefab or GameObject.
22+
- Add one or more `SnappingPoint` children.
23+
- Set each point’s:
24+
- **SnapType:** `Plug`, `Slot`, or `None`
25+
- **GroupId:** to match compatible types
26+
- **Parent:** assigned automatically (used to prevent self-snapping)
27+
28+
2. **Activate Tool**
29+
- Select a `SnappingObject` in the scene.
30+
- Press **`S`** to toggle the **Snapping Tool**.
31+
- If no object is selected, pressing `S` restores your previous tool.
32+
33+
3. **Use**
34+
- Drag a `Plug` point in SceneView.
35+
- Hover near a compatible `Slot` (white line = search range, green line = snap range).
36+
- Release mouse to snap and align the objects.
37+
38+
![snapping.gif](../Images/snapping.gif)
39+
40+
---
41+
42+
## Snap Rules
43+
44+
A `Plug` point will snap to a `Slot` when:
45+
- They have the **same GroupId**.
46+
- They belong to **different parents**.
47+
- The target point’s **SnapType** is not `Plug`.
48+
49+
---
50+
51+
## Constants
52+
53+
| Constant | Default | Description |
54+
|-----------|----------|-------------|
55+
| `RADIUS_SNAP` | 0.3 | Snap threshold |
56+
| `RADIUS_SEARCH` | 1.0 | Search radius |
57+
| `SNAP_TANGENT_LENGTH` | 1.0 | Bezier curve tangent scale |
58+
59+
---
60+
61+
## Tips
62+
- Only **Plugs** can be dragged to snap.
63+
- Keep `GroupId`s consistent between matching parts.
64+
- Make sure your project includes `Transform.GetMatrix/SetMatrix(ignoreScale: true)` helpers.
65+
66+
---
67+
68+
**Shortcut:** `S`
69+
**Namespace:** `OC.Editor`
70+
**Tool Name:** *Snapping*
71+
**Icon:** `d_Cubemap Icon`
72+
73+
---

Documentation/Tools/SnappingTool.md.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Editor/Scripts/SceneInteractionTool.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
using UnityEngine;
99
using UnityEngine.EventSystems;
1010

11-
namespace OC
11+
namespace OC.Editor
1212
{
1313
[EditorTool("Scene Interaction")]
1414
[Icon(ICON)]
File renamed without changes.
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using UnityEditor;
4+
using UnityEditor.EditorTools;
5+
using UnityEditor.ShortcutManagement;
6+
using UnityEngine;
7+
8+
namespace OC.Editor
9+
{
10+
[EditorTool("Snapping", typeof(SnappingObject))]
11+
[Icon(ICON)]
12+
public class SnappingTool : EditorTool
13+
{
14+
private const string ICON = "d_Cubemap Icon";
15+
private const float RADIUS_SNAP = 0.3f;
16+
private const float RADIUS_SEARCH = 1f;
17+
private const float SNAP_TANGENT_LENGTH = 1f;
18+
private List<SnappingPoint> _sceneSnappingPoints;
19+
private SnappingPoint _nearestSnappingPoint;
20+
private int _selectedPointIndex;
21+
22+
[Shortcut("Snapping Tool", typeof(SceneView), KeyCode.S)]
23+
private static void SnappingToolShortcut()
24+
{
25+
if (Selection.GetFiltered<SnappingObject>(SelectionMode.TopLevel).Length > 0)
26+
{
27+
ToolManager.SetActiveTool(typeof(SnappingTool));
28+
}
29+
else
30+
{
31+
ToolManager.RestorePreviousTool();
32+
}
33+
}
34+
35+
public override void OnActivated()
36+
{
37+
_sceneSnappingPoints = FindObjectsOfType<SnappingPoint>().ToList();
38+
}
39+
40+
public override void OnWillBeDeactivated()
41+
{
42+
_sceneSnappingPoints?.Clear();
43+
}
44+
45+
public override void OnToolGUI(EditorWindow window)
46+
{
47+
var snappingObject = target as SnappingObject;
48+
if (snappingObject == null) return;
49+
50+
if (GUIUtility.hotControl == 0)
51+
{
52+
_selectedPointIndex = -1;
53+
_nearestSnappingPoint = null;
54+
}
55+
56+
if (_selectedPointIndex > -1 && _nearestSnappingPoint != null)
57+
{
58+
var handleSize = HandleUtility.GetHandleSize(snappingObject.Points[_selectedPointIndex].transform.position);
59+
var distance = Vector3.Distance(snappingObject.Points[_selectedPointIndex].transform.position, _nearestSnappingPoint.transform.position);
60+
var scaledDistance = distance * handleSize;
61+
62+
if (scaledDistance < RADIUS_SNAP)
63+
{
64+
DrawHandleBezier(snappingObject.Points[_selectedPointIndex].transform, _nearestSnappingPoint.transform, Color.green);
65+
}
66+
else if (scaledDistance < RADIUS_SEARCH)
67+
{
68+
DrawHandleBezier(snappingObject.Points[_selectedPointIndex].transform, _nearestSnappingPoint.transform, Color.white);
69+
}
70+
71+
var e = Event.current;
72+
if (e.type == EventType.MouseUp && e.button == 0)
73+
{
74+
if (scaledDistance > RADIUS_SNAP) return;
75+
var targetMatrix = Matrix4x4.TRS(
76+
_nearestSnappingPoint.transform.position,
77+
_nearestSnappingPoint.transform.rotation * Quaternion.AngleAxis(180, Vector3.up),
78+
Vector3.one);
79+
SetSnappingObjectMatrix(snappingObject, snappingObject.Points[_selectedPointIndex], targetMatrix);
80+
e.Use();
81+
82+
//Reset state
83+
_selectedPointIndex = -1;
84+
_nearestSnappingPoint = null;
85+
GUIUtility.hotControl = 0;
86+
}
87+
}
88+
89+
for (var i = 0; i < snappingObject.Points.Count; i++)
90+
{
91+
var point = snappingObject.Points[i];
92+
if (point.SnapType is SnappingPoint.Type.Slot or SnappingPoint.Type.None) continue;
93+
94+
EditorGUI.BeginChangeCheck();
95+
var targetPosition = Handles.PositionHandle(point.transform.position, point.transform.rotation);
96+
var targetMatrix = Matrix4x4.TRS(targetPosition, point.transform.rotation, Vector3.one);
97+
98+
if (EditorGUI.EndChangeCheck())
99+
{
100+
_selectedPointIndex = i;
101+
if (TryFindNearestSnappingPoint(point, RADIUS_SEARCH * HandleUtility.GetHandleSize(point.transform.position), out var snappingPoint))
102+
{
103+
_nearestSnappingPoint = snappingPoint;
104+
}
105+
106+
SetSnappingObjectMatrix(snappingObject, point, targetMatrix);
107+
}
108+
}
109+
}
110+
111+
private void DrawHandleBezier(Transform from, Transform to, Color color)
112+
{
113+
var distance = Vector3.Distance(from.position, to.position);
114+
var tangent = distance * 0.3f * SNAP_TANGENT_LENGTH;
115+
var fromTangent = from.position + from.forward * tangent;
116+
var toTangent = to.position + to.forward * tangent;
117+
Handles.DrawBezier(from.position, to.position, fromTangent, toTangent, color, null, 5);
118+
}
119+
120+
private bool TryFindNearestSnappingPoint(SnappingPoint dragged, float radius, out SnappingPoint nearestSnappingPoint)
121+
{
122+
var distance = radius;
123+
nearestSnappingPoint = null;
124+
125+
foreach (var snappingPoint in _sceneSnappingPoints)
126+
{
127+
if (snappingPoint.Parent == dragged.Parent) continue;
128+
if (snappingPoint.GroupId != dragged.GroupId) continue;
129+
if (snappingPoint.SnapType == SnappingPoint.Type.Plug) continue;
130+
var d = Vector3.Distance(snappingPoint.transform.position, dragged.transform.position);
131+
if (!(d < distance)) continue;
132+
distance = d;
133+
nearestSnappingPoint = snappingPoint;
134+
}
135+
136+
return nearestSnappingPoint != null;
137+
}
138+
139+
private void SetSnappingObjectMatrix(SnappingObject snappingObject, SnappingPoint point, Matrix4x4 worldMatrix)
140+
{
141+
if (snappingObject == null) return;
142+
var root = snappingObject.transform.GetMatrix(ignoreScale: true);
143+
var offset = point.transform.GetMatrix(ignoreScale: true).inverse * root;
144+
snappingObject.transform.SetMatrix(worldMatrix * offset);
145+
Undo.RecordObject(snappingObject.transform, "Move SnappingObject");
146+
}
147+
}
148+
}

Editor/Scripts/Snapping/SnappingTool.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Editor/Scripts/SnappingTool/SnappingHandlesEditor.cs

Lines changed: 0 additions & 81 deletions
This file was deleted.

Editor/Scripts/SnappingTool/SnappingHandlesEditor.cs.meta

Lines changed: 0 additions & 3 deletions
This file was deleted.

README.md

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,41 +1042,10 @@ You can call this function from the Material Flow component’s context menu in
10421042
![BoundBoxColliderSize_Inspector.png](Documentation/Images/BoundBoxColliderSize_Inspector.png)
10431043

10441044

1045-
# Snapping Tool
1046-
1047-
The Snapping Tool included in the package allows to snap GameObjects to other GameObjects on defined snap points. This functionality is used to align GameObjects in a scene with precision.
1048-
1049-
## Usage
1050-
1051-
### SnapPoint Class
1052-
The `SnapPoint` class is used to define snapping points on a game object. It is visualized by a blue sphere gizmo.
1053-
1054-
![SnapPoint Component](Documentation/Images/SnapPoint.png)
1055-
1056-
- **Group Id**: Only SnapPoints with the same *Group Id* can snap to each other
1057-
- **Parent**: The *Parent* property must be set to the GameObject that should move with the snapping points.
1058-
- **Type**: Possible Types: *Plug* or *Slot*. Snap Points of *Plug* type can be dragged and snap to *Slot* Snap Points. *Slot* Snap Points can not be dragged.
1059-
- **Draw Gizmos**, **Gizmos Color**, **Gizmos Radius**: controls the Gizmos
1060-
1061-
### SnappingHandles Object
1062-
Provides visual handles for all `SnapPoint` components in the GameObjects children.
1063-
When the scene window is active and the GameObject containing the `SnappingHandles` component is selected, press the **S-Key** to display the handles. These handles can be used to drag the GameObject and snap it to other snap points if they are within proximity.
1064-
1065-
![SnappingHandles Component](Documentation/Images/SnappingHandles.png)
1066-
1067-
## Workflow
1068-
1069-
1. Attach the `SnapPoint` class to a GameObject to define a snap point.
1070-
2. Ensure that the *Parent* property of each `SnapPoint` is set to the GameObject that should be moved when dragging the SnapPoint.
1071-
3. Attach the `SnappingHandles` object to the GameObject that should be moved.
1072-
4. In the scene window, select the GameObject with the `SnappingHandles` component.
1073-
5. Press the **S-Key** to display the snapping handles.
1074-
6. Use the handles to drag the GameObject and snap it to other `SnapPoints`.
1045+
# [SnappingTool](Documentation/Tools/SnappingTool.md)
10751046

10761047
![Snapping in action](Documentation/Images/snapping.gif)
10771048

1078-
1079-
10801049
# Contributing
10811050

10821051
We welcome contributions from everyone and appreciate your effort to improve this project.

0 commit comments

Comments
 (0)