[Unity] 몬스터 BT NavMeshAgent

잘생김·2024년 2월 23일

이제 몬스터가 플레이어를 따라가게 만들기 위해 NavMesh를 적용해본다.

패키지메니져에서 AI Navigation을 설치해준다.


바닥면엔 NavMeshSurface 컴포넌트를 추가해 NavMeshAgent 컴포넌트를 적용한 몬스터가 잘 돌아다니도록 만들어준다.
그리고 MinoController.cs에 "_agent.SetDestination(_player.position);"를 추가해준다.

  • 공격범위 안으로 들어오면 몬스터는 멈추고 AttackActionNode를 실행한다.
  • 공격범위는 콜라이더에서 OnTriggerEnter를 쓰고 몬스터가 적정 범위내에서 멈추도록 NavMeshAgent 컴포넌트에서 Stopping Distanced에서 값을 넣어줬다.

플레이어 포지션을 감지해 자동으로 따라가게 만들려고하는데 문제가 발생했다.
그리고 해결의 결론부터 말하자면 이렇다.

1. 공격하는 애니메이션 플레이 도중에 플레이어가 움직이면 공격하는 애니메이션이 재생되는 상태에서 플레이어를 따라다닌다.

  • "BossTrack" 상태일 때만 플레이어를 추적한다.
  • 액션 노드 bool 속성의 클래스 isStoped을 추가해서 공격범위 밖으로 플레이어가 나갈 때 "BossTrack"을 한다.

2. Backstep 후 Stomp모션을 하는데 Backstep은 velocity를 적용해 뒤로 점프하는 모션을 넣었는데 점프는 하지만 플레이어한테 향하기 때문에 거리가 안 벌어진다.

  • 동일하게 isStoped의 값을 주는 액션 노드를 추가해서 애니메이션 시작 전 노드가 실행되게 했다.

처음엔 일딴 메소드 부터 만들었다.

조건은 특정상황에서 애니메이션이 재생될 때 isStoped의 반환이 true / false 인지에 따라 NavMeshAgent의 추척을 멈추거나 다시 재생하는 거였다.

public void TrackRangeInPlayer()
    {
        if (_attackRange == false && _isIdle == true)
        {
            if (_bossAnimator.GetCurrentAnimatorStateInfo(0).normalizedTime >= 1f)
            {
                _agent.enabled = true;
                _agent.SetDestination(_player.position);
            }
            if (_bossAnimator.GetCurrentAnimatorStateInfo(0).IsName("BossTrack"))
            {
                _agent.enabled = true;
                _agent.SetDestination(_player.position);
            }
        }
        else if (_attackRange == true && _isIdle == true)
        {
            if (_bossAnimator.GetCurrentAnimatorStateInfo(0).normalizedTime >= 1f)
            {
                transform.LookAt(_player.position);
            }

        }

        if (_bossAnimator.GetCurrentAnimatorStateInfo(0).IsName("BossJumpAttack"))
        {
            _agent.isStopped = true;
            if (_bossAnimator.GetCurrentAnimatorStateInfo(0).normalizedTime >= 1f)
            {
                _agent.isStopped = false;
            }
        }

        if (_bossAnimator.GetCurrentAnimatorStateInfo(0).IsName("BossBackstep"))
        {
            _agent.isStopped = true;

        }
        if (_bossAnimator.GetCurrentAnimatorStateInfo(0).IsName("BossStompAttack"))
        {
            _agent.isStopped = true;

            if(_bossAnimator.GetCurrentAnimatorStateInfo(0).normalizedTime >= 1f)
            {
                _agent.isStopped = false;
            }
        }

    }

그리고 팀장님이 이 어지러운 코드를 노드에 넣어서 구동시켜보자고 한다.

3. 플레이어가 몬스터의 뒤로 향해도 플레이어가 공격범위 내에 있으면 플레이어를 보지 않고도 공격모션을 한다.

  • 월드좌표계를 사용해서 이런 상황이 발생했다.
  • 벡터 내적을 사용하여 몬스터 기준으로 "BackStep"을 수행할 범위를 지정한다.

이 세가지 문제를 정리한 코드는 아래와 같다.

    private void Awake()
    {
        _bossAnimator = GetComponent<Animator>();
        _rigidbody = GetComponent<Rigidbody>();
        _agent = GetComponent<NavMeshAgent>();
        
        _treeA = new BehaviorTreeBuilder(gameObject)
            .Selector()
                .Sequence()
                    .Condition("attackRange", () => _dist <= _attackRange)
                    .Selector()
                        .Sequence()
                            .Condition("backPOS", () => _isForward == true)
                            .Do(() =>
                            {
                                _agent.isStopped = true;
                                return TaskStatus.Success;
                            })
                            .StateAction("BossBackstep", ProcessBackstep)
                            .StateAction("BossStompAttack")
                        .End()
                        .Sequence()
                            .Condition("keepDEF", () => _keepDEF == true)
                            .StateAction("BossKickAttack")
                        .End()
                        .SelectorRandom()
                            .StateAction("BossATK1")
                            .StateAction("BossATK2")
                            .StateAction("BossATK3")
                            .StateAction("BossATK4")
                        .End()
                    .End()
                .End()
                .Sequence()
                    .Condition("out7SEC", () => _dist >= _minJump && _dist < _maxJump)
                    .Do(() =>
                    {
                        _agent.isStopped = true;
                        return TaskStatus.Success;
                    })
                    .StateAction("BossJumpAttack", ProcessJumpAttack)
                .End()
                .RepeatUntilSuccess()
                    .Do("BossTrack", () =>
                    {
                        _bossAnimator.Play("BossTrack");
                        _agent.isStopped = false;
                        _agent.SetDestination(_player.position);
                        if (_dist > 5f)
                        {
                            return TaskStatus.Success;
                        }
                        else
                        {
                            _agent.isStopped = true;
                            return TaskStatus.Failure;
                        }
                    })
                .End()
            .End()
            .Build();
    }
    
        // 플레이어와의 위치 계산
    private void _checkDistance()
    {
        _dist = Vector3.Distance(_player.position, transform.position);
        Vector3 directionToPlayer = (_player.position - transform.position).normalized;
        Vector3 bossForward = transform.forward;

        float dotProduct = Vector3.Dot(directionToPlayer, bossForward);

        if (dotProduct > 0.5f)
        {
            _isForward = false;
        }
        else
        {
            _isForward = true;
        }
    }

Behavior Tree의 조건식과 구현에 더 익숙해진거같다.

profile
비전공자가 개발자로 취업하기 위해

0개의 댓글