using Unity.Netcode; using UnityEngine; using UnityEngine.InputSystem; namespace Northbound { /// /// 플레이어가 월드의 오브젝트와 상호작용하는 시스템 /// public class PlayerInteraction : NetworkBehaviour { [Header("Interaction Settings")] public float interactionRange = 3f; public LayerMask interactableLayer = ~0; [Header("Detection")] public Transform rayOrigin; public bool useForwardDirection = true; [Header("Animation")] public bool playAnimations = true; public bool useAnimationEvents = true; public bool blockDuringAnimation = true; [Header("Equipment")] public bool useEquipment = true; [Header("Debug")] public bool showDebugRay = true; private PlayerInputActions _inputActions; private IInteractable _currentInteractable; private Camera _mainCamera; private Animator _animator; private EquipmentSocket _equipmentSocket; private bool _isInteracting; private float _interactionStartTime; private const float INTERACTION_TIMEOUT = 3f; public bool IsInteracting => _isInteracting; private void Awake() { _animator = GetComponent(); _equipmentSocket = GetComponent(); } public override void OnNetworkSpawn() { base.OnNetworkSpawn(); if (rayOrigin == null) rayOrigin = transform; _inputActions = new PlayerInputActions(); _inputActions.Player.Interact.performed += OnInteract; _inputActions.Enable(); } public override void OnNetworkDespawn() { if (_inputActions != null) { _inputActions.Player.Interact.performed -= OnInteract; _inputActions.Disable(); _inputActions.Dispose(); } base.OnNetworkDespawn(); } private void Update() { if (!IsOwner) return; if (_isInteracting && Time.time - _interactionStartTime > INTERACTION_TIMEOUT) { _isInteracting = false; } DetectInteractable(); } private void DetectInteractable() { 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, _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)) { _currentInteractable = interactable; return; } } _currentInteractable = null; } private void OnInteract(InputAction.CallbackContext context) { if (blockDuringAnimation && IsInteracting) return; if (_currentInteractable == null) return; var equipmentData = _currentInteractable.GetEquipmentData(); string animTrigger = _currentInteractable.GetInteractionAnimation(); bool hasAnimation = !string.IsNullOrEmpty(animTrigger); if (playAnimations && _animator != null && hasAnimation) { _isInteracting = true; _interactionStartTime = Time.time; if (useAnimationEvents) { _animator.SetTrigger(animTrigger); } else { _animator.SetTrigger(animTrigger); _currentInteractable.Interact(OwnerClientId); _isInteracting = false; } } else { _currentInteractable.Interact(OwnerClientId); } } public void AttachEquipment() { if (!IsOwner) return; if (_currentInteractable == null || !useEquipment) return; var equipmentData = _currentInteractable.GetEquipmentData(); if (equipmentData == null || !equipmentData.attachOnStart) return; if (_equipmentSocket == null) { Debug.LogWarning("[PlayerInteraction] EquipmentSocket component not found on player"); return; } if (equipmentData.equipmentPrefab == null) { Debug.LogWarning("[PlayerInteraction] Equipment prefab is null. Assign a prefab in the Resource's EquipmentData in Inspector"); return; } _equipmentSocket.AttachToSocket(equipmentData.socketName, equipmentData.equipmentPrefab); } public void DetachEquipment() { if (!IsOwner) return; if (_currentInteractable == null || !useEquipment) return; var equipmentData = _currentInteractable.GetEquipmentData(); if (equipmentData == null || !equipmentData.detachOnEnd) return; if (_equipmentSocket == null) return; _equipmentSocket.DetachFromSocket(equipmentData.socketName); } public void OnInteractionComplete() { _isInteracting = false; if (_currentInteractable != null) { _currentInteractable.Interact(OwnerClientId); } } } }