상호작용

플레이어가 상호작용 가능한 오브젝트에 다가가서 데쉬 버튼을 누를 시 발생함.

상호작용이 가능할 경우 대상 오브젝트의 상황에 맞는 말풍선 표시 ( 대화, 세이브, 회복 등 )

플레이어 근처의 상호작용 오브젝트 중 가장 가까운 오브젝트를 우선순위로 함.

스프라이트 사이즈가 큰 오브젝트의 경우 플레이어와 거리는 머나 보기에는 가까운 경우?

⇒ 일단은 상황 발생 시 생각하기로 하기

상호작용 가능한 오브젝트는 InteractableObject 컴포넌트를 부착할 예정.

InteractableObject 컴포넌트에서 말풍선, 상호작용 시 아래 인터페이스 함수 호출.

상호작용 가능한 오브젝트의 스크립트에 IInteractable 인터페이스 상속.

상속받아서 구체적인 상호작용 시 동작 구현.

플레이어는 상호작용 가능한 상태일 때 상호작용 가능한 오브젝트 탐색.

InteractableObject Class

상호작용 가능한 오브젝트는 무조건 해당 컴포넌트 부착해야함.

플레이어 오브젝트에서 이 컴포넌트를 받아 상호작용 버튼 누를 시 함수 호출함.

[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);
    }

IInteractable 및 상호작용 오브젝트