130 lines
4.7 KiB
C#
130 lines
4.7 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
/// <summary>
|
|
/// Handles socket proximity detection, highlighting, and closest-socket tracking.
|
|
///
|
|
/// Key fixes vs previous version:
|
|
/// 1. Searches from col.transform.ROOT — so if OverlapSphere hits a child mesh
|
|
/// collider, we still find SocketPoints that are siblings of that mesh.
|
|
/// 2. Uses QueryTriggerInteraction.Collide — socket colliders are usually triggers;
|
|
/// without this flag the overlap silently finds nothing when
|
|
/// Physics.queriesHitTriggers is false in Project Settings.
|
|
/// 3. Deduplicates across colliders that share a root, so a multi-collider block
|
|
/// doesn't add its sockets multiple times.
|
|
/// </summary>
|
|
public class SnapSystem : MonoBehaviour
|
|
{
|
|
private static SnapSystem instance;
|
|
public static SnapSystem getInstance() => instance;
|
|
|
|
[SerializeField] private float socketDetectionRadius = 0.5f;
|
|
|
|
private readonly List<SocketPoint> nearbySockets = new List<SocketPoint>();
|
|
private readonly HashSet<Transform> visitedRoots = new HashSet<Transform>();
|
|
private SocketPoint closestSocket;
|
|
|
|
private void Awake()
|
|
{
|
|
if (instance == null) instance = this;
|
|
else Destroy(gameObject);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Public API
|
|
// -------------------------------------------------------------------------
|
|
|
|
/// <summary>
|
|
/// Re-scans for sockets around <paramref name="detectionPoint"/>.
|
|
/// Call every frame while a ghost is being dragged.
|
|
/// </summary>
|
|
/// <param name="detectionPoint">
|
|
/// World-space ray hit point on the world surface — NOT the ghost's own position.
|
|
/// </param>
|
|
/// <param name="ghostRoot">
|
|
/// The ghost's root transform. Sockets belonging to this hierarchy are ignored.
|
|
/// </param>
|
|
public void UpdateSockets(Vector3 detectionPoint, Transform ghostRoot)
|
|
{
|
|
ClearHighlights();
|
|
nearbySockets.Clear();
|
|
visitedRoots.Clear();
|
|
closestSocket = null;
|
|
|
|
// QueryTriggerInteraction.Collide ensures trigger colliders (typical for sockets) are found
|
|
Collider[] colliders = Physics.OverlapSphere(detectionPoint, socketDetectionRadius,
|
|
Physics.AllLayers, QueryTriggerInteraction.Collide);
|
|
|
|
foreach (Collider col in colliders)
|
|
{
|
|
Transform root = col.transform.root;
|
|
|
|
// Skip anything that belongs to the held ghost
|
|
if (ghostRoot != null && root == ghostRoot) continue;
|
|
|
|
// Don't process the same block twice (it may have multiple colliders)
|
|
if (visitedRoots.Contains(root)) continue;
|
|
visitedRoots.Add(root);
|
|
|
|
// Search from root so we find ALL SocketPoints on the block,
|
|
// regardless of which child collider was hit by the overlap.
|
|
foreach (SocketPoint socket in root.GetComponentsInChildren<SocketPoint>())
|
|
{
|
|
if (socket.IsOccupied()) continue;
|
|
|
|
nearbySockets.Add(socket);
|
|
socket.Highlight(HighlightState.Compatible);
|
|
|
|
Debug.Log($"[SnappingSystem] Found socket: {socket.name} on {root.name}");
|
|
}
|
|
}
|
|
|
|
if (nearbySockets.Count > 0)
|
|
{
|
|
closestSocket = FindClosest(detectionPoint, nearbySockets);
|
|
closestSocket?.Highlight(HighlightState.Active);
|
|
Debug.Log($"[SnappingSystem] Closest socket: {closestSocket?.name}");
|
|
}
|
|
}
|
|
|
|
/// <summary>Resets all highlight states without clearing the list.</summary>
|
|
public void ClearHighlights()
|
|
{
|
|
foreach (SocketPoint socket in nearbySockets)
|
|
if (socket != null)
|
|
socket.Highlight(HighlightState.Default);
|
|
}
|
|
|
|
/// <summary>Full reset — clears highlights and all cached data.</summary>
|
|
public void Clear()
|
|
{
|
|
ClearHighlights();
|
|
nearbySockets.Clear();
|
|
visitedRoots.Clear();
|
|
closestSocket = null;
|
|
}
|
|
|
|
public List<SocketPoint> GetNearbySockets() => nearbySockets;
|
|
public SocketPoint GetClosestSocket() => closestSocket;
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Private helpers
|
|
// -------------------------------------------------------------------------
|
|
|
|
private static SocketPoint FindClosest(Vector3 position, List<SocketPoint> sockets)
|
|
{
|
|
SocketPoint closest = null;
|
|
float minDist = float.MaxValue;
|
|
|
|
foreach (SocketPoint socket in sockets)
|
|
{
|
|
float dist = Vector3.Distance(position, socket.GetTransform().position);
|
|
if (dist < minDist)
|
|
{
|
|
minDist = dist;
|
|
closest = socket;
|
|
}
|
|
}
|
|
return closest;
|
|
}
|
|
} |