using System.Collections.Generic; using UnityEngine; using UnityEngine.XR.Interaction.Toolkit.Interactables; public class ClamBiteDetector : MonoBehaviour { [Header("References")] [SerializeField] private ClamOpenClose clam; [SerializeField] private RaftHealth health; [SerializeField] private MemoryFragmentReset memoryFragment; [Header("Bite Damage")] [SerializeField] private int biteDamage = 20; [Header("Bite Zone")] [SerializeField] private Collider biteZoneCollider; [Tooltip("조개 미션이 시작되었을 때만 물림 판정을 합니다.")] [SerializeField] private bool missionActiveOnStart = false; [Tooltip("조개가 닫히는 동안 이미 한 번 물렸으면 추가 판정을 막습니다.")] [SerializeField] private bool biteOncePerClose = true; [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() { if (biteZoneCollider == null) biteZoneCollider = GetComponent(); if (biteZoneCollider != null) { biteZoneCollider.isTrigger = true; biteZoneCollider.enabled = false; } if (clam == null) clam = GetComponentInParent(); if (health == null) health = FindFirstObjectByType(); if (memoryFragment == null) memoryFragment = FindFirstObjectByType(); missionActive = missionActiveOnStart; } private void OnEnable() { if (clam != null) { clam.onCloseStarted.AddListener(EnableBiteWindow); clam.onClosed.AddListener(DisableBiteWindow); clam.onOpened.AddListener(ResetBiteState); } } private void OnDisable() { if (clam != null) { clam.onCloseStarted.RemoveListener(EnableBiteWindow); clam.onClosed.RemoveListener(DisableBiteWindow); clam.onOpened.RemoveListener(ResetBiteState); } } private void OnTriggerEnter(Collider other) { collidersInside.Add(other); if (IsBiteWindowOpen()) TryBite(other); } private void OnTriggerStay(Collider other) { collidersInside.Add(other); if (IsBiteWindowOpen()) TryBite(other); } 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) biteZoneCollider.enabled = true; if (showDebugLog) Debug.Log("[ClamBiteDetector] 조개 물림 판정 ON", this); foreach (Collider col in collidersInside) { if (col != null) TryBite(col); } } private void DisableBiteWindow() { if (biteZoneCollider != null) biteZoneCollider.enabled = false; collidersInside.Clear(); if (showDebugLog && missionActive) Debug.Log("[ClamBiteDetector] 조개 물림 판정 OFF", this); } private void ResetBiteState() { hasBittenThisClose = false; } private bool IsBiteWindowOpen() { if (!missionActive) return false; if (biteZoneCollider == null) return false; return biteZoneCollider.enabled; } private void TryBite(Collider other) { if (other == null) return; if (!missionActive) return; if (biteOncePerClose && hasBittenThisClose) return; bool isHand = IsHandCollider(other); bool isGrabbedFragment = IsGrabbedMemoryFragment(other); if (!isHand && !isGrabbedFragment) return; hasBittenThisClose = true; if (health != null) health.TakeDamage(biteDamage); if (memoryFragment != null) memoryFragment.ResetFragment(); if (showDebugLog) { 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; } }