ProjectDDD/Assets/_DDD/_Scripts/RestaurantCharacter/RestaurantCharacterInteraction.cs
2025-08-05 19:46:36 +09:00

175 lines
6.2 KiB
C#

using System.Threading.Tasks;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.InputSystem;
namespace DDD
{
public class RestaurantCharacterInteraction : MonoBehaviour, IInteractor, IEventHandler<RestaurantInteractionEvent>
{
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<RestaurantInteractionEvent>(this);
if (_restaurantPlayerDataSo)
{
_restaurantPlayerDataSo.InteractAction.performed -= OnInteractPerformed;
_restaurantPlayerDataSo.InteractAction.canceled -= OnInteractCanceled;
}
}
private async Task Initialize()
{
_restaurantPlayerDataSo = await AssetManager.LoadAsset<RestaurantPlayerDataSo>(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<RestaurantInteractionEvent>(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<IInteractable>();
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);
}
}
}