228 lines
7.7 KiB
C#
228 lines
7.7 KiB
C#
#nullable enable
|
|
using System.Collections.Generic;
|
|
using RTGLite;
|
|
using UnityEngine;
|
|
using UnityEngine.EventSystems;
|
|
using UnityEngine.UI;
|
|
using UVC.Core;
|
|
|
|
namespace UVC.Studio.Manager
|
|
{
|
|
/// <summary>
|
|
/// 화면에서 마우스 클릭을 감지하여 객체 선택을 처리하는 컴포넌트
|
|
/// RTScene.Raycast를 사용하여 Collider 없이도 MeshRenderer 기반으로 객체 감지
|
|
/// </summary>
|
|
public class SelectionInputHandler : MonoBehaviour
|
|
{
|
|
private SelectionManager? _selectionManager;
|
|
private StageObjectManager? _stageObjectManager;
|
|
|
|
/// <summary>
|
|
/// 더블클릭 감지 시간 간격 (초)
|
|
/// </summary>
|
|
private const float DoubleClickThreshold = 0.5f;
|
|
|
|
/// <summary>
|
|
/// 마지막 클릭 시간
|
|
/// </summary>
|
|
private float _lastClickTime = 0f;
|
|
|
|
/// <summary>
|
|
/// 마지막 클릭된 StageObject (더블클릭 확인용)
|
|
/// </summary>
|
|
private StageObjectManager.StageObject? _lastClickedStageObject;
|
|
|
|
protected async void Start()
|
|
{
|
|
// InjectorSceneContext 초기화 대기
|
|
await InjectorSceneContext.Instance.WaitForInitializationAsync();
|
|
|
|
_selectionManager = InjectorAppContext.Instance.Get<SelectionManager>();
|
|
_stageObjectManager = InjectorAppContext.Instance.Get<StageObjectManager>();
|
|
|
|
if (_selectionManager == null)
|
|
{
|
|
Debug.LogWarning("[SelectionInputHandler] SelectionManager not found.");
|
|
}
|
|
|
|
if (_stageObjectManager == null)
|
|
{
|
|
Debug.LogWarning("[SelectionInputHandler] StageObjectManager not found.");
|
|
}
|
|
}
|
|
|
|
protected void Update()
|
|
{
|
|
// 마우스 왼쪽 버튼 클릭 감지
|
|
// 기즈모 UI 위에서는 선택하지 않음
|
|
if (Input.GetMouseButtonUp(0) &&
|
|
(RTGizmos.get == null || !RTGizmos.get.IsGizmoGUIHovered()))
|
|
{
|
|
HandleClick();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 마우스 클릭 처리
|
|
/// </summary>
|
|
private void HandleClick()
|
|
{
|
|
// UI 위에서 클릭한 경우 무시
|
|
if (IsPointerOverInteractableUI())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (_selectionManager == null || _stageObjectManager == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// RTScene을 사용한 레이캐스트 (Collider 없이 MeshRenderer로 감지)
|
|
var pickedObject = PickObject();
|
|
Debug.Log($"[SelectionInputHandler] Picked Object: {(pickedObject != null ? pickedObject.name : "None")}");
|
|
|
|
// Shift 또는 Ctrl 키로 다중 선택
|
|
bool addToSelection = Input.GetKey(KeyCode.LeftShift) ||
|
|
Input.GetKey(KeyCode.RightShift) ||
|
|
Input.GetKey(KeyCode.LeftControl) ||
|
|
Input.GetKey(KeyCode.RightControl);
|
|
|
|
if (pickedObject == null)
|
|
{
|
|
// 빈 공간 클릭 시 선택 해제
|
|
// Shift/Ctrl 키를 누르고 있지 않으면 선택 해제
|
|
// 단, 기즈모 드래그 직후에는 선택 해제하지 않음
|
|
if (!addToSelection)
|
|
{
|
|
var gizmoUndoBridge = GizmoUndoBridge.Instance;
|
|
if (gizmoUndoBridge == null || !gizmoUndoBridge.IsDraggingOrJustEnded)
|
|
{
|
|
_selectionManager.DeselectAll();
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// 클릭된 객체 또는 부모에서 StageObject 찾기
|
|
var stageObject = FindStageObject(pickedObject);
|
|
|
|
if (stageObject != null)
|
|
{
|
|
// 더블클릭 감지
|
|
float currentTime = Time.unscaledTime;
|
|
bool isDoubleClick = (currentTime - _lastClickTime <= DoubleClickThreshold) &&
|
|
(_lastClickedStageObject == stageObject);
|
|
|
|
if (isDoubleClick)
|
|
{
|
|
// 더블클릭: 카메라 포커스 이동
|
|
_stageObjectManager.Focus(stageObject.GameObject);
|
|
_lastClickTime = 0f; // 연속 더블클릭 방지
|
|
_lastClickedStageObject = null;
|
|
return;
|
|
}
|
|
|
|
// 클릭 정보 저장
|
|
_lastClickTime = currentTime;
|
|
_lastClickedStageObject = stageObject;
|
|
|
|
if (addToSelection)
|
|
{
|
|
// 다중 선택: 토글
|
|
_selectionManager.ToggleSelection(stageObject, true);
|
|
}
|
|
else
|
|
{
|
|
// 단일 선택
|
|
_selectionManager.Select(stageObject, false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// 빈 공간 클릭 시 더블클릭 상태 초기화
|
|
_lastClickTime = 0f;
|
|
_lastClickedStageObject = null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Physics.Raycast를 사용하여 마우스 위치에서 객체를 픽킹합니다.
|
|
/// </summary>
|
|
/// <returns>픽킹된 GameObject, 없으면 null</returns>
|
|
private GameObject? PickObject()
|
|
{
|
|
if (Camera.main == null) return null;
|
|
|
|
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
|
|
|
|
if (Physics.Raycast(ray, out RaycastHit hit))
|
|
{
|
|
return hit.collider.gameObject;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// GameObject 또는 부모에서 StageObject 찾기
|
|
/// </summary>
|
|
private StageObjectManager.StageObject? FindStageObject(GameObject clickedObject)
|
|
{
|
|
if (_stageObjectManager == null) return null;
|
|
|
|
// 클릭된 객체에서 시작하여 부모까지 탐색
|
|
Transform? current = clickedObject.transform;
|
|
|
|
while (current != null)
|
|
{
|
|
var stageObject = _stageObjectManager.GetByGameObject(current.gameObject);
|
|
if (stageObject != null)
|
|
{
|
|
return stageObject;
|
|
}
|
|
current = current.parent;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 실제 상호작용 가능한 UI 위에 포인터가 있는지 확인
|
|
/// </summary>
|
|
private bool IsPointerOverInteractableUI()
|
|
{
|
|
if (EventSystem.current == null) return false;
|
|
|
|
var pointerEventData = new PointerEventData(EventSystem.current)
|
|
{
|
|
position = Input.mousePosition
|
|
};
|
|
|
|
var raycastResults = new List<RaycastResult>();
|
|
EventSystem.current.RaycastAll(pointerEventData, raycastResults);
|
|
|
|
foreach (var result in raycastResults)
|
|
{
|
|
// UI 레이어인지 확인
|
|
if (result.gameObject.layer != LayerMask.NameToLayer("UI")) continue;
|
|
|
|
// Graphic 컴포넌트 확인 (Image, Text 등)
|
|
var graphic = result.gameObject.GetComponent<Graphic>();
|
|
if (graphic != null && graphic.raycastTarget)
|
|
{
|
|
var selectable = result.gameObject.GetComponentInParent<Selectable>();
|
|
var hasInteraction = selectable != null && selectable.interactable;
|
|
|
|
if (graphic.color.a > 0.1f || hasInteraction)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|