diff --git a/.gitattributes b/.gitattributes index 34a4ec1..e4ad624 100644 --- a/.gitattributes +++ b/.gitattributes @@ -7,5 +7,6 @@ *.wav binary *.mp3 binary *.ogg binary +*.mp4 binary *.fbx binary *.asset binary diff --git a/.gitignore b/.gitignore index 6528df0..6d55965 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ # IDE / Generated *.csproj +*.csproj.user *.slnx *.sln .vscode/ diff --git a/Assets/Scenes/Game.unity b/Assets/Scenes/Game.unity index a523d91..4257265 100644 --- a/Assets/Scenes/Game.unity +++ b/Assets/Scenes/Game.unity @@ -150,10 +150,6 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 441482e8936e35048a1dffac814e3ef8, type: 3} m_Name: m_EditorClassIdentifier: - m_Profile: {fileID: 11400000, guid: 8882f7434ef4fcd458f02738f32c7b11, type: 2} - m_StaticLightingSkyUniqueID: 0 - m_SkySettings: {fileID: 0} - m_SkySettingsFromProfile: {fileID: 0} --- !u!4 &72731932 Transform: m_ObjectHideFlags: 1 @@ -616,42 +612,42 @@ PrefabInstance: - target: {fileID: 7481659171502770090, guid: d614df01a29ad3e45bb831298dfbad2c, type: 3} propertyPath: m_LocalPosition.x - value: 0.3567679 + value: 0 objectReference: {fileID: 0} - target: {fileID: 7481659171502770090, guid: d614df01a29ad3e45bb831298dfbad2c, type: 3} propertyPath: m_LocalPosition.y - value: -0.4698689 + value: 0 objectReference: {fileID: 0} - target: {fileID: 7481659171502770090, guid: d614df01a29ad3e45bb831298dfbad2c, type: 3} propertyPath: m_LocalPosition.z - value: -0.22523999 + value: 0 objectReference: {fileID: 0} - target: {fileID: 7481659171502770090, guid: d614df01a29ad3e45bb831298dfbad2c, type: 3} propertyPath: m_LocalRotation.w - value: 0.99958926 + value: 0.9239 objectReference: {fileID: 0} - target: {fileID: 7481659171502770090, guid: d614df01a29ad3e45bb831298dfbad2c, type: 3} propertyPath: m_LocalRotation.x - value: -0 + value: 0.3827 objectReference: {fileID: 0} - target: {fileID: 7481659171502770090, guid: d614df01a29ad3e45bb831298dfbad2c, type: 3} propertyPath: m_LocalRotation.y - value: -0.028659718 + value: 0 objectReference: {fileID: 0} - target: {fileID: 7481659171502770090, guid: d614df01a29ad3e45bb831298dfbad2c, type: 3} propertyPath: m_LocalRotation.z - value: -0 + value: 0 objectReference: {fileID: 0} - target: {fileID: 7481659171502770090, guid: d614df01a29ad3e45bb831298dfbad2c, type: 3} propertyPath: m_LocalEulerAnglesHint.x - value: 0 + value: 45 objectReference: {fileID: 0} - target: {fileID: 7481659171502770090, guid: d614df01a29ad3e45bb831298dfbad2c, type: 3} @@ -4558,7 +4554,6 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 3f8ab482667b2f44691ffe7131ffbdb7, type: 3} m_Name: m_EditorClassIdentifier: - sceneName: Menu --- !u!1 &2138780048 GameObject: m_ObjectHideFlags: 0 @@ -4883,17 +4878,17 @@ PrefabInstance: - target: {fileID: 3968956848465607219, guid: f555cbb0b089fb64ca7c7a5b07f11520, type: 3} propertyPath: m_LocalPosition.x - value: 0.36676788 + value: 0 objectReference: {fileID: 0} - target: {fileID: 3968956848465607219, guid: f555cbb0b089fb64ca7c7a5b07f11520, type: 3} propertyPath: m_LocalPosition.y - value: -0.18886888 + value: 0 objectReference: {fileID: 0} - target: {fileID: 3968956848465607219, guid: f555cbb0b089fb64ca7c7a5b07f11520, type: 3} propertyPath: m_LocalPosition.z - value: -0.23423982 + value: 0 objectReference: {fileID: 0} - target: {fileID: 3968956848465607219, guid: f555cbb0b089fb64ca7c7a5b07f11520, type: 3} @@ -5093,17 +5088,17 @@ PrefabInstance: - target: {fileID: 1002067974212273307, guid: e7173b1ee3369204eb181b376ede2a3e, type: 3} propertyPath: m_LocalPosition.x - value: 0.36676788 + value: 0 objectReference: {fileID: 0} - target: {fileID: 1002067974212273307, guid: e7173b1ee3369204eb181b376ede2a3e, type: 3} propertyPath: m_LocalPosition.y - value: -0.18886888 + value: 0 objectReference: {fileID: 0} - target: {fileID: 1002067974212273307, guid: e7173b1ee3369204eb181b376ede2a3e, type: 3} propertyPath: m_LocalPosition.z - value: -0.23423982 + value: 0 objectReference: {fileID: 0} - target: {fileID: 1002067974212273307, guid: e7173b1ee3369204eb181b376ede2a3e, type: 3} @@ -5305,17 +5300,17 @@ PrefabInstance: - target: {fileID: 7481659171502770090, guid: d614df01a29ad3e45bb831298dfbad2c, type: 3} propertyPath: m_LocalPosition.x - value: 0.3567679 + value: 0 objectReference: {fileID: 0} - target: {fileID: 7481659171502770090, guid: d614df01a29ad3e45bb831298dfbad2c, type: 3} propertyPath: m_LocalPosition.y - value: -0.4698689 + value: 0 objectReference: {fileID: 0} - target: {fileID: 7481659171502770090, guid: d614df01a29ad3e45bb831298dfbad2c, type: 3} propertyPath: m_LocalPosition.z - value: -0.22523999 + value: 0 objectReference: {fileID: 0} - target: {fileID: 7481659171502770090, guid: d614df01a29ad3e45bb831298dfbad2c, type: 3} diff --git a/Assets/Script/VRPointerController.cs b/Assets/Script/VRPointerController.cs new file mode 100644 index 0000000..3ee7ae4 --- /dev/null +++ b/Assets/Script/VRPointerController.cs @@ -0,0 +1,278 @@ +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.EventSystems; +using UnityEngine.UI; +using UnityEngine.XR; + +namespace VRBeats +{ + [RequireComponent(typeof(LineRenderer))] + public class VRPointerController : MonoBehaviour + { + [SerializeField] private bool isRightHand = true; + [SerializeField] private float maxDistance = 50f; + + private LineRenderer _line; + private bool _prevTrigger; + private Selectable _currentHover; + + private static readonly Color NormalColor = new Color(1f, 1f, 1f, 0.8f); + private static readonly Color HoverColor = new Color(0.3f, 0.8f, 1f, 1f); + + private float _deviceLogTimer; + + // 버튼별 이전 상태 + private bool _prevGrip; + private bool _prevPrimary; + private bool _prevSecondary; + private bool _prevThumbstick; + + private void Awake() + { + _line = GetComponent(); + _line.positionCount = 2; + _line.startWidth = 0.005f; + _line.endWidth = 0.001f; + _line.useWorldSpace = true; + // enabled 상태는 OnEnable/OnDisable이 관리 — Awake에서 건드리지 않음 + } + + private void Start() + { + // Awake 이후 reflection으로 isRightHand가 설정되므로 Start에서 로그 + Debug.Log($"[VRPointer] Start — {gameObject.name} / isRightHand={isRightHand}"); + } + + private void OnEnable() + { + if (_line != null) _line.enabled = true; + } + + private void OnDisable() + { + if (_line != null) _line.enabled = false; + } + + private void Update() + { + // 3초마다 연결된 디바이스 목록 출력 + _deviceLogTimer += Time.deltaTime; + if (_deviceLogTimer >= 3f) + { + _deviceLogTimer = 0f; + LogConnectedDevices(); + } + + bool trigger = GetButton(CommonUsages.triggerButton); + bool grip = GetButton(CommonUsages.gripButton); + bool primary = GetButton(CommonUsages.primaryButton); + bool secondary = GetButton(CommonUsages.secondaryButton); + bool thumbstick = GetButton(CommonUsages.primary2DAxisClick); + + bool triggerDown = trigger && !_prevTrigger; + bool gripDown = grip && !_prevGrip; + bool primaryDown = primary && !_prevPrimary; + bool secondaryDown = secondary && !_prevSecondary; + bool thumbstickDown = thumbstick && !_prevThumbstick; + + _prevTrigger = trigger; + _prevGrip = grip; + _prevPrimary = primary; + _prevSecondary = secondary; + _prevThumbstick = thumbstick; + + string hand = isRightHand ? "R" : "L"; + if (triggerDown) Debug.Log($"[VRPointer:{hand}] 검지 트리거 눌림"); + if (gripDown) Debug.Log($"[VRPointer:{hand}] 그립(중지) 눌림"); + if (primaryDown) Debug.Log($"[VRPointer:{hand}] {(isRightHand ? "A" : "X")} 버튼 눌림"); + if (secondaryDown) Debug.Log($"[VRPointer:{hand}] {(isRightHand ? "B" : "Y")} 버튼 눌림"); + if (thumbstickDown)Debug.Log($"[VRPointer:{hand}] 조이스틱 클릭 눌림"); + + Ray ray = new Ray(transform.position, transform.forward); + float hitDist = maxDistance; + + Selectable hit = FindSelectableUnderRay(ray, ref hitDist); + + // 호버 변화 로그 + if (hit != _currentHover) + { + Debug.Log(hit != null + ? $"[VRPointer] HOVER → {hit.gameObject.name}" + : $"[VRPointer] HOVER → (없음)"); + } + + UpdateHoverState(hit); + + // 검지 트리거 또는 A/X 버튼으로 클릭 + if (triggerDown || primaryDown) + { + if (_currentHover != null) + { + string btn = triggerDown ? "검지 트리거" : (isRightHand ? "A" : "X"); + Debug.Log($"[VRPointer:{hand}] CLICK [{btn}] → {_currentHover.gameObject.name}"); + Click(_currentHover); + } + else + { + Debug.Log($"[VRPointer:{hand}] CLICK — 레이 아래 버튼 없음 " + + $"pos={transform.position:F2} fwd={transform.forward:F2}"); + DebugRaycastAttempt(new Ray(transform.position, transform.forward)); + } + } + + DrawLine(hitDist); + } + + private void LogConnectedDevices() + { + var all = new List(); + InputDevices.GetDevices(all); + + if (all.Count == 0) + { + Debug.LogWarning($"[VRPointer] 연결된 XR 디바이스 없음 ({gameObject.name})"); + return; + } + + var chars = InputDeviceCharacteristics.Controller | + (isRightHand ? InputDeviceCharacteristics.Right : InputDeviceCharacteristics.Left); + var matched = new List(); + InputDevices.GetDevicesWithCharacteristics(chars, matched); + + Debug.Log($"[VRPointer] 전체 디바이스 {all.Count}개 | " + + $"{(isRightHand ? "오른손" : "왼손")} 컨트롤러 {matched.Count}개 ({gameObject.name})"); + } + + private void UpdateHoverState(Selectable hit) + { + if (hit == _currentHover) return; + + var es = EventSystem.current; + if (_currentHover != null) + ExecuteEvents.Execute(_currentHover.gameObject, + new PointerEventData(es), ExecuteEvents.pointerExitHandler); + + _currentHover = hit; + + if (_currentHover != null) + ExecuteEvents.Execute(_currentHover.gameObject, + new PointerEventData(es), ExecuteEvents.pointerEnterHandler); + } + + private static void Click(Selectable sel) + { + var es = EventSystem.current; + if (es == null) + { + Debug.LogWarning("[VRPointer] EventSystem.current is null — click 실패"); + return; + } + + var eventData = new PointerEventData(es); + ExecuteEvents.Execute(sel.gameObject, eventData, ExecuteEvents.pointerDownHandler); + ExecuteEvents.Execute(sel.gameObject, eventData, ExecuteEvents.pointerUpHandler); + ExecuteEvents.Execute(sel.gameObject, eventData, ExecuteEvents.pointerClickHandler); + + var btn = sel.GetComponent