About
Here's my assignment for the end of the Tool Programming on Unity module.
Assignment characteristics:
Create a level editor tool with the following features:
- Interface from which to select the prefab
- Preview of the prefab in the game viewport, following the mouse cursor
- Ability to rotate the preview of the selected prefab by 90 degrees at a time
- Spawning and snapping of the prefab at the entrance of the wall it is aligned with
- Undo system
Project Info
![]() |
Team Size: 1 |
![]() |
Time Frame: 1 week |
![]() |
Engine: Unity |
Preview's Positioning in 3D Space
The prefab preview needs to follow the mouse, so to calculate this position, it's fairly straightforward to understand that a raycast is required.
This raycast originates from the camera's position and is directed toward the mouse pointer (a function already provided by Unity).
Now that we have the direction, the key is determining at what distance from the camera the intersection point with the terrain lies along this path.
The first idea, which I initially implemented, was to spawn an invisible plane and simply get the exact point by detecting where the raycast hits.
This works, but it's not a very elegant solution.

If we think about the problem for a moment, it's not too difficult to realize that the camera's position and the point we want to calculate are the two vertices of the acute angles in a right triangle.
Therefore, the distance we need to calculate is simply the hypotenuse of this triangle.
Once this is understood, the solution becomes straightforward, as trigonometry tells us that:
hypotenuse = Cam.position.y / cos(α), where cos(α) = Cam.downward • Raycast.direction.
private void UpdatePreviewPosition()
{
Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit) && hit.collider.CompareTag("SnapPoint") && _enablePreviewSnapping)
{
TrySnapPreviewPosition(hit, ray);
}
else
{
_previewPosition = CalculatePreviewPosition(ray);
}
}
/// Calculate where to locate the preview in the 3D world space based on the camera and mouse pointer position
/// param name="ray" ray that starts from the camera position and go through the mouse pointer
private Vector3 CalculatePreviewPosition(Ray ray)
{
// H = Y / D where H = hypotenuse , Y = cathetus , D = Cos(angle between Y and H)
float hypotenuse = ray.origin.y / Vector3.Dot(Vector3.down, ray.direction);
return ray.origin + ray.direction * hypotenuse + Vector3.up * _spawnPositionHeight;
}
Preview Snapping
The reasoning I followed for this feature is as follows:
Each prefab is a SnappableObject, which has a list of snap points (i.e., a list of Transforms), each accompanied by a collider.
We define the direction from the center of a SnappableObject to one of its snap points as Dn.
This means that if a SnappableObject has four snap points, we can calculate D1, D2, D3, and D4.
Based on this definition, we can say that two snap points from two different SnappableObjects (Dx and Dy) are aligned if the dot product between them equals 1.
private void TrySnapPreviewPosition(RaycastHit snapPoint, Ray ray)
{
_selectedObject.TryGetComponent(out SnappableObject snappableObject);
//this vector is a direction that goes from the center of the snappableObject
//present in the scene to its snap point (door) currently inspected by the mouse pointer
Vector3 direction = snapPoint.transform.parent.rotation * snapPoint.transform.localPosition.normalized;
//(if doors are aligned)
if (CanSnap(direction, snappableObject))
{
_previewPosition = snapPoint.transform.position;
}
else
{
_previewPosition = CalculatePreviewPosition(ray);
}
}
private bool CanSnap(Vector3 snapDirection, SnappableObject snappableObj)
{
foreach(Transform trs in snappableObj.SnapPoint)
{
if (Vector3.Dot(snapDirection, _previewRotation * trs.localPosition.normalized) < -0.9f)
return true;
}
return false;
}