using System.Threading.Tasks; using Sirenix.OdinInspector; using UnityEngine; using UnityEngine.InputSystem; namespace DDD { public class RestaurantCharacterInteraction : MonoBehaviour, IInteractor, IEventHandler { private RestaurantPlayerDataSo _restaurantPlayerDataSo; [ReadOnly, SerializeField] private Collider[] _nearColliders = new Collider[10]; private IInteractable _nearestInteractable; private IInteractable _previousInteractable; private IInteractable _interactingTarget; private float _interactHeldTime; private bool _isInteracting; private void Start() { _ = Initialize(); } private void Update() { if (!_restaurantPlayerDataSo) return; _nearestInteractable = GetNearestInteractable(); if (_nearestInteractable != _previousInteractable) { _previousInteractable = _nearestInteractable; if (_nearestInteractable != null && _nearestInteractable.CanInteract()) { BroadcastShowUi(_nearestInteractable, 0f); } else { EventBus.Broadcast(GameEvents.HideInteractionUiEvent); } } if (_isInteracting) { // 도중에 타겟이 바뀐 경우 초기화 if (_nearestInteractable != _interactingTarget) { _isInteracting = false; _interactHeldTime = 0f; _interactingTarget = null; // UI 초기화 if (_nearestInteractable != null && _nearestInteractable.CanInteract()) { BroadcastShowUi(_nearestInteractable, 0f); } return; } _interactHeldTime += Time.deltaTime; float requiredHoldTime = _interactingTarget.GetRequiredHoldTime(); float ratio = Mathf.Clamp01(_interactHeldTime / requiredHoldTime); if (_interactHeldTime >= requiredHoldTime) { ratio = 0f; _isInteracting = false; _interactingTarget.OnInteracted(this); _interactingTarget = null; } BroadcastShowUi(_interactingTarget, ratio); } } private void OnDestroy() { EventBus.Unregister(this); if (_restaurantPlayerDataSo) { _restaurantPlayerDataSo.InteractAction.performed -= OnInteractPerformed; _restaurantPlayerDataSo.InteractAction.canceled -= OnInteractCanceled; } } private async Task Initialize() { _restaurantPlayerDataSo = await AssetManager.LoadAsset(DataConstants.RestaurantPlayerDataSo); Debug.Assert(_restaurantPlayerDataSo != null, "_restaurantPlayerDataSo is null"); _restaurantPlayerDataSo.InteractAction = InputManager.Instance.GetAction(InputActionMaps.Restaurant, nameof(RestaurantActions.Interact)); _restaurantPlayerDataSo.InteractAction.performed += OnInteractPerformed; _restaurantPlayerDataSo.InteractAction.canceled += OnInteractCanceled; EventBus.Register(this); } public void Invoke(RestaurantInteractionEvent evt) { // TODO : 이벤트결과를 보고 할 일이 있다면 여기서 뭔가 처리. 기본적으로 이벤트에서 이미 인터페이스로 인터랙션 처리됨 } public GameObject GetInteractorGameObject() { return gameObject; } private void OnInteractPerformed(InputAction.CallbackContext context) { if (_nearestInteractable == null || _nearestInteractable.CanInteract() == false) return; float requiredHoldTime = _nearestInteractable.GetRequiredHoldTime(); if (requiredHoldTime <= 0f) { _nearestInteractable.OnInteracted(this); } else { _isInteracting = true; _interactHeldTime = 0f; _interactingTarget = _nearestInteractable; } } private void OnInteractCanceled(InputAction.CallbackContext context) { _isInteracting = false; _interactHeldTime = 0f; _interactingTarget = null; if (_nearestInteractable != null && _nearestInteractable.CanInteract()) { BroadcastShowUi(_nearestInteractable, 0f); } } private IInteractable GetNearestInteractable() { int nearColliderCount = Physics.OverlapSphereNonAlloc(transform.position, _restaurantPlayerDataSo.InteractionRadius, _nearColliders, _restaurantPlayerDataSo.InteractionLayerMask, QueryTriggerInteraction.Collide); IInteractable nearestInteractable = null; float minDistance = float.MaxValue; for (int i = 0; i < nearColliderCount; i++) { var interactable = _nearColliders[i].GetComponent(); if (interactable == null || interactable.CanInteract() == false) continue; float sqrMagnitude = (interactable.GetInteractableGameObject().transform.position - transform.position).sqrMagnitude; if (sqrMagnitude > minDistance) continue; nearestInteractable = interactable; minDistance = sqrMagnitude; } return nearestInteractable; } private void BroadcastShowUi(IInteractable interactable, float ratio) { var evt = GameEvents.ShowInteractionUiEvent; evt.TextKey = interactable.GetInteractionMessageKey(); evt.HoldProgress = ratio; EventBus.Broadcast(evt); } } }