diff --git a/.gitignore b/.gitignore index 79e8d45..7c62d28 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ Assets/_Recovery Assets/_Recovery.meta GameData/Backups .claude/settings.local.json +AGENTS.md diff --git a/Assets/Prefabs/Player/Player.prefab b/Assets/Prefabs/Player/Player.prefab index 08ccf4e..6048f45 100644 --- a/Assets/Prefabs/Player/Player.prefab +++ b/Assets/Prefabs/Player/Player.prefab @@ -59,7 +59,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.NetworkObject - GlobalObjectIdHash: 4211758632 + GlobalObjectIdHash: 1360081626 InScenePlacedSourceGlobalObjectIdHash: 4211758632 DeferredDespawnTick: 0 Ownership: 1 @@ -714,6 +714,18 @@ PrefabInstance: serializedVersion: 3 m_TransformParent: {fileID: 1710962577221812646} m_Modifications: + - target: {fileID: 1325989084779099817, guid: 274613c415998a647a86a5e09950ce41, type: 3} + propertyPath: m_Pivot.x + value: 0.5 + objectReference: {fileID: 0} + - target: {fileID: 1325989084779099817, guid: 274613c415998a647a86a5e09950ce41, type: 3} + propertyPath: m_Pivot.y + value: 0.5 + objectReference: {fileID: 0} + - target: {fileID: 1325989084779099817, guid: 274613c415998a647a86a5e09950ce41, type: 3} + propertyPath: m_AnchorMax.x + value: 0 + objectReference: {fileID: 0} - target: {fileID: 1325989084779099817, guid: 274613c415998a647a86a5e09950ce41, type: 3} propertyPath: m_AnchorMax.y value: 0 @@ -724,7 +736,11 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 1325989084779099817, guid: 274613c415998a647a86a5e09950ce41, type: 3} propertyPath: m_SizeDelta.x - value: 4 + value: 500 + objectReference: {fileID: 0} + - target: {fileID: 1325989084779099817, guid: 274613c415998a647a86a5e09950ce41, type: 3} + propertyPath: m_SizeDelta.y + value: 50 objectReference: {fileID: 0} - target: {fileID: 1325989084779099817, guid: 274613c415998a647a86a5e09950ce41, type: 3} propertyPath: m_AnchoredPosition.x @@ -834,6 +850,10 @@ PrefabInstance: propertyPath: m_Name value: ModalPanel objectReference: {fileID: 0} + - target: {fileID: 7839034454177399683, guid: 274613c415998a647a86a5e09950ce41, type: 3} + propertyPath: m_fontStyle + value: 1 + objectReference: {fileID: 0} m_RemovedComponents: [] m_RemovedGameObjects: [] m_AddedGameObjects: [] @@ -878,6 +898,8 @@ MonoBehaviour: modalPanel: {fileID: 4694931302848755623} keyText: {fileID: 4193449683656256336} interactText: {fileID: 8795452566802714605} + unavailablePanel: {fileID: 4694931302848755623} + unavailableText: {fileID: 8795452566802714605} defaultKey: E --- !u!114 &8795452566802714605 stripped MonoBehaviour: diff --git a/Assets/Scripts/InteractableModal.cs b/Assets/Scripts/InteractableModal.cs index de9374f..15a78ac 100644 --- a/Assets/Scripts/InteractableModal.cs +++ b/Assets/Scripts/InteractableModal.cs @@ -9,6 +9,8 @@ namespace Northbound [SerializeField] private GameObject modalPanel; [SerializeField] private TextMeshProUGUI keyText; [SerializeField] private TextMeshProUGUI interactText; + [SerializeField] private GameObject unavailablePanel; + [SerializeField] private TextMeshProUGUI unavailableText; [Header("Settings")] [SerializeField] private string defaultKey = "E"; @@ -27,6 +29,11 @@ namespace Northbound { modalPanel.SetActive(false); } + + if (unavailablePanel != null) + { + unavailablePanel.SetActive(false); + } } public void ShowModal(IInteractable interactable) @@ -54,6 +61,11 @@ namespace Northbound { modalPanel.SetActive(false); } + + if (unavailablePanel != null) + { + unavailablePanel.SetActive(false); + } } public void UpdateModalContent() @@ -86,5 +98,48 @@ namespace Northbound return prompt; } + + /// + /// 상호작용 불가능 메시지를 표시합니다. + /// + /// 상호작용 불가능한 대상 + public void ShowUnavailableMessage(IInteractable interactable) + { + if (interactable == null) + { + HideModal(); + return; + } + + if (unavailablePanel != null) + { + // unavailablePanel이 할당된 경우 전용 패널 사용 + if (modalPanel != null) + { + modalPanel.SetActive(false); + } + + unavailablePanel.SetActive(true); + + if (unavailableText != null) + { + string prompt = interactable.GetInteractionPrompt(); + unavailableText.text = ExtractInteractText(prompt); + } + } + else if (modalPanel != null && interactText != null) + { + // unavailablePanel이 없는 경우 modalPanel을 사용하여 불가능 메시지 표시 + modalPanel.SetActive(true); + + if (keyText != null) + { + keyText.text = ""; + } + + string prompt = interactable.GetInteractionPrompt(); + interactText.text = ExtractInteractText(prompt); + } + } } } diff --git a/Assets/Scripts/InteractableModalManager.cs b/Assets/Scripts/InteractableModalManager.cs index 0629a9d..466daa2 100644 --- a/Assets/Scripts/InteractableModalManager.cs +++ b/Assets/Scripts/InteractableModalManager.cs @@ -12,6 +12,7 @@ namespace Northbound [SerializeField] private PlayerInteraction playerInteraction; private IInteractable _currentInteractable; + private IInteractable _previousUnavailableInteractable; private bool _isEnabled = false; private void Awake() @@ -33,14 +34,13 @@ namespace Northbound FindModalComponent(); - if (interactableModal != null) - { - interactableModal.HideModal(); - } - else + if (interactableModal == null) { Debug.LogError("[InteractableModalManager] InteractableModal is still null in Start!"); } + + // Start()에서 HideModal()을 호출하지 않음 - 첫 번째 Update에서 Modal이 표시되지 않는 문제 방지 + // modalPanel과 unavailablePanel은 InteractableModal.Start()에서 이미 false로 설정됨 } private void FindModalComponent() @@ -88,16 +88,26 @@ namespace Northbound _isEnabled = true; IInteractable detectedInteractable = GetDetectedInteractable(); + IInteractable unavailableInteractable = playerInteraction.CurrentUnavailableInteractable; - if (detectedInteractable != _currentInteractable) + bool interactableChanged = detectedInteractable != _currentInteractable; + bool unavailableChanged = unavailableInteractable != _previousUnavailableInteractable; + + if (interactableChanged || unavailableChanged) { _currentInteractable = detectedInteractable; + _previousUnavailableInteractable = unavailableInteractable; if (_currentInteractable != null) { Debug.Log($"[InteractableModalManager] Show modal for interactable"); ShowModal(_currentInteractable); } + else if (unavailableInteractable != null) + { + Debug.Log($"[InteractableModalManager] Show unavailable message"); + ShowUnavailableMessageModal(unavailableInteractable); + } else { Debug.Log($"[InteractableModalManager] Hide modal"); @@ -149,6 +159,14 @@ namespace Northbound } } + private void ShowUnavailableMessageModal(IInteractable interactable) + { + if (interactableModal != null) + { + interactableModal.ShowUnavailableMessage(interactable); + } + } + private void UpdateModalContent() { if (interactableModal != null) diff --git a/Assets/Scripts/PlayerInteraction.cs b/Assets/Scripts/PlayerInteraction.cs index d54e1f3..26a64aa 100644 --- a/Assets/Scripts/PlayerInteraction.cs +++ b/Assets/Scripts/PlayerInteraction.cs @@ -34,6 +34,7 @@ namespace Northbound private PlayerInputActions _inputActions; private IInteractable _currentInteractable; + private IInteractable _unavailableInteractable; private Camera _mainCamera; private Animator _animator; private EquipmentSocket _equipmentSocket; @@ -45,6 +46,7 @@ namespace Northbound public bool IsInteracting => _isInteracting; public float WorkPower => workPower; + public IInteractable CurrentUnavailableInteractable => _unavailableInteractable; public override void OnNetworkSpawn() { @@ -92,30 +94,42 @@ namespace Northbound { Vector3 origin = rayOrigin.position; Vector3 direction = useForwardDirection ? transform.forward : _mainCamera.transform.forward; - + Ray ray = new Ray(origin, direction); if (showDebugRay) { - Debug.DrawRay(ray.origin, ray.direction * interactionRange, + Debug.DrawRay(ray.origin, ray.direction * interactionRange, _currentInteractable != null ? Color.green : Color.yellow); } if (Physics.Raycast(ray, out RaycastHit hit, interactionRange, interactableLayer)) { IInteractable interactable = hit.collider.GetComponent(); - + if (interactable == null) interactable = hit.collider.GetComponentInParent(); - if (interactable != null && interactable.CanInteract(OwnerClientId)) + if (interactable != null) { - _currentInteractable = interactable; - return; + if (interactable.CanInteract(OwnerClientId)) + { + _currentInteractable = interactable; + _unavailableInteractable = null; + return; + } + else + { + // CanInteract가 false인 경우 추적 + _currentInteractable = null; + _unavailableInteractable = interactable; + return; + } } } _currentInteractable = null; + _unavailableInteractable = null; } private void OnInteract(InputAction.CallbackContext context)