using System.Collections.Generic; using Sirenix.OdinInspector; using UnityEngine; namespace DDD { public class RestaurantCharacterInteraction : MonoBehaviour, IInteractor, IEventHandler { [SerializeField, ReadOnly] protected Collider[] _nearColliders = new Collider[10]; protected IInteractable _nearestInteractable; protected IInteractable _previousInteractable; protected IInteractable _interactingTarget; protected float _interactHeldTime; protected bool _isInteracting; protected float _interactionRadius = 1f; protected LayerMask _interactionLayerMask = (LayerMask)(-1); private Dictionary _cachedSolvers = new(); protected virtual void Start() { } protected virtual void Update() { _nearestInteractable = GetNearestInteractable(); if (_nearestInteractable != _previousInteractable) { _previousInteractable = _nearestInteractable; OnNearestInteractableChanged(_nearestInteractable); } if (_isInteracting) { if (_nearestInteractable != _interactingTarget) { ResetInteractionState(); return; } _interactHeldTime += Time.deltaTime; float requiredHoldTime = _interactingTarget.GetRequiredHoldTime(); float ratio = Mathf.Clamp01(_interactHeldTime / requiredHoldTime); if (_interactHeldTime >= requiredHoldTime) { _isInteracting = false; _interactingTarget.OnInteracted(this); _interactingTarget = null; OnInteractionCompleted(); } OnInteractionHoldProgress(ratio); } } protected virtual void OnDestroy() { } protected virtual void OnNearestInteractableChanged(IInteractable newTarget) { } protected virtual void OnInteractionHoldProgress(float ratio) { } protected virtual void OnInteractionCompleted() { } protected void ResetInteractionState() { _isInteracting = false; _interactingTarget = null; _interactHeldTime = 0f; OnInteractionHoldProgress(0f); } protected IInteractable GetNearestInteractable() { int colliderCount = Physics.OverlapSphereNonAlloc(transform.position, _interactionRadius, _nearColliders, _interactionLayerMask); float closestDistance = float.MaxValue; IInteractable closest = null; for (int i = 0; i < colliderCount; i++) { var col = _nearColliders[i]; if (col.TryGetComponent(out var interactable) == false || interactable.CanInteract() == false) continue; var type = interactable.GetInteractionType(); if (ContainTypeToSolver(type, out var solver) == false || solver.CanExecuteInteraction() == false) continue; float distance = Vector3.Distance(transform.position, col.transform.position); if (distance < closestDistance) { closestDistance = distance; closest = interactable; } } return closest; } public virtual void Invoke(RestaurantInteractionEvent evt) { } public GameObject GetInteractorGameObject() => gameObject; private bool ContainTypeToSolver(InteractionType type, out IInteractionSolver solver) { if (_cachedSolvers.TryGetValue(type, out solver)) return solver != null; solver = null; if (!RestaurantInteractionEventSolvers.TypeToSolver.TryGetValue(type, out var solverType)) return false; if (!TryGetComponent(solverType, out var component)) return false; solver = component as IInteractionSolver; _cachedSolvers[type] = solver; return solver != null; } } }