107 lines
3.5 KiB
C#
107 lines
3.5 KiB
C#
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using UnityEngine.Events;
|
||
|
||
/// <summary>
|
||
/// Manages selection state for placed interactable objects.
|
||
/// Listens to RayInteractor.OnSelectEvent (which only fires when no ghost is active).
|
||
/// Supports single and multi-select (Ctrl/Shift).
|
||
/// </summary>
|
||
public class SelectionManager : MonoBehaviour
|
||
{
|
||
private static SelectionManager instance;
|
||
public static SelectionManager getInstance() => instance;
|
||
|
||
private RayInteractor rayInteractor;
|
||
private BaseInputProvider inputProvider;
|
||
|
||
private readonly List<IInteractable> currentSelections = new List<IInteractable>();
|
||
private bool multiSelectEnabled = false;
|
||
|
||
public UnityEvent<IInteractable> SelectionEvent;
|
||
|
||
// -------------------------------------------------------------------------
|
||
// Unity lifecycle
|
||
// -------------------------------------------------------------------------
|
||
|
||
private void Awake()
|
||
{
|
||
if (instance == null) instance = this;
|
||
else Destroy(gameObject);
|
||
}
|
||
|
||
private void Start()
|
||
{
|
||
rayInteractor = RayInteractor.getInstance();
|
||
inputProvider = InputProviderManager.getInstance().getCurrentInputProvider();
|
||
|
||
inputProvider.OnEnableMultiSelect.AddListener(() => multiSelectEnabled = true);
|
||
inputProvider.OnDisableMultiSelect.AddListener(() => multiSelectEnabled = false);
|
||
|
||
// RayInteractor only fires this when no ghost is active, so we can safely handle selection here
|
||
rayInteractor.OnSelectEvent.AddListener(HandleSelect);
|
||
}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// Selection logic
|
||
// -------------------------------------------------------------------------
|
||
|
||
private void HandleSelect(IInteractable interactable)
|
||
{
|
||
if (InputModeManager.Is(InputMode.UI)) return;
|
||
if (interactable == null)
|
||
{
|
||
SelectionEvent?.Invoke(interactable);
|
||
// Clicked empty space – clear selection unless multi-select is held
|
||
if (!multiSelectEnabled)
|
||
ClearSelection();
|
||
return;
|
||
}
|
||
|
||
if (multiSelectEnabled)
|
||
{
|
||
// Toggle the clicked interactable
|
||
if (currentSelections.Contains(interactable))
|
||
{
|
||
currentSelections.Remove(interactable);
|
||
interactable.highLightOff();
|
||
}
|
||
else
|
||
{
|
||
currentSelections.Add(interactable);
|
||
interactable.highLightOn();
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// Replace selection with just this interactable
|
||
bool Exists = currentSelections.Contains(interactable);
|
||
ClearSelection();
|
||
if (!Exists) {
|
||
currentSelections.Add(interactable);
|
||
interactable.highLightOn();
|
||
}
|
||
}
|
||
SelectionEvent?.Invoke(interactable);
|
||
}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// Public API
|
||
// -------------------------------------------------------------------------
|
||
|
||
public List<IInteractable> GetCurrentSelections() => currentSelections;
|
||
|
||
public void ClearSelection()
|
||
{
|
||
foreach (IInteractable selection in currentSelections)
|
||
selection.highLightOff();
|
||
|
||
currentSelections.Clear();
|
||
SelectionEvent?.Invoke(null);
|
||
}
|
||
|
||
public bool HasOneSelection()
|
||
{
|
||
return !(currentSelections.Count > 1 || currentSelections.Count == 0);
|
||
}
|
||
} |