using System; using System.Collections; using UnityEngine; using UnityEngine.XR; using UnityEngine.XR.Interaction.Toolkit; using UnityEngine.XR.Interaction.Toolkit.Interactors; public enum HandSide { Left, Right } public class InteractionView : MonoBehaviour { public event Action OnRobotHoverStateChanged; public event Action OnRobotGrabStart; public event Action OnRobotGrabbed; public event Action OnRobotReleased; public event Action OnPointClicked; public event Action OnPointDragStart; public event Action OnPointDragUpdate; public event Action OnPointDragEnd; [SerializeField]private NearFarInteractor nearFarInteractor; public HandSide handSide; private XRBaseInteractor baseInteractor; private IXRRayProvider rayProvider; private InputDevice hapticDevice; private bool isHapticDeviceFound = false; [SerializeField] [Tooltip("µå·¡±× ½Ã À§Ä¡¸¦ Ç¥½ÃÇÒ ¹ÝÅõ¸í ·Îº¿ ¸ðµ¨")] private GameObject ghostRobot; [SerializeField] [Tooltip("µå·¡±×¿ë ¸¶¿ì½º À̹ÌÁö")] private GameObject dragArrow; [SerializeField] private float clickTimeThreshold = 0.5f; [SerializeField] private float dragMovementThreshold = 0.05f; private Coroutine clickOrDragCoroutine = null; public Vector3 startGrabPosition; public float distance; private bool isInitialized = false; private bool isGrabbingPoint = false; private int currentGrabbedPointIndex = -1; public Vector3 currentTargetPosition = Vector3.zero; Quaternion currentTargetRotation = Quaternion.identity; public bool isGrabbingRobot = false; void Update() { if (!isInitialized || rayProvider == null) { if (nearFarInteractor == null) { nearFarInteractor = GetComponentInChildren(true); baseInteractor = nearFarInteractor as XRBaseInteractor; rayProvider = nearFarInteractor as IXRRayProvider; } if (nearFarInteractor != null) { InitializeInteraction(); } } else { currentTargetPosition = rayProvider.rayEndPoint; currentTargetRotation = baseInteractor.attachTransform.rotation; if (isGrabbingRobot) { OnRobotGrabbed?.Invoke(currentTargetPosition, currentTargetRotation); return; } if (isGrabbingPoint) { OnPointDragUpdate?.Invoke(currentGrabbedPointIndex, currentTargetPosition, currentTargetRotation); } else if (clickOrDragCoroutine != null) { distance = Vector3.Distance(startGrabPosition, currentTargetPosition); if (distance > dragMovementThreshold) { StartDragMode(); } } } } private void InitializeInteraction() { // XRI À̺¥Æ® ±¸µ¶ baseInteractor.selectEntered.AddListener(HandleGrabStart); baseInteractor.selectExited.AddListener(HandleGrabEnd); baseInteractor.hoverEntered.AddListener(OnHoverEntered); baseInteractor.hoverExited.AddListener(OnHoverExited); TryInitializeHapticDevice(); isInitialized = true; AppManager.Instance.RegisterView(this); } private void OnDestroy() { if (baseInteractor != null) { baseInteractor.selectEntered.RemoveListener(HandleGrabStart); baseInteractor.selectExited.RemoveListener(HandleGrabEnd); } } private void TryInitializeHapticDevice() { XRNode node = (this.handSide == HandSide.Left) ? XRNode.LeftHand : XRNode.RightHand; InputDevice device = InputDevices.GetDeviceAtXRNode(node); if (device.isValid) { hapticDevice = device; isHapticDeviceFound = true; } } /// /// InputDevice¸¦ »ç¿ëÇÑ ÇÝÆ½ /// public void SendHapticImpulse(float amplitude, float duration) { // ÄÁÆ®·Ñ·¯°¡ ¿¬°áµÇÁö ¾Ê¾Ò°Å³ª(isValid) ²¨Á®ÀÖÀ» ¼ö ÀÖÀ¸¹Ç·Î ¸Å¹ø È®ÀÎ if (!isHapticDeviceFound || !hapticDevice.isValid) { TryInitializeHapticDevice(); // ´Ù½Ã ã±â ½Ãµµ if (!isHapticDeviceFound) return; // ±×·¡µµ ¾øÀ¸¸é Áß´Ü } // Áøµ¿ ±â´ÉÀÌ ÀÖ´ÂÁö È®ÀÎ HapticCapabilities capabilities; if (hapticDevice.TryGetHapticCapabilities(out capabilities)) { if (capabilities.supportsImpulse) { // Áøµ¿ Àü¼Û hapticDevice.SendHapticImpulse(0, amplitude, duration); } } } // --- XRI À̺¥Æ® Çڵ鷯 --- private void HandleGrabStart(SelectEnterEventArgs args) { GameObject grabbedGO = args.interactableObject.transform.gameObject; RobotPoint point = grabbedGO.GetComponent(); if (point != null) { isGrabbingPoint = true; isGrabbingRobot = false; currentGrabbedPointIndex = point.pointIndex; // ŸÀÌ¸Ó ½ÃÀÛ startGrabPosition = rayProvider.rayEndPoint; if (clickOrDragCoroutine != null) StopCoroutine(clickOrDragCoroutine); clickOrDragCoroutine = StartCoroutine(ClickOrDragTimer()); //OnPointDragStart?.Invoke(currentGrabbedPointIndex); } else if (grabbedGO.CompareTag("RobotArm")) { isGrabbingPoint = false; isGrabbingRobot = true; currentGrabbedPointIndex = -1; OnRobotGrabStart?.Invoke(startGrabPosition); OnPointDragStart?.Invoke(currentGrabbedPointIndex); } } private void HandleGrabEnd(SelectExitEventArgs args) { if (isGrabbingRobot) { OnRobotReleased?.Invoke(new RobotData()); } else { if (clickOrDragCoroutine != null) { StopCoroutine(clickOrDragCoroutine); clickOrDragCoroutine = null; OnPointClicked?.Invoke(currentGrabbedPointIndex, currentTargetPosition); } else if (isGrabbingPoint) { OnPointDragEnd?.Invoke(currentGrabbedPointIndex); } } // »óÅ ÃʱâÈ­ clickOrDragCoroutine = null; isGrabbingPoint = false; isGrabbingRobot = false; currentGrabbedPointIndex = -1; } private void OnHoverEntered(HoverEnterEventArgs args) { // È£¹öÇÑ ¹°Ã¼°¡ '·Îº¿'ÀÎÁö È®ÀÎ if (args.interactableObject.transform.CompareTag("RobotArm")) { OnRobotHoverStateChanged?.Invoke(true); } } // È£¹ö Á¾·á ½Ã È£Ãâ private void OnHoverExited(HoverExitEventArgs args) { if (args.interactableObject.transform.CompareTag("RobotArm")) { OnRobotHoverStateChanged?.Invoke(false); } } // Ŭ¸¯/µå·¡±× ±¸ºÐÇϴ ŸÀÌ¸Ó ÄÚ·çÆ¾ private IEnumerator ClickOrDragTimer() { yield return new WaitForSeconds(clickTimeThreshold); if (clickOrDragCoroutine != null) { Debug.Log("½Ã°£ Ãʰú·Î µå·¡±× ½ÃÀÛ (OnPointDragStart)"); StartDragMode(); } } // ŸÀÌ¸Ó ÁßÁöÇÏ°í µå·¡±× ¸ðµå·Î Àüȯ private void StartDragMode() { if (clickOrDragCoroutine != null) { StopCoroutine(clickOrDragCoroutine); clickOrDragCoroutine = null; } isGrabbingPoint = true; OnPointDragStart?.Invoke(currentGrabbedPointIndex); } // °í½ºÆ® ·Îº¿ÀÇ À§Ä¡/ȸÀü ¼³Á¤ public void ShowGhostRobot() { if (ghostRobot == null) { Debug.LogWarning("Ghost RobotÀÌ Inspector¿¡ ÇÒ´çµÇÁö ¾Ê¾Ò½À´Ï´Ù."); return; } ghostRobot.SetActive(true); } // °í½ºÆ® ·Îº¿ ºñȰ¼ºÈ­ public void HideGhostRobot() { if (ghostRobot != null) { ghostRobot.SetActive(false); } } // µå·¡±× È­»ìÇ¥ Ȱ¼ºÈ­ ¹× À§Ä¡ ¼³Á¤ public void ShowDragArrow(Vector3 position) { if (dragArrow == null) { Debug.LogWarning("Drag Arrow°¡ Inspector¿¡ ÇÒ´çµÇÁö ¾Ê¾Ò½À´Ï´Ù."); return; } dragArrow.SetActive(true); dragArrow.transform.position = position; } // µå·¡±× È­»ìÇ¥ ºñȰ¼ºÈ­ public void HideDragArrow() { if (dragArrow != null) { dragArrow.SetActive(false); } } // ±¤¼± ³¡ À§Ä¡ ¹Ýȯ public Vector3 GetCurrentRayPosition() { if (rayProvider != null) { return rayProvider.rayEndPoint; } if (baseInteractor != null) { return baseInteractor.attachTransform.position; } return transform.position; } }