using UnityEngine; using UnityEngine.Events; using UnityEngine.XR; /// /// VR 플레이어의 몸, 손, 컨트롤러 Collider에 붙여서 /// "이 Collider는 인벤토리 아이템을 획득할 수 있다"고 표시하는 컴포넌트입니다. /// 기능: 획득 허용/차단, InventoryManager Override, 컨트롤러 햅틱, 빠른 아이템 사용 메서드. /// [DisallowMultipleComponent] public class PlayerInventoryCollector : MonoBehaviour { [Header("Collection")] [SerializeField] private bool canCollectItems = true; [SerializeField] private InventoryManager inventoryManagerOverride; [Tooltip("확인창이 필요한 아이템을 빠른 사용으로 눌렀을 때 InventoryUI의 확인창을 사용합니다.")] [SerializeField] private bool respectInventoryUseConfirmation = true; [SerializeField] private InventoryUI inventoryUIForConfirmedUses; [Header("Haptic")] [SerializeField] private bool enableHaptic = true; [SerializeField] private XRNode hapticNode = XRNode.RightHand; [Range(0f, 1f)] [SerializeField] private float defaultHapticAmplitude = 0.35f; [SerializeField] private float defaultHapticDuration = 0.08f; [Header("Quick Use - Compass")] [SerializeField] private bool consumeCompassOnQuickUse = false; [SerializeField] private string compassMissingMessage = "낡은 나침반이 필요합니다."; [SerializeField] private string compassUsedMessage = "나침반이 방향을 가리킵니다."; public UnityEvent onCompassUsed; public UnityEvent onCompassMissing; [Header("Quick Use - Fish")] [SerializeField] private int fishUseAmount = 1; [SerializeField] private string fishMissingMessage = "생선이 부족합니다."; [SerializeField] private string fishUsedMessage = "생선을 사용했습니다."; public UnityEvent onFishUsed; public UnityEvent onFishMissing; [Header("Quick Use - Trash")] [SerializeField] private int trashUseAmount = 1; [SerializeField] private string trashMissingMessage = "쓰레기가 부족합니다."; [SerializeField] private string trashUsedMessage = "쓰레기를 사용했습니다."; public UnityEvent onTrashUsed; public UnityEvent onTrashMissing; [Header("Quick Use - Bottle")] [SerializeField] private bool consumeBottleOnQuickUse = false; [SerializeField] private string bottleMissingMessage = "마법병이 필요합니다."; [SerializeField] private string bottleUsedMessage = "마법병을 사용했습니다."; public UnityEvent onBottleUsed; public UnityEvent onBottleMissing; [Header("Quick Use - Memory Piece")] [SerializeField] private string memoryPieceMissingMessage = "기억의 조각이 없습니다."; [SerializeField] private string memoryPieceUsedMessage = "기억의 조각을 확인했습니다."; public UnityEvent onMemoryPieceUsed; public UnityEvent onMemoryPieceMissing; public bool CanCollectItems => canCollectItems; public InventoryManager InventoryManagerOverride => inventoryManagerOverride; public void SetCanCollectItems(bool canCollect) { canCollectItems = canCollect; } public void SetInventoryManagerOverride(InventoryManager manager) { inventoryManagerOverride = manager; } public void PlayDefaultHaptic() { PlayHaptic(defaultHapticAmplitude, defaultHapticDuration); } public void PlayHaptic(float amplitude, float duration) { if (!enableHaptic) return; InputDevice device = InputDevices.GetDeviceAtXRNode(hapticNode); if (!device.isValid) return; HapticCapabilities capabilities; if (device.TryGetHapticCapabilities(out capabilities) && capabilities.supportsImpulse) device.SendHapticImpulse(0, Mathf.Clamp01(amplitude), Mathf.Max(0f, duration)); } public void UseCompass() { InventoryManager manager = ResolveManager(); if (manager == null) return; bool result = manager.TryUseItem(InventoryItemType.OldCompass, 1, consumeCompassOnQuickUse, false, true); if (result) { manager.RequestMessage(compassUsedMessage); PlayDefaultHaptic(); onCompassUsed?.Invoke(); } else { manager.RequestMessage(compassMissingMessage); onCompassMissing?.Invoke(); } } public void UseFish() { TryUseQuickItem(InventoryItemType.Fish, fishUseAmount, true, fishUsedMessage, fishMissingMessage, onFishUsed, onFishMissing); } public void UseTrash() { TryUseQuickItem(InventoryItemType.Trash, trashUseAmount, true, trashUsedMessage, trashMissingMessage, onTrashUsed, onTrashMissing); } public void UseBottle() { TryUseQuickItem(InventoryItemType.Bottle, 1, consumeBottleOnQuickUse, bottleUsedMessage, bottleMissingMessage, onBottleUsed, onBottleMissing); } public void UseMemoryPiece() { TryUseQuickItem(InventoryItemType.MemoryPiece, 1, false, memoryPieceUsedMessage, memoryPieceMissingMessage, onMemoryPieceUsed, onMemoryPieceMissing); } public bool TryUseQuickItem(InventoryItemType itemType, int amount, bool consume, string successMessage, string missingMessage, UnityEvent onSuccess, UnityEvent onFail) { InventoryManager manager = ResolveManager(); if (manager == null) return false; amount = Mathf.Max(1, amount); if (respectInventoryUseConfirmation && manager.RequiresUseConfirmation(itemType)) { InventoryUI ui = ResolveInventoryUI(); if (ui != null) { ui.RequestUseItem(itemType, amount); return true; } } bool result = manager.TryUseItem(itemType, amount, consume, false, true); if (result) { if (!string.IsNullOrWhiteSpace(successMessage)) manager.RequestMessage(successMessage); PlayDefaultHaptic(); onSuccess?.Invoke(); return true; } if (!string.IsNullOrWhiteSpace(missingMessage)) manager.RequestMessage(missingMessage); else manager.RequestMessage(manager.GetInsufficientMessage(itemType, amount)); onFail?.Invoke(); return false; } private InventoryUI ResolveInventoryUI() { if (inventoryUIForConfirmedUses != null) return inventoryUIForConfirmedUses; inventoryUIForConfirmedUses = FindFirstObjectByType(); return inventoryUIForConfirmedUses; } private InventoryManager ResolveManager() { if (inventoryManagerOverride != null) return inventoryManagerOverride; if (InventoryManager.Instance != null) return InventoryManager.Instance; return FindFirstObjectByType(); } #if UNITY_EDITOR private void OnValidate() { fishUseAmount = Mathf.Max(1, fishUseAmount); trashUseAmount = Mathf.Max(1, trashUseAmount); defaultHapticDuration = Mathf.Max(0f, defaultHapticDuration); } #endif }