상호작용 클래스 분리

This commit is contained in:
NTG_Lenovo 2025-08-06 17:43:41 +09:00
parent 0b34f02a34
commit 239ab0601a
2 changed files with 150 additions and 121 deletions

View File

@ -1,62 +1,39 @@
using System.Threading.Tasks;
using Sirenix.OdinInspector; using Sirenix.OdinInspector;
using UnityEngine; using UnityEngine;
using UnityEngine.InputSystem;
namespace DDD namespace DDD
{ {
public class RestaurantCharacterInteraction : MonoBehaviour, IInteractor, IEventHandler<RestaurantInteractionEvent> public class RestaurantCharacterInteraction : MonoBehaviour, IInteractor, IEventHandler<RestaurantInteractionEvent>
{ {
private RestaurantPlayerDataSo _restaurantPlayerDataSo; [SerializeField, ReadOnly] protected Collider[] _nearColliders = new Collider[10];
[ReadOnly, SerializeField] private Collider[] _nearColliders = new Collider[10]; protected IInteractable _nearestInteractable;
private IInteractable _nearestInteractable; protected IInteractable _previousInteractable;
private IInteractable _previousInteractable; protected IInteractable _interactingTarget;
private IInteractable _interactingTarget;
private float _interactHeldTime; protected float _interactHeldTime;
private bool _isInteracting; protected bool _isInteracting;
private void Start() protected float _interactionRadius = 1f;
protected LayerMask _interactionLayerMask = (LayerMask)(-1);
protected virtual void Start() { }
protected virtual void Update()
{ {
_ = Initialize();
}
private void Update()
{
if (!_restaurantPlayerDataSo) return;
_nearestInteractable = GetNearestInteractable(); _nearestInteractable = GetNearestInteractable();
if (_nearestInteractable != _previousInteractable) if (_nearestInteractable != _previousInteractable)
{ {
_previousInteractable = _nearestInteractable; _previousInteractable = _nearestInteractable;
OnNearestInteractableChanged(_nearestInteractable);
if (_nearestInteractable != null && _nearestInteractable.CanInteract())
{
BroadcastShowUi(_nearestInteractable, 0f);
}
else
{
EventBus.Broadcast(GameEvents.HideInteractionUiEvent);
}
} }
if (_isInteracting) if (_isInteracting)
{ {
// 도중에 타겟이 바뀐 경우 초기화
if (_nearestInteractable != _interactingTarget) if (_nearestInteractable != _interactingTarget)
{ {
_isInteracting = false; ResetInteractionState();
_interactHeldTime = 0f;
_interactingTarget = null;
// UI 초기화
if (_nearestInteractable != null && _nearestInteractable.CanInteract())
{
BroadcastShowUi(_nearestInteractable, 0f);
}
return; return;
} }
@ -67,109 +44,56 @@ private void Update()
if (_interactHeldTime >= requiredHoldTime) if (_interactHeldTime >= requiredHoldTime)
{ {
ratio = 0f;
_isInteracting = false; _isInteracting = false;
_interactingTarget.OnInteracted(this); _interactingTarget.OnInteracted(this);
_interactingTarget = null; _interactingTarget = null;
OnInteractionCompleted();
} }
BroadcastShowUi(_interactingTarget, ratio); OnInteractionHoldProgress(ratio);
} }
} }
private void OnDestroy() protected virtual void OnDestroy() { }
{
EventBus.Unregister<RestaurantInteractionEvent>(this);
if (_restaurantPlayerDataSo) protected virtual void OnNearestInteractableChanged(IInteractable newTarget) { }
{ protected virtual void OnInteractionHoldProgress(float ratio) { }
_restaurantPlayerDataSo.InteractAction.performed -= OnInteractPerformed; protected virtual void OnInteractionCompleted() { }
_restaurantPlayerDataSo.InteractAction.canceled -= OnInteractCanceled;
}
}
private async Task Initialize() protected void ResetInteractionState()
{
_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; _isInteracting = false;
_interactHeldTime = 0f; _interactHeldTime = 0f;
OnInteractionHoldProgress(0f);
_interactingTarget = null; _interactingTarget = null;
if (_nearestInteractable != null && _nearestInteractable.CanInteract())
{
BroadcastShowUi(_nearestInteractable, 0f);
}
} }
private IInteractable GetNearestInteractable() protected IInteractable GetNearestInteractable()
{ {
int nearColliderCount = Physics.OverlapSphereNonAlloc(transform.position, _restaurantPlayerDataSo.InteractionRadius, int nearColliderCount = Physics.OverlapSphereNonAlloc(transform.position, _interactionRadius,
_nearColliders, _restaurantPlayerDataSo.InteractionLayerMask, QueryTriggerInteraction.Collide); _nearColliders, _interactionLayerMask, QueryTriggerInteraction.Collide);
IInteractable nearestInteractable = null; IInteractable nearest = null;
float minDistance = float.MaxValue; float minSqrDistance = float.MaxValue;
for (int i = 0; i < nearColliderCount; i++) for (int i = 0; i < nearColliderCount; i++)
{ {
var interactable = _nearColliders[i].GetComponent<IInteractable>(); var interactable = _nearColliders[i].GetComponent<IInteractable>();
if (interactable == null || interactable.CanInteract() == false) continue; if (interactable == null || !interactable.CanInteract()) continue;
float sqrMagnitude = (interactable.GetInteractableGameObject().transform.position - transform.position).sqrMagnitude; float sqrDistance = (interactable.GetInteractableGameObject().transform.position - transform.position).sqrMagnitude;
if (sqrMagnitude > minDistance) continue; if (sqrDistance < minSqrDistance)
nearestInteractable = interactable;
minDistance = sqrMagnitude;
}
return nearestInteractable;
}
private void BroadcastShowUi(IInteractable interactable, float ratio)
{ {
var evt = GameEvents.ShowInteractionUiEvent; minSqrDistance = sqrDistance;
evt.TextKey = interactable.GetInteractionMessageKey(); nearest = interactable;
evt.HoldProgress = ratio;
EventBus.Broadcast(evt);
} }
} }
return nearest;
}
public virtual void Invoke(RestaurantInteractionEvent evt) { }
public GameObject GetInteractorGameObject() => gameObject;
}
} }

View File

@ -0,0 +1,105 @@
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.InputSystem;
namespace DDD
{
public class RestaurantPlayerInteraction : RestaurantCharacterInteraction
{
private RestaurantPlayerDataSo _restaurantPlayerDataSo;
protected override void Start()
{
base.Start();
_ = Initialize();
}
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;
_interactionRadius = _restaurantPlayerDataSo.InteractionRadius;
_interactionLayerMask = _restaurantPlayerDataSo.InteractionLayerMask;
EventBus.Register<RestaurantInteractionEvent>(this);
}
protected override void OnDestroy()
{
base.OnDestroy();
if (_restaurantPlayerDataSo != null)
{
_restaurantPlayerDataSo.InteractAction.performed -= OnInteractPerformed;
_restaurantPlayerDataSo.InteractAction.canceled -= OnInteractCanceled;
}
EventBus.Unregister<RestaurantInteractionEvent>(this);
}
private void OnInteractPerformed(InputAction.CallbackContext context)
{
if (_nearestInteractable == null || !_nearestInteractable.CanInteract()) return;
float requiredHoldTime = _nearestInteractable.GetRequiredHoldTime();
if (requiredHoldTime <= 0f)
{
_nearestInteractable.OnInteracted(this);
}
else
{
_isInteracting = true;
_interactHeldTime = 0f;
_interactingTarget = _nearestInteractable;
}
}
private void OnInteractCanceled(InputAction.CallbackContext context)
{
ResetInteractionState();
}
protected override void OnNearestInteractableChanged(IInteractable newTarget)
{
if (newTarget != null && newTarget.CanInteract())
{
BroadcastShowUi(newTarget, 0f);
}
else
{
if (_isInteracting == false)
{
EventBus.Broadcast(GameEvents.HideInteractionUiEvent);
}
}
}
protected override void OnInteractionHoldProgress(float ratio)
{
if (_interactingTarget != null)
{
BroadcastShowUi(_interactingTarget, ratio);
}
}
protected override void OnInteractionCompleted()
{
}
private void BroadcastShowUi(IInteractable interactable, float ratio)
{
var evt = GameEvents.ShowInteractionUiEvent;
evt.TextKey = interactable.GetInteractionMessageKey();
evt.HoldProgress = ratio;
EventBus.Broadcast(evt);
}
}
}