diff --git a/Assets/01_Scenes/Cave_Test_2.unity b/Assets/01_Scenes/Cave_Test_2.unity index 7b65bf44..9e2bc848 100644 --- a/Assets/01_Scenes/Cave_Test_2.unity +++ b/Assets/01_Scenes/Cave_Test_2.unity @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:26dd174996a95d037bc40437d88cbe2f5a55a2532d6550fed72a51dd0674bddc -size 781114 +oid sha256:7ee90cb504baf5bfa40c4fcf5713a6c96739b4ad9c4a4c8f6bdf4e76f586a7bc +size 921497 diff --git a/Assets/02_Scripts/Cave/ClamBiteDetector.cs b/Assets/02_Scripts/Cave/ClamBiteDetector.cs index f68f7531..82f51618 100644 --- a/Assets/02_Scripts/Cave/ClamBiteDetector.cs +++ b/Assets/02_Scripts/Cave/ClamBiteDetector.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using UnityEngine; +using UnityEngine.XR.Interaction.Toolkit.Interactables; public class ClamBiteDetector : MonoBehaviour { @@ -14,17 +15,28 @@ public class ClamBiteDetector : MonoBehaviour [Header("Bite Zone")] [SerializeField] private Collider biteZoneCollider; + [Tooltip("조개 미션이 시작되었을 때만 물림 판정을 합니다.")] + [SerializeField] private bool missionActiveOnStart = false; + [Tooltip("조개가 닫히는 동안 이미 한 번 물렸으면 추가 판정을 막습니다.")] [SerializeField] private bool biteOncePerClose = true; - [Header("Target Tags")] + [Header("Target Detection")] + [Tooltip("손 오브젝트에 붙일 태그입니다. 태그를 안 쓰면 XRHandMarker로도 판정합니다.")] [SerializeField] private string handTag = "PlayerHand"; + + [Tooltip("기억의 조각 태그입니다. 단, 조각은 잡힌 상태일 때만 물림 대상으로 봅니다.")] [SerializeField] private string fragmentTag = "MemoryFragment"; + [Tooltip("기억의 조각은 플레이어가 잡고 있을 때만 물림 판정합니다.")] + [SerializeField] private bool biteFragmentOnlyWhenGrabbed = true; + [Header("Debug")] [SerializeField] private bool showDebugLog = true; + private bool missionActive; private bool hasBittenThisClose; + private readonly HashSet collidersInside = new(); private void Awake() @@ -46,6 +58,8 @@ private void Awake() if (memoryFragment == null) memoryFragment = FindFirstObjectByType(); + + missionActive = missionActiveOnStart; } private void OnEnable() @@ -89,8 +103,34 @@ private void OnTriggerExit(Collider other) collidersInside.Remove(other); } + public void StartBiteMission() + { + missionActive = true; + hasBittenThisClose = false; + collidersInside.Clear(); + + if (showDebugLog) + Debug.Log("[ClamBiteDetector] 조개 미션 시작. 물림 판정 활성 준비.", this); + } + + public void StopBiteMission() + { + missionActive = false; + hasBittenThisClose = false; + collidersInside.Clear(); + + if (biteZoneCollider != null) + biteZoneCollider.enabled = false; + + if (showDebugLog) + Debug.Log("[ClamBiteDetector] 조개 미션 정지. 물림 판정 비활성.", this); + } + private void EnableBiteWindow() { + if (!missionActive) + return; + hasBittenThisClose = false; if (biteZoneCollider != null) @@ -113,7 +153,7 @@ private void DisableBiteWindow() collidersInside.Clear(); - if (showDebugLog) + if (showDebugLog && missionActive) Debug.Log("[ClamBiteDetector] 조개 물림 판정 OFF", this); } @@ -124,6 +164,9 @@ private void ResetBiteState() private bool IsBiteWindowOpen() { + if (!missionActive) + return false; + if (biteZoneCollider == null) return false; @@ -135,13 +178,16 @@ private void TryBite(Collider other) if (other == null) return; + if (!missionActive) + return; + if (biteOncePerClose && hasBittenThisClose) return; - bool isHand = other.CompareTag(handTag) || other.GetComponentInParent() != null; - bool isFragment = other.CompareTag(fragmentTag) || other.GetComponentInParent() != null; + bool isHand = IsHandCollider(other); + bool isGrabbedFragment = IsGrabbedMemoryFragment(other); - if (!isHand && !isFragment) + if (!isHand && !isGrabbedFragment) return; hasBittenThisClose = true; @@ -157,4 +203,40 @@ private void TryBite(Collider other) Debug.Log($"[ClamBiteDetector] 조개에게 물림. 데미지 {biteDamage}, 기억의 조각 리셋", this); } } + + private bool IsHandCollider(Collider other) + { + if (other.CompareTag(handTag)) + return true; + + XRHandMarker marker = other.GetComponentInParent(); + + return marker != null; + } + + private bool IsGrabbedMemoryFragment(Collider other) + { + MemoryFragmentReset fragment = other.GetComponentInParent(); + + if (fragment == null) + { + if (!other.CompareTag(fragmentTag)) + return false; + + fragment = memoryFragment; + } + + if (fragment == null) + return false; + + XRGrabInteractable grab = fragment.GetComponent(); + + if (grab == null) + return !biteFragmentOnlyWhenGrabbed; + + if (biteFragmentOnlyWhenGrabbed) + return grab.isSelected; + + return true; + } } \ No newline at end of file diff --git a/Assets/02_Scripts/Cave/RaftRestartManager.cs b/Assets/02_Scripts/Cave/RaftRestartManager.cs new file mode 100644 index 00000000..2cb7bddb --- /dev/null +++ b/Assets/02_Scripts/Cave/RaftRestartManager.cs @@ -0,0 +1,97 @@ +using System.Collections; +using UnityEngine; +using UnityEngine.SceneManagement; + +public class RaftRestartManager : MonoBehaviour +{ + [Header("References")] + [SerializeField] private RaftHealth raftHealth; + + [Header("Restart")] + [SerializeField] private bool restartAutomatically = false; + [SerializeField] private float autoRestartDelay = 2.0f; + + [Tooltip("키보드 테스트용입니다. VR 버튼 리스타트는 나중에 연결해도 됩니다.")] + [SerializeField] private KeyCode restartKey = KeyCode.R; + + [Header("Debug")] + [SerializeField] private bool showDebugLog = true; + + private bool waitingForRestart; + private Coroutine restartRoutine; + + private void Awake() + { + if (raftHealth == null) + raftHealth = FindFirstObjectByType(); + } + + private void OnEnable() + { + if (raftHealth != null) + { + raftHealth.onDead.AddListener(OnRaftDead); + } + } + + private void OnDisable() + { + if (raftHealth != null) + { + raftHealth.onDead.RemoveListener(OnRaftDead); + } + } + + private void Update() + { + if (!waitingForRestart) + return; + +#if ENABLE_LEGACY_INPUT_MANAGER + if (Input.GetKeyDown(restartKey)) + { + RestartNow(); + } +#endif + } + + public void OnRaftDead() + { + if (waitingForRestart) + return; + + waitingForRestart = true; + + if (showDebugLog) + { + Debug.Log("[RaftRestartManager] 체력 0. 리스타트 대기 상태입니다. R 키 또는 RestartNow 호출로 재시작합니다."); + } + + if (restartAutomatically) + { + if (restartRoutine != null) + StopCoroutine(restartRoutine); + + restartRoutine = StartCoroutine(AutoRestartRoutine()); + } + } + + private IEnumerator AutoRestartRoutine() + { + yield return new WaitForSeconds(autoRestartDelay); + RestartNow(); + } + + public void RestartNow() + { + if (showDebugLog) + { + Debug.Log("[RaftRestartManager] 현재 씬을 다시 시작합니다."); + } + + Time.timeScale = 1f; + + Scene currentScene = SceneManager.GetActiveScene(); + SceneManager.LoadScene(currentScene.buildIndex); + } +} \ No newline at end of file diff --git a/Assets/02_Scripts/Cave/RaftRestartManager.cs.meta b/Assets/02_Scripts/Cave/RaftRestartManager.cs.meta new file mode 100644 index 00000000..24bf56ba --- /dev/null +++ b/Assets/02_Scripts/Cave/RaftRestartManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 68a1b671a1769224bba0e6f62386a131 \ No newline at end of file diff --git a/Assets/XRI Starter Kit/Assets/Official Unity Assets/Materials/Flashlight Lens.mat b/Assets/XRI Starter Kit/Assets/Official Unity Assets/Materials/Flashlight Lens.mat index ade2f18c..4fa5fb33 100644 --- a/Assets/XRI Starter Kit/Assets/Official Unity Assets/Materials/Flashlight Lens.mat +++ b/Assets/XRI Starter Kit/Assets/Official Unity Assets/Materials/Flashlight Lens.mat @@ -14,9 +14,6 @@ Material: m_ValidKeywords: - _EMISSION - _ENVIRONMENTREFLECTIONS_OFF - - _METALLICSPECGLOSSMAP - - _NORMALMAP - - _OCCLUSIONMAP m_InvalidKeywords: - _GLOSSYREFLECTIONS_OFF - _METALLICGLOSSMAP @@ -57,7 +54,7 @@ Material: m_Scale: {x: 1, y: 1} m_Offset: {x: 0, y: 0} - _MainTex: - m_Texture: {fileID: 2800000, guid: d24d9daec71459c4590bcf2a7dffd1fe, type: 3} + m_Texture: {fileID: 0} m_Scale: {x: 1, y: 1} m_Offset: {x: 0, y: 0} - _MetallicGlossMap: