플레이어가 상호작용 가능한 오브젝트에 다가가서 데쉬 버튼을 누를 시 발생함.
상호작용이 가능할 경우 대상 오브젝트의 상황에 맞는 말풍선 표시 ( 대화, 세이브, 회복 등 )
플레이어 근처의 상호작용 오브젝트 중 가장 가까운 오브젝트를 우선순위로 함.
스프라이트 사이즈가 큰 오브젝트의 경우 플레이어와 거리는 머나 보기에는 가까운 경우?
⇒ 일단은 상황 발생 시 생각하기로 하기
상호작용 가능한 오브젝트는 InteractableObject 컴포넌트를 부착할 예정.
InteractableObject 컴포넌트에서 말풍선, 상호작용 시 아래 인터페이스 함수 호출.
상호작용 가능한 오브젝트의 스크립트에 IInteractable 인터페이스 상속.
상속받아서 구체적인 상호작용 시 동작 구현.
플레이어는 상호작용 가능한 상태일 때 상호작용 가능한 오브젝트 탐색.
상호작용 가능한 오브젝트는 무조건 해당 컴포넌트 부착해야함.
플레이어 오브젝트에서 이 컴포넌트를 받아 상호작용 버튼 누를 시 함수 호출함.
[RequireComponent(typeof(EmotionableObject),typeof(IInteractable))] // 필수로 부착해야 함
public class InteractableObject : MonoBehaviour
{
public EmotionBubble.EmotionType emotionType; // 상호작용 가능 시 출력할 말풍선
private EmotionableObject _emotionableObject;
private IInteractable _interactable;
private CancellationTokenSource _disableCts = new();
private void Awake()
{
_emotionableObject = GetComponent<EmotionableObject>();
_interactable = GetComponent<IInteractable>();
}
// 해당 오브젝트가 상호작용 가능한 상태일 경우에만 실행됨
public void Interact()
{
if (_interactable.CanInteract)
{
_interactable.Interact();
}
}
// 상호작용 가능한 상태가 바뀔 경우 상태에 맞추어 말풍선 표시/제거
// 플레이어에서 근처 상호작용 가능한 오브젝트 찾은 뒤 호출함.
public async UniTaskVoid CheckInteractable()
{
_disableCts.CancelAndDispose();
_disableCts = new CancellationTokenSource();
while (true)
{
if (_interactable.CanInteract)
{
_emotionableObject.CreateEmotionBubble(emotionType);
}
else
{
_emotionableObject.RemoveEmotionBubble();
}
await UniTask.WaitUntilValueChanged(this,
interactableObject => interactableObject._interactable.CanInteract,
cancellationToken: _disableCts.Token);
}
}
// 플레이어가 근처에서 멀어져서 상호작용 불가능해지면 호출함.
public void StopCheckInteractable()
{
_emotionableObject.RemoveEmotionBubble();
_disableCts.Cancel();
}
private void OnDisable()
{
_disableCts.Cancel();
}
}
업데이트 함수에서 매 프레임 체크
private InteractableObject _currentInteractableObject;
private Collider2D _currentInteractedCollider;
private readonly List<Collider2D> _interactableColliders = new(); // 주변 상호작용 가능한 콜라이더 담는 용도
private readonly ContactFilter2D _interactionFilter = new()
{
layerMask = LayerCache.GetLayerMask(LayerName.Object),
useLayerMask = true,
};
private const float InteractionRange = 0.75f;
// 업데이트 함수에서 호출
private void CheckInteractableObject()
{
// 근처 원 범위에서 상호작용 가능한 오브젝트 체크
var numOfInteracted = Physics2D.OverlapCircle(Player.GetFlyingPosition(),
InteractionRange, _interactionFilter, _interactableColliders);
if (numOfInteracted != 0)
{
// 가장 가까운 오브젝트 확인
var closed = _interactableColliders
.OrderBy(collider2D => (collider2D.transform.position - Player.GetFlyingPosition()).sqrMagnitude)
.First();
// 가장 가까운 오브젝트가 현재 알고있는 상호작용오브젝트와 다를 경우에만 감정표현 표시 및 저장
if (_currentInteractedCollider != closed)
{
if (_currentInteractedCollider)
{
_currentInteractableObject.StopCheckInteractable();
}
_currentInteractedCollider = closed;
_currentInteractableObject = _currentInteractedCollider.GetComponent<InteractableObject>();
_currentInteractableObject.CheckInteractable().Forget();
}
}
else
{
// 상호작용 가능한 오브젝트 없는데 알고있는 경우
if (_currentInteractedCollider)
{
_currentInteractableObject.StopCheckInteractable();
_currentInteractableObject = null;
_currentInteractedCollider = null;
}
}
}
// 데쉬 버튼 누를 시 상호작용 가능한 오브젝트 있으면 오브젝트에 상호작용.
private void DashButtonDown()
{
if (_currentInteractableObject)
{
_currentInteractableObject.Interact();
return;
}
if (Player.IsDashCooldown)
{
return;
}
Player.ChangeState(PlayerState.Dash);
}