2026-01-20 11:27:56 +09:00
|
|
|
#nullable enable
|
|
|
|
|
using RTGLite;
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
using UVC.Core;
|
2026-03-10 11:35:30 +09:00
|
|
|
using UVC.UIToolkit;
|
|
|
|
|
using UVC.UIToolkit.Util;
|
2026-01-20 11:27:56 +09:00
|
|
|
|
2026-02-23 18:20:09 +09:00
|
|
|
namespace UVC.Studio.Manager
|
2026-01-20 11:27:56 +09:00
|
|
|
{
|
|
|
|
|
/// <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 초기화 대기
|
2026-03-10 11:35:30 +09:00
|
|
|
await InjectorSceneContext.Instance.WaitForInitializationAsync();
|
2026-01-20 11:27:56 +09:00
|
|
|
|
|
|
|
|
_selectionManager = InjectorAppContext.Instance.Get<SelectionManager>();
|
|
|
|
|
_stageObjectManager = InjectorAppContext.Instance.Get<StageObjectManager>();
|
|
|
|
|
|
|
|
|
|
if (_selectionManager == null)
|
|
|
|
|
{
|
2026-03-10 11:35:30 +09:00
|
|
|
Debug.LogWarning("[SelectionInputHandler] SelectionManager not found.");
|
2026-01-20 11:27:56 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_stageObjectManager == null)
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarning("[SelectionInputHandler] StageObjectManager not found.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected void Update()
|
|
|
|
|
{
|
|
|
|
|
// 마우스 왼쪽 버튼 클릭 감지
|
2026-03-10 11:35:30 +09:00
|
|
|
// 기즈모 UI 또는 UI Toolkit 패널 위에서는 선택하지 않음
|
|
|
|
|
if (Input.GetMouseButtonDown(0))
|
2026-01-20 11:27:56 +09:00
|
|
|
{
|
2026-03-10 11:35:30 +09:00
|
|
|
UTKPointerBlocker.IsPointerOverUI(Input.mousePosition, true);
|
2026-01-20 11:27:56 +09:00
|
|
|
}
|
2026-03-10 11:35:30 +09:00
|
|
|
else if (Input.GetMouseButtonUp(0))
|
2026-02-23 18:20:09 +09:00
|
|
|
{
|
2026-03-10 11:35:30 +09:00
|
|
|
bool isPointerOverUI = UTKPointerBlocker.IsPointerOverUI(Input.mousePosition);
|
|
|
|
|
bool isGizmoHovered = RTGizmos.get != null ? RTGizmos.get.IsGizmoGUIHovered() : false;
|
|
|
|
|
if (!isGizmoHovered && !isPointerOverUI) HandleClick();
|
2026-02-23 18:20:09 +09:00
|
|
|
}
|
|
|
|
|
}
|
2026-01-20 11:27:56 +09:00
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 마우스 클릭 처리
|
|
|
|
|
/// </summary>
|
|
|
|
|
private void HandleClick()
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
if (_selectionManager == null || _stageObjectManager == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-10 11:35:30 +09:00
|
|
|
// RTScene을 사용한 레이캐스트 (Collider 없이 MeshRenderer로 감지)
|
2026-01-20 11:27:56 +09:00
|
|
|
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)
|
|
|
|
|
{
|
2026-03-10 11:35:30 +09:00
|
|
|
_selectionManager.DeselectAll();
|
2026-01-20 11:27:56 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 클릭된 객체 또는 부모에서 StageObject 찾기
|
|
|
|
|
var stageObject = FindStageObject(pickedObject);
|
|
|
|
|
|
|
|
|
|
if (stageObject != null)
|
|
|
|
|
{
|
|
|
|
|
// 더블클릭 감지
|
|
|
|
|
float currentTime = Time.unscaledTime;
|
|
|
|
|
bool isDoubleClick = (currentTime - _lastClickTime <= DoubleClickThreshold) &&
|
|
|
|
|
(_lastClickedStageObject == stageObject);
|
|
|
|
|
|
|
|
|
|
if (isDoubleClick)
|
|
|
|
|
{
|
|
|
|
|
// 더블클릭: 카메라 포커스 이동
|
2026-03-10 11:35:30 +09:00
|
|
|
_stageObjectManager.Focus(stageObject.GameObject);
|
2026-01-20 11:27:56 +09:00
|
|
|
_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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|