#nullable enable using System.Collections.Generic; using RTGLite; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; using UVC.Core; namespace UVC.Studio.Manager { /// /// 화면에서 마우스 클릭을 감지하여 객체 선택을 처리하는 컴포넌트 /// RTScene.Raycast를 사용하여 Collider 없이도 MeshRenderer 기반으로 객체 감지 /// public class SelectionInputHandler : MonoBehaviour { private SelectionManager? _selectionManager; private StageObjectManager? _stageObjectManager; /// /// 더블클릭 감지 시간 간격 (초) /// private const float DoubleClickThreshold = 0.5f; /// /// 마지막 클릭 시간 /// private float _lastClickTime = 0f; /// /// 마지막 클릭된 StageObject (더블클릭 확인용) /// private StageObjectManager.StageObject? _lastClickedStageObject; protected async void Start() { // InjectorSceneContext 초기화 대기 await InjectorSceneContext.Instance.WaitForInitializationAsync(); _selectionManager = InjectorAppContext.Instance.Get(); _stageObjectManager = InjectorAppContext.Instance.Get(); 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(); } } /// /// 마우스 클릭 처리 /// 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; } } /// /// Physics.Raycast를 사용하여 마우스 위치에서 객체를 픽킹합니다. /// /// 픽킹된 GameObject, 없으면 null 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; } /// /// GameObject 또는 부모에서 StageObject 찾기 /// 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; } /// /// 실제 상호작용 가능한 UI 위에 포인터가 있는지 확인 /// private bool IsPointerOverInteractableUI() { if (EventSystem.current == null) return false; var pointerEventData = new PointerEventData(EventSystem.current) { position = Input.mousePosition }; var raycastResults = new List(); 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(); if (graphic != null && graphic.raycastTarget) { var selectable = result.gameObject.GetComponentInParent(); var hasInteraction = selectable != null && selectable.interactable; if (graphic.color.a > 0.1f || hasInteraction) { return true; } } } return false; } } }