Initial commit
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
//using Codice.CM.WorkspaceServer;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
public class DeleteInteractor : MonoBehaviour
|
||||
{
|
||||
private BaseInputProvider inputProvider;
|
||||
private CommandHandler commandHandler;
|
||||
private SelectionManager selectionManager;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
commandHandler = CommandHandler.getInstance();
|
||||
selectionManager = SelectionManager.getInstance();
|
||||
|
||||
inputProvider = InputProviderManager.getInstance()?.getCurrentInputProvider();
|
||||
|
||||
inputProvider.OnDelete.AddListener(() =>
|
||||
{
|
||||
DeleteObjects();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete an object and handle all its connections
|
||||
/// </summary>
|
||||
public void DeleteObjects()
|
||||
{
|
||||
List<IInteractable> selections = selectionManager.GetCurrentSelections();
|
||||
List<string> deletedBlockIds = new List<string>();
|
||||
foreach (IInteractable interactable in selections)
|
||||
{
|
||||
if (interactable is BaseInteractable baseInteractable)
|
||||
{
|
||||
deletedBlockIds.Add(FindBlockIndex(baseInteractable.gameObject));
|
||||
}
|
||||
}
|
||||
selectionManager.ClearSelection();
|
||||
commandHandler.removeGroup(deletedBlockIds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the index of a block in the environment (for CommandHandler)
|
||||
/// </summary>
|
||||
private string FindBlockIndex(GameObject obj)
|
||||
{
|
||||
|
||||
Block blockComponent = obj.GetComponent<Block>();
|
||||
if (blockComponent != null) return blockComponent.getBlockId();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f5bedc9ea59bf7242bd864dea16867ed
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,109 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Responsible only for raycasting and firing selection events.
|
||||
/// Socket detection has moved to SnappingSystem.
|
||||
/// Drag/drop logic has moved to GhostManager.
|
||||
/// </summary>
|
||||
public class RayInteractor : MonoBehaviour
|
||||
{
|
||||
private static RayInteractor instance;
|
||||
public static RayInteractor getInstance() => instance;
|
||||
|
||||
[SerializeField] private LayerMask gizmoLayerMask;
|
||||
[SerializeField] private LayerMask interactableLayerMask;
|
||||
[SerializeField] private LayerMask worldLayerMask;
|
||||
|
||||
private BaseInputProvider inputProvider;
|
||||
private Camera mainCamera;
|
||||
|
||||
private RaycastHit lastHit;
|
||||
private GameObject currentGizmo;
|
||||
|
||||
public UnityEvent OnWorldClick;
|
||||
|
||||
/// <summary>Fired on click when no ghost is active. Passes the hit IInteractable (or null).</summary>
|
||||
public UnityEvent<IInteractable> OnSelectEvent = new UnityEvent<IInteractable>();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (instance == null) instance = this;
|
||||
else Destroy(gameObject);
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
mainCamera = Camera.main;
|
||||
SetupInput();
|
||||
}
|
||||
|
||||
private void SetupInput()
|
||||
{
|
||||
inputProvider = InputProviderManager.getInstance()?.getCurrentInputProvider();
|
||||
|
||||
// Update ray hit every time the cursor moves
|
||||
inputProvider.OnCursorMoved.AddListener(screenPos =>
|
||||
{
|
||||
Ray ray = mainCamera.ScreenPointToRay(screenPos);
|
||||
|
||||
if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity, gizmoLayerMask, QueryTriggerInteraction.Ignore))
|
||||
{
|
||||
currentGizmo = hit.collider.gameObject;
|
||||
lastHit = new RaycastHit();
|
||||
}
|
||||
else
|
||||
{
|
||||
currentGizmo = null;
|
||||
|
||||
if (Physics.Raycast(ray, out hit, Mathf.Infinity, interactableLayerMask, QueryTriggerInteraction.Ignore))
|
||||
{
|
||||
lastHit = hit;
|
||||
}
|
||||
else if (Physics.Raycast(ray, out hit, Mathf.Infinity, worldLayerMask, QueryTriggerInteraction.Ignore))
|
||||
{
|
||||
lastHit = hit;
|
||||
}
|
||||
}
|
||||
|
||||
Debug.DrawRay(ray.origin, ray.direction * 100f, Color.red);
|
||||
});
|
||||
|
||||
// Place/click: delegate to GhostManager if a ghost is active, otherwise fire selection
|
||||
inputProvider.OnPlace.AddListener(() =>
|
||||
{
|
||||
if (GhostManagerV2.getInstance()?.HasActiveGhost() == true)
|
||||
return;
|
||||
|
||||
IInteractable hitInteractable = lastHit.collider?.GetComponentInParent<IInteractable>();
|
||||
OnSelectEvent.Invoke(hitInteractable);
|
||||
if (hitInteractable == null && currentGizmo == null)
|
||||
OnWorldClick.Invoke();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>Returns true and fills hitInfo if the last raycast produced a valid hit.</summary>
|
||||
public bool GetHitInfo(out RaycastHit hitInfo)
|
||||
{
|
||||
hitInfo = lastHit;
|
||||
return lastHit.collider != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the currently hovered gizmo GameObject, or null if no gizmo is hovered.
|
||||
/// </summary>
|
||||
public GameObject GetCurrentGizmo() => currentGizmo;
|
||||
|
||||
/// <summary>Re-casts a fresh ray from the current mouse position and returns the world hit point.</summary>
|
||||
public Vector3 GetHitPosition()
|
||||
{
|
||||
Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
|
||||
if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity, interactableLayerMask) ||
|
||||
Physics.Raycast(ray, out hit, Mathf.Infinity, worldLayerMask))
|
||||
{
|
||||
return hit.point;
|
||||
}
|
||||
return Vector3.zero;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2014fc0b500d2924eae27636ab6dfa44
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,130 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 045c88f5578ae514cb9b66dabdd880bb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,57 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class TestCommandInteractor : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private CommandHandler commandHandler;
|
||||
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.C))
|
||||
{
|
||||
commandHandler.createBlock("Cube", new Vector3(0, 0, 0), Quaternion.identity);
|
||||
Debug.Log("Create Command Executed");
|
||||
}
|
||||
if (Input.GetKeyDown(KeyCode.M))
|
||||
{
|
||||
commandHandler.moveBlock("0", Vector3.zero, new Vector3(Random.Range(-5, 5), Random.Range(-5, 5), Random.Range(-5, 5))
|
||||
, Quaternion.identity, Quaternion.Euler(new Vector3(Random.Range(-5, 5), Random.Range(-5, 5), Random.Range(-5, 5))));
|
||||
Debug.Log("Move Command Executed");
|
||||
}
|
||||
if (Input.GetKeyDown(KeyCode.R))
|
||||
{
|
||||
commandHandler.rotateBlock("0", Quaternion.Euler(Random.Range(0, 180), Random.Range(0, 180), Random.Range(0, 180)));
|
||||
Debug.Log("Rotate Command Executed");
|
||||
}
|
||||
if (Input.GetKeyDown(KeyCode.D))
|
||||
{
|
||||
commandHandler.removeBlock("0");
|
||||
Debug.Log("Delete Command Executed");
|
||||
}
|
||||
if (Input.GetKey(KeyCode.LeftControl) && Input.GetKeyDown(KeyCode.Z))
|
||||
{
|
||||
commandHandler.undoAction();
|
||||
Debug.Log("Undo Action Executed");
|
||||
}
|
||||
if (Input.GetKey(KeyCode.LeftControl) && Input.GetKeyDown(KeyCode.Y))
|
||||
{
|
||||
commandHandler.redoAction();
|
||||
Debug.Log("Redo Action Executed");
|
||||
}
|
||||
if (Input.GetKeyDown(KeyCode.X))
|
||||
{
|
||||
commandHandler.clearHistory();
|
||||
Debug.Log("Clear History Executed");
|
||||
}
|
||||
// if (Input.GetKeyDown(KeyCode.S))
|
||||
// {
|
||||
// commandHandler.saveCommands();
|
||||
// }
|
||||
// if (Input.GetKeyDown(KeyCode.L))
|
||||
// {
|
||||
// commandHandler.loadCommands();
|
||||
// }
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 29fd79635eb86014f95cae3fc00ffa19
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user