문달기
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.XR.Interaction.Toolkit;
|
||||
using UnityEngine.XR.Interaction.Toolkit.Interactables;
|
||||
|
||||
public class ClamBiteDetector : MonoBehaviour
|
||||
@@ -21,22 +23,48 @@ public class ClamBiteDetector : MonoBehaviour
|
||||
[Tooltip("조개가 닫히는 동안 이미 한 번 물렸으면 추가 판정을 막습니다.")]
|
||||
[SerializeField] private bool biteOncePerClose = true;
|
||||
|
||||
[Header("Target Detection")]
|
||||
[Tooltip("손 오브젝트에 붙일 태그입니다. 태그를 안 쓰면 XRHandMarker로도 판정합니다.")]
|
||||
[SerializeField] private string handTag = "PlayerHand";
|
||||
[Tooltip("미션 중 BiteZone Collider를 계속 켜두어 조각이 빠져나갔는지 추적합니다.")]
|
||||
[SerializeField] private bool keepBiteZoneEnabledDuringMission = true;
|
||||
|
||||
[Tooltip("기억의 조각 태그입니다. 단, 조각은 잡힌 상태일 때만 물림 대상으로 봅니다.")]
|
||||
[Header("Fragment Rule")]
|
||||
[Tooltip("기억의 조각을 잡은 뒤 BiteZone을 빠져나가지 못한 상태에서 조개가 닫히면 물림 처리합니다.")]
|
||||
[SerializeField] private bool biteIfGrabbedFragmentDidNotExitBiteZone = true;
|
||||
|
||||
[Tooltip("기억의 조각을 잡고 있을 때만 조각 물림 판정을 합니다.")]
|
||||
[SerializeField] private bool biteFragmentOnlyWhenGrabbed = true;
|
||||
|
||||
[Header("Hand Rule")]
|
||||
[Tooltip("손 콜라이더가 BiteZone 안에 있으면 조개가 닫힐 때 물림 처리합니다.")]
|
||||
[SerializeField] private bool biteHandInsideZone = true;
|
||||
|
||||
[Header("Target Detection")]
|
||||
[SerializeField] private string handTag = "PlayerHand";
|
||||
[SerializeField] private string fragmentTag = "MemoryFragment";
|
||||
|
||||
[Tooltip("기억의 조각은 플레이어가 잡고 있을 때만 물림 판정합니다.")]
|
||||
[SerializeField] private bool biteFragmentOnlyWhenGrabbed = true;
|
||||
[Header("Clear Event")]
|
||||
[Tooltip("기억의 조각을 잡은 상태로 BiteZone 밖으로 빼냈을 때 한 번만 성공 처리합니다.")]
|
||||
[SerializeField] private bool clearOnce = true;
|
||||
|
||||
[Tooltip("성공하면 조개 물림 미션을 끕니다.")]
|
||||
[SerializeField] private bool stopMissionOnClear = true;
|
||||
|
||||
public UnityEvent onMemoryFragmentEscaped;
|
||||
|
||||
[Header("Debug")]
|
||||
[SerializeField] private bool showDebugLog = true;
|
||||
|
||||
private bool missionActive;
|
||||
private bool biteWindowOpen;
|
||||
private bool hasBittenThisClose;
|
||||
|
||||
private bool fragmentGrabbed;
|
||||
private bool fragmentInsideBiteZone;
|
||||
private bool fragmentGrabStartedInsideBiteZone;
|
||||
private bool clearTriggered;
|
||||
|
||||
private XRGrabInteractable memoryGrabInteractable;
|
||||
private Collider[] memoryFragmentColliders;
|
||||
|
||||
private readonly HashSet<Collider> collidersInside = new();
|
||||
|
||||
private void Awake()
|
||||
@@ -59,16 +87,45 @@ private void Awake()
|
||||
if (memoryFragment == null)
|
||||
memoryFragment = FindFirstObjectByType<MemoryFragmentReset>();
|
||||
|
||||
if (memoryFragment != null)
|
||||
{
|
||||
memoryGrabInteractable = memoryFragment.GetComponent<XRGrabInteractable>();
|
||||
memoryFragmentColliders = memoryFragment.GetComponentsInChildren<Collider>(true);
|
||||
}
|
||||
|
||||
missionActive = missionActiveOnStart;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (missionActive && keepBiteZoneEnabledDuringMission)
|
||||
{
|
||||
SetBiteZoneEnabled(true);
|
||||
RefreshInitialOverlapState();
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!missionActive || !fragmentGrabbed || !fragmentGrabStartedInsideBiteZone || clearTriggered)
|
||||
return;
|
||||
|
||||
UpdateFragmentOverlapState();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (clam != null)
|
||||
{
|
||||
clam.onCloseStarted.AddListener(EnableBiteWindow);
|
||||
clam.onClosed.AddListener(DisableBiteWindow);
|
||||
clam.onOpened.AddListener(ResetBiteState);
|
||||
clam.onCloseStarted.AddListener(OnClamCloseStarted);
|
||||
clam.onClosed.AddListener(OnClamClosed);
|
||||
clam.onOpened.AddListener(OnClamOpened);
|
||||
}
|
||||
|
||||
if (memoryGrabInteractable != null)
|
||||
{
|
||||
memoryGrabInteractable.selectEntered.AddListener(OnFragmentGrabbed);
|
||||
memoryGrabInteractable.selectExited.AddListener(OnFragmentReleased);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,9 +133,15 @@ private void OnDisable()
|
||||
{
|
||||
if (clam != null)
|
||||
{
|
||||
clam.onCloseStarted.RemoveListener(EnableBiteWindow);
|
||||
clam.onClosed.RemoveListener(DisableBiteWindow);
|
||||
clam.onOpened.RemoveListener(ResetBiteState);
|
||||
clam.onCloseStarted.RemoveListener(OnClamCloseStarted);
|
||||
clam.onClosed.RemoveListener(OnClamClosed);
|
||||
clam.onOpened.RemoveListener(OnClamOpened);
|
||||
}
|
||||
|
||||
if (memoryGrabInteractable != null)
|
||||
{
|
||||
memoryGrabInteractable.selectEntered.RemoveListener(OnFragmentGrabbed);
|
||||
memoryGrabInteractable.selectExited.RemoveListener(OnFragmentReleased);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,111 +149,225 @@ private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
collidersInside.Add(other);
|
||||
|
||||
if (IsBiteWindowOpen())
|
||||
TryBite(other);
|
||||
if (IsMemoryFragmentCollider(other))
|
||||
{
|
||||
fragmentInsideBiteZone = true;
|
||||
}
|
||||
|
||||
if (biteWindowOpen)
|
||||
{
|
||||
TryBiteByCollider(other);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTriggerStay(Collider other)
|
||||
{
|
||||
collidersInside.Add(other);
|
||||
|
||||
if (IsBiteWindowOpen())
|
||||
TryBite(other);
|
||||
if (IsMemoryFragmentCollider(other))
|
||||
{
|
||||
fragmentInsideBiteZone = true;
|
||||
}
|
||||
|
||||
if (biteWindowOpen)
|
||||
{
|
||||
TryBiteByCollider(other);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTriggerExit(Collider other)
|
||||
{
|
||||
collidersInside.Remove(other);
|
||||
|
||||
if (IsMemoryFragmentCollider(other))
|
||||
{
|
||||
UpdateFragmentOverlapState();
|
||||
}
|
||||
}
|
||||
|
||||
public void StartBiteMission()
|
||||
{
|
||||
missionActive = true;
|
||||
biteWindowOpen = false;
|
||||
hasBittenThisClose = false;
|
||||
clearTriggered = false;
|
||||
|
||||
collidersInside.Clear();
|
||||
|
||||
SetBiteZoneEnabled(true);
|
||||
RefreshInitialOverlapState();
|
||||
|
||||
if (showDebugLog)
|
||||
Debug.Log("[ClamBiteDetector] 조개 미션 시작. 물림 판정 활성 준비.", this);
|
||||
Debug.Log("[ClamBiteDetector] 조개 미션 시작. BiteZone 추적 ON.", this);
|
||||
}
|
||||
|
||||
public void StopBiteMission()
|
||||
{
|
||||
missionActive = false;
|
||||
biteWindowOpen = false;
|
||||
hasBittenThisClose = false;
|
||||
|
||||
fragmentGrabbed = false;
|
||||
fragmentInsideBiteZone = false;
|
||||
fragmentGrabStartedInsideBiteZone = false;
|
||||
|
||||
collidersInside.Clear();
|
||||
|
||||
if (biteZoneCollider != null)
|
||||
biteZoneCollider.enabled = false;
|
||||
SetBiteZoneEnabled(false);
|
||||
|
||||
if (showDebugLog)
|
||||
Debug.Log("[ClamBiteDetector] 조개 미션 정지. 물림 판정 비활성.", this);
|
||||
Debug.Log("[ClamBiteDetector] 조개 미션 정지. BiteZone 추적 OFF.", this);
|
||||
}
|
||||
|
||||
private void EnableBiteWindow()
|
||||
private void OnClamOpened()
|
||||
{
|
||||
hasBittenThisClose = false;
|
||||
biteWindowOpen = false;
|
||||
|
||||
if (!missionActive)
|
||||
return;
|
||||
|
||||
SetBiteZoneEnabled(true);
|
||||
RefreshInitialOverlapState();
|
||||
}
|
||||
|
||||
private void OnClamCloseStarted()
|
||||
{
|
||||
if (!missionActive)
|
||||
return;
|
||||
|
||||
hasBittenThisClose = false;
|
||||
biteWindowOpen = true;
|
||||
|
||||
if (biteZoneCollider != null)
|
||||
biteZoneCollider.enabled = true;
|
||||
SetBiteZoneEnabled(true);
|
||||
RefreshInitialOverlapState();
|
||||
|
||||
if (showDebugLog)
|
||||
Debug.Log("[ClamBiteDetector] 조개 물림 판정 ON", this);
|
||||
Debug.Log("[ClamBiteDetector] 조개 닫힘 시작. 물림 판정 체크.", this);
|
||||
|
||||
foreach (Collider col in collidersInside)
|
||||
TryBiteGrabbedFragmentIfStillInside();
|
||||
TryBiteCurrentHandsInside();
|
||||
}
|
||||
|
||||
private void OnClamClosed()
|
||||
{
|
||||
biteWindowOpen = false;
|
||||
hasBittenThisClose = false;
|
||||
collidersInside.Clear();
|
||||
|
||||
if (!missionActive)
|
||||
return;
|
||||
|
||||
if (keepBiteZoneEnabledDuringMission)
|
||||
{
|
||||
if (col != null)
|
||||
TryBite(col);
|
||||
SetBiteZoneEnabled(true);
|
||||
RefreshInitialOverlapState();
|
||||
}
|
||||
else
|
||||
{
|
||||
SetBiteZoneEnabled(false);
|
||||
}
|
||||
|
||||
if (showDebugLog)
|
||||
Debug.Log("[ClamBiteDetector] 조개 닫힘 완료. 물림 판정 종료.", this);
|
||||
}
|
||||
|
||||
private void DisableBiteWindow()
|
||||
private void OnFragmentGrabbed(SelectEnterEventArgs args)
|
||||
{
|
||||
if (biteZoneCollider != null)
|
||||
biteZoneCollider.enabled = false;
|
||||
fragmentGrabbed = true;
|
||||
fragmentInsideBiteZone = IsFragmentOverlappingBiteZone();
|
||||
fragmentGrabStartedInsideBiteZone = fragmentInsideBiteZone;
|
||||
|
||||
collidersInside.Clear();
|
||||
RefreshInitialOverlapState();
|
||||
|
||||
if (showDebugLog && missionActive)
|
||||
Debug.Log("[ClamBiteDetector] 조개 물림 판정 OFF", this);
|
||||
if (showDebugLog)
|
||||
Debug.Log("[ClamBiteDetector] 기억의 조각을 잡았습니다.", this);
|
||||
}
|
||||
|
||||
private void ResetBiteState()
|
||||
private void OnFragmentReleased(SelectExitEventArgs args)
|
||||
{
|
||||
hasBittenThisClose = false;
|
||||
fragmentGrabbed = false;
|
||||
fragmentGrabStartedInsideBiteZone = false;
|
||||
|
||||
if (showDebugLog)
|
||||
Debug.Log("[ClamBiteDetector] 기억의 조각을 놓았습니다.", this);
|
||||
}
|
||||
|
||||
private bool IsBiteWindowOpen()
|
||||
private void TryBiteGrabbedFragmentIfStillInside()
|
||||
{
|
||||
if (!missionActive)
|
||||
return false;
|
||||
|
||||
if (biteZoneCollider == null)
|
||||
return false;
|
||||
|
||||
return biteZoneCollider.enabled;
|
||||
}
|
||||
|
||||
private void TryBite(Collider other)
|
||||
{
|
||||
if (other == null)
|
||||
if (!biteIfGrabbedFragmentDidNotExitBiteZone)
|
||||
return;
|
||||
|
||||
if (!missionActive)
|
||||
if (memoryFragment == null)
|
||||
return;
|
||||
|
||||
if (biteOncePerClose && hasBittenThisClose)
|
||||
return;
|
||||
|
||||
bool isHand = IsHandCollider(other);
|
||||
bool isGrabbedFragment = IsGrabbedMemoryFragment(other);
|
||||
bool isSelected = IsFragmentGrabbed();
|
||||
|
||||
if (!isHand && !isGrabbedFragment)
|
||||
if (biteFragmentOnlyWhenGrabbed && !isSelected)
|
||||
return;
|
||||
|
||||
bool shouldBite = isSelected && IsFragmentOverlappingBiteZone();
|
||||
|
||||
if (!shouldBite)
|
||||
return;
|
||||
|
||||
BiteNow("기억의 조각을 잡은 뒤 BiteZone 밖으로 빼내지 못함");
|
||||
}
|
||||
|
||||
private void TryBiteCurrentHandsInside()
|
||||
{
|
||||
if (!biteHandInsideZone)
|
||||
return;
|
||||
|
||||
foreach (Collider col in collidersInside)
|
||||
{
|
||||
if (col == null)
|
||||
continue;
|
||||
|
||||
if (IsHandCollider(col))
|
||||
{
|
||||
BiteNow("손이 BiteZone 안에 있음");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TryBiteByCollider(Collider other)
|
||||
{
|
||||
if (!missionActive)
|
||||
return;
|
||||
|
||||
if (!biteWindowOpen)
|
||||
return;
|
||||
|
||||
if (biteOncePerClose && hasBittenThisClose)
|
||||
return;
|
||||
|
||||
if (biteHandInsideZone && IsHandCollider(other))
|
||||
{
|
||||
BiteNow("손이 BiteZone 안에 있음");
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsMemoryFragmentCollider(other))
|
||||
{
|
||||
if (memoryGrabInteractable != null && memoryGrabInteractable.isSelected)
|
||||
{
|
||||
BiteNow("기억의 조각이 BiteZone 안에 있음");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void BiteNow(string reason)
|
||||
{
|
||||
if (biteOncePerClose && hasBittenThisClose)
|
||||
return;
|
||||
|
||||
hasBittenThisClose = true;
|
||||
biteWindowOpen = false;
|
||||
|
||||
if (health != null)
|
||||
health.TakeDamage(biteDamage);
|
||||
@@ -198,45 +375,153 @@ private void TryBite(Collider other)
|
||||
if (memoryFragment != null)
|
||||
memoryFragment.ResetFragment();
|
||||
|
||||
fragmentGrabbed = false;
|
||||
fragmentInsideBiteZone = false;
|
||||
fragmentGrabStartedInsideBiteZone = false;
|
||||
|
||||
if (showDebugLog)
|
||||
{
|
||||
Debug.Log($"[ClamBiteDetector] 조개에게 물림. 데미지 {biteDamage}, 기억의 조각 리셋", this);
|
||||
Debug.Log($"[ClamBiteDetector] 조개에게 물림. 이유: {reason}. 데미지 {biteDamage}, 기억의 조각 리셋", this);
|
||||
}
|
||||
}
|
||||
|
||||
private void TriggerMemoryFragmentEscaped()
|
||||
{
|
||||
if (clearOnce && clearTriggered)
|
||||
return;
|
||||
|
||||
clearTriggered = true;
|
||||
|
||||
if (showDebugLog)
|
||||
Debug.Log("[ClamBiteDetector] 기억의 조각 꺼내기 성공. 클리어 이벤트 실행.", this);
|
||||
|
||||
onMemoryFragmentEscaped?.Invoke();
|
||||
|
||||
if (stopMissionOnClear)
|
||||
{
|
||||
StopBiteMission();
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshInitialOverlapState()
|
||||
{
|
||||
if (biteZoneCollider == null || !biteZoneCollider.enabled)
|
||||
return;
|
||||
|
||||
collidersInside.Clear();
|
||||
fragmentInsideBiteZone = false;
|
||||
|
||||
Bounds bounds = biteZoneCollider.bounds;
|
||||
|
||||
Collider[] hits = Physics.OverlapBox(
|
||||
bounds.center,
|
||||
bounds.extents,
|
||||
biteZoneCollider.transform.rotation,
|
||||
~0,
|
||||
QueryTriggerInteraction.Collide
|
||||
);
|
||||
|
||||
foreach (Collider hit in hits)
|
||||
{
|
||||
if (hit == null)
|
||||
continue;
|
||||
|
||||
collidersInside.Add(hit);
|
||||
|
||||
if (IsMemoryFragmentCollider(hit))
|
||||
fragmentInsideBiteZone = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateFragmentOverlapState()
|
||||
{
|
||||
bool wasInside = fragmentInsideBiteZone;
|
||||
fragmentInsideBiteZone = IsFragmentOverlappingBiteZone();
|
||||
|
||||
if (!wasInside || fragmentInsideBiteZone)
|
||||
return;
|
||||
|
||||
if (!fragmentGrabbed || !missionActive || !fragmentGrabStartedInsideBiteZone)
|
||||
return;
|
||||
|
||||
if (showDebugLog)
|
||||
Debug.Log("[ClamBiteDetector] 기억의 조각이 BiteZone 밖으로 빠져나갔습니다.", this);
|
||||
|
||||
TriggerMemoryFragmentEscaped();
|
||||
}
|
||||
|
||||
private bool IsFragmentOverlappingBiteZone()
|
||||
{
|
||||
if (biteZoneCollider == null || memoryFragment == null)
|
||||
return false;
|
||||
|
||||
if (memoryFragmentColliders == null || memoryFragmentColliders.Length == 0)
|
||||
memoryFragmentColliders = memoryFragment.GetComponentsInChildren<Collider>(true);
|
||||
|
||||
foreach (Collider fragmentCollider in memoryFragmentColliders)
|
||||
{
|
||||
if (fragmentCollider == null || fragmentCollider == biteZoneCollider)
|
||||
continue;
|
||||
|
||||
if (Physics.ComputePenetration(
|
||||
biteZoneCollider,
|
||||
biteZoneCollider.transform.position,
|
||||
biteZoneCollider.transform.rotation,
|
||||
fragmentCollider,
|
||||
fragmentCollider.transform.position,
|
||||
fragmentCollider.transform.rotation,
|
||||
out _,
|
||||
out _))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (biteZoneCollider.bounds.Intersects(fragmentCollider.bounds))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsFragmentGrabbed()
|
||||
{
|
||||
if (memoryGrabInteractable != null)
|
||||
return memoryGrabInteractable.isSelected;
|
||||
|
||||
return fragmentGrabbed;
|
||||
}
|
||||
|
||||
private void SetBiteZoneEnabled(bool enabled)
|
||||
{
|
||||
if (biteZoneCollider == null)
|
||||
return;
|
||||
|
||||
biteZoneCollider.enabled = enabled;
|
||||
}
|
||||
|
||||
private bool IsMemoryFragmentCollider(Collider other)
|
||||
{
|
||||
if (other == null)
|
||||
return false;
|
||||
|
||||
if (other.CompareTag(fragmentTag))
|
||||
return true;
|
||||
|
||||
MemoryFragmentReset fragment = other.GetComponentInParent<MemoryFragmentReset>();
|
||||
return fragment != null;
|
||||
}
|
||||
|
||||
private bool IsHandCollider(Collider other)
|
||||
{
|
||||
if (other == null)
|
||||
return false;
|
||||
|
||||
if (other.CompareTag(handTag))
|
||||
return true;
|
||||
|
||||
XRHandMarker marker = other.GetComponentInParent<XRHandMarker>();
|
||||
|
||||
return marker != null;
|
||||
}
|
||||
|
||||
private bool IsGrabbedMemoryFragment(Collider other)
|
||||
{
|
||||
MemoryFragmentReset fragment = other.GetComponentInParent<MemoryFragmentReset>();
|
||||
|
||||
if (fragment == null)
|
||||
{
|
||||
if (!other.CompareTag(fragmentTag))
|
||||
return false;
|
||||
|
||||
fragment = memoryFragment;
|
||||
}
|
||||
|
||||
if (fragment == null)
|
||||
return false;
|
||||
|
||||
XRGrabInteractable grab = fragment.GetComponent<XRGrabInteractable>();
|
||||
|
||||
if (grab == null)
|
||||
return !biteFragmentOnlyWhenGrabbed;
|
||||
|
||||
if (biteFragmentOnlyWhenGrabbed)
|
||||
return grab.isSelected;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user