내일배움캠프 Unity 82일차 TIL - 팀 9와 4분의 3 - 개발일지

Wooooo·2024년 2월 22일
0

내일배움캠프Unity

목록 보기
84/94

[오늘의 키워드]

Player State 리팩토링 진행 상황
Player Interact 개선 진행 상황


[Player State 리팩토링 진행 상황]

Idle, Run State

    public override void Enter()
    {
        _stateMachine.MovementSpeedModifier = 0f;
        base.Enter();

        WeaponItemData hand = _stateMachine.Player.ToolSystem.EquippedTool.itemSlot.itemData as WeaponItemData;

        if (hand != null)
        {
            if (hand.isTwoHandedTool)
                _targetParameterHash = _stateMachine.Player.AnimationData.EquipTwoHandedToolIdleParameterHash;
            else if (hand.isTwinTool)
                _targetParameterHash = _stateMachine.Player.AnimationData.EquipTwinToolIdleParameterHash;
            else
                _targetParameterHash = _stateMachine.Player.AnimationData.IdleParameterHash;
        }
        else
            _targetParameterHash = _stateMachine.Player.AnimationData.IdleParameterHash;

        StartAnimation(_targetParameterHash);
    }

들고 있는 도구마다 상태 클래스가 있었는데, 하나로 합친 뒤 현재 들고 있는 도구에 따라 다른 애니메이션을 재생하도록 했다.

    protected virtual void OnMove()
    {
        _stateMachine.ChangeState(_stateMachine.RunState);
    }
    
    protected override void OnMovementCanceled(InputAction.CallbackContext context)
    {
        base.OnMovementCanceled(context);
        _stateMachine.ChangeState(_stateMachine.IdleState);
    }

이제 다른 상태에서는 if-else문으로 걸러줄 필요가 없어졌다.

TODO

달리는 도중에 도구를 바꾸면 애니메이션이 어색한 상황이다.
RunState -> IdleState -> RunState를 순식간에 전이하는게 원인이므로 해결이 필요하다.

또, 다른 도구를 끼고 있다가 단검으로 스위칭하는 순간 공격 시, 단검 전용 공격 애니메이션이 아니라 일반 공격 애니메이션을 재생하는 문제가 있다.
이는 Animator에서 트랜지션이 단검 상태로 넘어가기 전에 공격 애니메이션을 재생하여 일반 공격이 재생되는 것이 원인인 것 같다.

Destroy, Interact, Make State

public class PlayerInteractState : PlayerBaseState
{
    protected IInteractable _interactable;
    protected IDestructible _destructible;
    protected float _progressTime;
    protected string _targetTag;
    protected Vector3 _targetPos;

    // TEST
    private float _lapseTime = 0f;

    public PlayerInteractState(PlayerStateMachine playerStateMachine) : base(playerStateMachine)
    {

    }

    public override void Enter()
    {
        if (_interactable == null && _destructible == null)
        {
            _stateMachine.ChangeState(_stateMachine.IdleState);
            return;
        }

        _stateMachine.MovementSpeedModifier = 0;
        base.Enter();
        StartAnimation(_stateMachine.Player.AnimationData.InteractParameterHash);
        _lapseTime = 0f;

        _stateMachine.Player.Animator.SetBool(_targetTag, true);
    }

    public override void Exit()
    {
        base.Exit();
        _stateMachine.Player.Animator.SetBool(_targetTag, false);
        _interactable = null;
        _destructible = null;
        _targetTag = string.Empty;

        StopAnimation(_stateMachine.Player.AnimationData.InteractParameterHash);
    }

    public override void Update()
    {
        // exit 조건 설정
        float normalizedTime = GetNormalizedTime(_stateMachine.Player.Animator, "Interact");
        _lapseTime += Time.deltaTime;

        // TODO: 시간받아와서
        if (_lapseTime >= 1f)
        {
            _interactable?.Interact(_stateMachine.Player);
            _destructible?.Destruct(_stateMachine.Player);
            _stateMachine.ChangeState(_stateMachine.IdleState);
                // TODO: 내구도 소모하는 기능 ResourceObjectParent로 이관. 수치 설정 해야함
        }
    }

    public void SetTarget(IInteractable target, string tag, Vector3 position)
    {
        _interactable = target;
        _targetPos = position;
        _targetTag = tag;
    }

    public void SetTarget(IDestructible target, string tag, Vector3 position)
    {
        _destructible = target;
        _targetPos = position;
        _targetTag = tag;
    }
}

이들은 Interact State 하나로 합쳤다.
MakeState는 InteractState와 동작방식이 거의 같아서 금방 기능을 합칠 수 있었지만,

DestroyState는 IDestructible의 메서드를 호출해야하고 InteractState는 IInteractable의 메서드를 호출해야해서 두 개의 인터페이스 변수를 두고, SetTarget() 메서드를 오버로딩해서 해결했다.

TODO

아직 인터페이스들에 Get~~Time() 메서드를 구현해두지 않았다.
따라서 모든 오브젝트의 상호작용 시간이 1이고, 제작대나 모닥불 같은 오브젝트도 1초 뒤에 UI가 열린다.
이 부분은 수치만 조정하면 될 것으로 예상 중.


[Player Interact 개선 진행 상황]

    protected virtual void OnInteractStarted(InputAction.CallbackContext context)
    {
        if (_stateMachine.IsFalling) return;

        // ================================= TEST =====================================
        Collider[] targets;
        var hand = _stateMachine.Player.EquippedItem.itemSlot.itemData;

        if (hand is WeaponItemData)
        {
            _stateMachine.ChangeState(_stateMachine.ComboAttackState);
            return;
        }
        if (hand is ToolItemData tool)
        {
            targets = Physics.OverlapSphere(_stateMachine.Player.transform.position, tool.range, tool.targetLayers, QueryTriggerInteraction.Collide);
            foreach (var target in targets)
            {
                if (target.TryGetComponent<IDestructible>(out var destructible))
                {
                    _stateMachine.InteractState.SetTarget(destructible, tool.targetTagName, target.transform.position);
                    _stateMachine.ChangeState(_stateMachine.InteractState);
                    return;
                }
            }
            foreach (var target in targets)
            {
                if (!target.CompareTag(tool.targetTagName))
                    continue;

                if (target.TryGetComponent<IInteractable>(out var interactable))
                {
                    _stateMachine.InteractState.SetTarget(interactable, tool.targetTagName, target.transform.position);
                    _stateMachine.ChangeState(_stateMachine.InteractState);
                    return;
                }
            }
        }
        targets = Physics.OverlapSphere(_stateMachine.Player.transform.position, _emptyHandData.range, LayerMask.GetMask("Architecture"), QueryTriggerInteraction.Collide);
        foreach (var target in targets)
        {
            _stateMachine.InteractState.SetTarget(target.GetComponent<IInteractable>(), target.tag, target.transform.position);
            _stateMachine.ChangeState(_stateMachine.InteractState);
            return;
        }
        targets = Physics.OverlapSphere(_stateMachine.Player.transform.position, _emptyHandData.range, _emptyHandData.targetLayers, QueryTriggerInteraction.Collide);
        foreach (var target in targets)
        if (target.CompareTag(_emptyHandData.targetTagName))
        {
            _stateMachine.InteractState.SetTarget(target.GetComponent<IInteractable>(), target.tag, target.transform.position);
            _stateMachine.ChangeState(_stateMachine.InteractState);
            return;
        }
        // ============================================================================
    }

상호작용 순서 설정

  1. 무기를 들고 있다면 공격
  2. 도구를 들고 있다면 알맞는 오브젝트에게 상호작용
    a) 파괴할 수 있는 오브젝트를 먼저 탐색
    b) 상호작용 할 수 있는 자원 오브젝트를 탐색
  3. 제작대, 화로 등 구조물과 상호작용
  4. 맨 손으로 채집할 수 있는 자원 오브젝트와 상호작용

다른 부분은 다 괜찮았는데, 2번에서 조금 해맸다.
파괴할 수 있는 오브젝트 중 대부분은 상호작용도 할 수 있다.

처음엔 a) b)의 반복문이 한 반복문 안에서 실행되고 있었는데, 하나의 객체에서 IInteractable을 구현한 컴포넌트가 먼저 탐색 될 수 있다는 걸 깨달았다. 따라서 a)를 먼저 탐색한 후, b)를 탐색하도록 했다.

그 다음엔 상호작용이 불가능한 울타리 오브젝트는 부숴지는데 제작대와 같은 상호작용이 가능한 오브젝트는 부숴지지 않는 문제가 있었다.

기존 코드

foreach (var target in targets)
{
	if (!target.CompareTag(tool.targetTagName))
    	continue;

	if (target.TryGetComponent<IDestructible>(out var destructible))
    {
    	_stateMachine.InteractState.SetTarget(destructible, tool.targetTagName, target.transform.position);
        _stateMachine.ChangeState(_stateMachine.InteractState);
        return;
	}
}
변경 코드

foreach (var target in targets)
{
	if (target.TryGetComponent<IDestructible>(out var destructible))
    {
    	_stateMachine.InteractState.SetTarget(destructible, tool.targetTagName, target.transform.position);
        _stateMachine.ChangeState(_stateMachine.InteractState);
        return;
	}
}

구조물을 파괴할 때 사용하는 '망치' 도구의 targetTagNameArchitecture인데, 울타리와 같은 상호작용이 안되는 구조물은 tag가 Architecture였지만, 제작대나 화로같이 상호작용이 가능한 오브젝트는 tag가 Make였다. 따라서 파괴 가능한 오브젝트의 탐색 시에는 태그 비교를 안해주기로 했다.

상호작용 오브젝트 설정

플레이어가 상호작용 시 어떤 오브젝트에 상호작용해야할 지 미리 계산한 뒤 InteractState에게 "이 오브젝트에게 상호작용 해라"라고 알려주도록 개선했다.
상호작용 할 오브젝트를 알려주기 위한 메서드는 SetTarget() 메서드다.

근처의 여러 오브젝트 존재 시

기존에는 탐색 범위 내에 여러 오브젝트가 있을 때 상호작용 버튼을 눌러도 상호작용을 하지 않는 문제가 있었다.
이는 탐색된 오브젝트 중 0번째 인덱스에 있는 오브젝트에게만 상호작용을 시도했기 때문인데, 0번째 인덱스의 오브젝트가 현재 착용한 도구로 상호작용을 할 수 없으면 아무 행동도 하지 않는 것이었다.
따라서, 근처에 있는 모든 오브젝트에게 상호작용 시도를 할 수 있도록 반복문을 돌게 했다.

TODO

아직 무기 착용 시의 상호작용 기능은 없다.
근처의 몬스터도 탐색한 다음, 몬스터가 없다면 상호작용을 하고, 상호작용할 오브젝트 마저도 없다면 허공에 공격할 수 있도록 개선할 계획이다.

또, 오브젝트들을 탐색하는 코드가 되게 길어지고, PlayerBaseState의 한 메서드 안에 들어가 있는 것이 가독성도 되게 별로고 수정 사항이 생겼을 때, 나중에 이 기능이 어디에서 호출되는지 까먹었을 때 한번에 기억해낼 수 있도록 이 기능들을 별도의 클래스로 분리할 계획이다.

나중에 지금 상호작용 버튼을 누르면 이 오브젝트에게 상호작용 됩니다 라고 해당 오브젝트에 아웃라인을 띄울 계획도 있는데, 이를 위해서라도 이 기능은 클래스로 분리하는 것이 옳은 방향인 것 같다.

profile
game developer

0개의 댓글