공부/인프런 - Rookiss

Part 3-13-6. 미니 RPG : 몬스터 AI

셩잇님 2023. 9. 1. 13:50
반응형

 

 

미니 RPG

몬스터 AI

 

📜BaseController 👉 플레이어와 몬스터의 공통적인 속성과 기능 모음

1. 📜PlayerController

2. 📜MonsterController

 

 

📜MonsterController와 📜PlayerController의 공통적인 함수 및 멤버들은 📜BaseController로 옮겨주었다. 몬스터 애니메이션 컨트롤러의 애니메이션 클립의 이름들도 플레이어 애니메이션 컨트롤러와 동일하게.

 



📜MonsterController

몬스터 오브젝트에 붙여준다.


📜PlayerController 와 상당수 비슷하다. 여기에 없는건 📜BaseController로부터 상속 받는다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class MonsterController : BaseController
{
    Stat _stat;

    [SerializeField]
    float _scanRange = 10;

    [SerializeField]
    float _attackRange = 2;

    public override void Init()
    {
        _stat = gameObject.GetComponent<Stat>();

        if (gameObject.GetComponentInChildren<UI_HPBar>() == null)
            Managers.UI.MakeWorldSpace<UI_HPBar>(transform);
    }

 

게임이 시작되면 UI_HPBar를 몬스터에게 붙인다.

📜BaseController로부터 이 Init을 실행시키는 Start 를 상속 받음

 

    protected override void UpdateIdle()
    {
        Debug.Log("Monster UpdateIdle");

        GameObject player = GameObject.FindGameObjectWithTag("Player");
        if (player == null)
            return;

        float distance = (player.transform.position - transform.position).magnitude;
        if (distance <= _scanRange)
        {
            _lockTarget = player;
            State = Define.State.Moving;
            return;
        }
    }

 

UpdateIdle 👉 몬스터가 Idle 상태일 때 매프레임 실행할 일

“Player”태그를 가진 오브젝트를 찾아 player에 할당. 플레이어 오브젝트 찾기.

플레이어가 사정거리내에 존재하면 _lockTarget에 플레이어 오브젝트 할당하고 이제 플레이어 쫓아가야 하니까 상태를 Moving으로 변경

 

    protected override void UpdateMoving()
    {
        Debug.Log("Monster UpdateMoving");

        // 플레이어가 내 사정거리보다 가까우면 공격
        if (_lockTarget != null)
        {
            _destPos = _lockTarget.transform.position;
            float distance = (_destPos - transform.position).magnitude;
            if (distance <= _attackRange)
            {
                State = Define.State.Skill;
                return;
            }
        }

        // 길 찾기 이동
        Vector3 dir = _destPos - transform.position;
        if (dir.magnitude < 0.1f)
        {
            State = Define.State.Idle;
        }
        else
        {
            NavMeshAgent nma = gameObject.GetOrAddComponent<NavMeshAgent>();
            nma.SetDestination(_destPos);
            nma.speed = _stat.MoveSpeed;

            transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(dir), 20 * Time.deltaTime);
        }
    }

 

UpdateMoving 👉 몬스터가 Moving 상태일 때 매프레임 실행할 일

 

1. ‘시야’ 사정거리보다 가까우면 UpdateIdle 를 통해 _lockTarget에 플레이어 들어있는 상태

플레이어를 향해 _destPos 업뎃하고

플레이어가 ‘공격’ 사정거리보다 가까우면 공격. 그리고 길 찾을 필요 없으니 return

 

2. 길 찾기

도착했다면 Idle 상태로 돌아가기

아니라면 플레이어 향해 바라보며 쫓아가야 함.

 

    protected override void UpdateSkill()
    {
        Debug.Log("Monster UpdateSkill");

        if (_lockTarget != null)
        {
            Vector3 dir = _lockTarget.transform.position - transform.position;
            Quaternion quat = Quaternion.LookRotation(dir);
            transform.rotation = Quaternion.Lerp(transform.rotation, quat, 20 * Time.deltaTime);
        }
    }

 

UpdateSkill 👉 몬스터가 Skill 상태일 때 매프레임 실행할 일

공격 중에 플레이어 바라보고 공격하게끔

 

    void OnHitEvent()
    {
        Debug.Log("Monster OnHitEvent");

        if (_lockTarget != null) // 플레이어 타겟팅 중
        {
            // 체력
            Stat targetStat = _lockTarget.GetComponent<Stat>();
            int damage = Mathf.Max(0, _stat.Attack - targetStat.Defense);
            targetStat.Hp -= damage;

            if (targetStat.Hp > 0)
            {
                float distance = (_lockTarget.transform.position - transform.position).magnitude;
                if (_attackRange >= distance)
                    State = Define.State.Skill;
                else
                    State = Define.State.Moving;
            }
            else
            {
                State = Define.State.Idle;
            }
        }
        else  // 플레이어 타겟팅 중이 아닐 땐
        {
            State = Define.State.Idle;
        }
    }
}

 

OnHitEvent 👉 몬스터의 공격 애니메이션 중 발생하는 이벤트 (플레이어의 체력 깎기)

 

1. 플레이어가 아직 안 죽었다면

공격 사정거리 이내라면 다시 공격

아니라면 다시 쫓기

 

2. 플레이어가 죽었다면
정지

 

이동시 밀리는 현상

📜MonsterController : 몬스터가 이동시 플레이어를 미는 현상

 

    protected override void UpdateMoving()
    {
        Debug.Log("Monster UpdateMoving");

        // 플레이어가 내 사정거리보다 가까우면 공격
        if (_lockTarget != null)
        {
            _destPos = _lockTarget.transform.position;
            float distance = (_destPos - transform.position).magnitude;
            if (distance <= _attackRange)
            {
                NavMeshAgent nma = gameObject.GetOrAddComponent<NavMeshAgent>();
                nma.SetDestination(transform.position);
                State = Define.State.Skill;
                return;
            }
        }
            if (distance <= _attackRange)
            {
                NavMeshAgent nma = gameObject.GetOrAddComponent<NavMeshAgent>();
                nma.SetDestination(transform.position);

 

공격할 때도 계속 짧은 새 마다 플레이어를 쫓지 않도록(밀지 않도록), 공격 사정 거리 내에 있으면 그냥 제자리에 있도록 nma.SetDestination(transform.position)으로 설정한다.

 

📜PlayerController : 플레이어가 이동시 몬스터를 미는 현상

protected override void UpdateMoving()
{
		// 이동
		Vector3 dir = _destPos - transform.position;
		if (dir.magnitude < 0.1f)
		{
			State = Define.State.Idle;
		}
		else
		{
			Debug.DrawRay(transform.position + Vector3.up * 0.5f, dir.normalized, Color.green);
			if (Physics.Raycast(transform.position + Vector3.up * 0.5f, dir, 1.0f, LayerMask.GetMask("Block")))
			{
				if (Input.GetMouseButton(0) == false) //
					State = Define.State.Idle;
				return;
			}

			float moveDist = Mathf.Clamp(_stat.MoveSpeed * Time.deltaTime, 0, dir.magnitude);
			transform.position += dir.normalized * moveDist;
			transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(dir), 20 * Time.deltaTime);
		}
	}

 

NavMeshAgent의 nma.Move 함수로 이동하지 않고 직접 플레이어의 위치를 업뎃시켜 해결하였다. transform.position += dir.normalized * moveDist;

NavMeshAgent를 붙여서 이동하는 방식은 기본적으로 Agent들은 서로 피해가도록 되어 있어 너무 인접하게 붙으면 의도치 않게 상대를 밀치기도 한다. 이는 Obstacle Avoidance 속성 때문이다. 따라서 이를 해결하는 방법 중 하나는 NavMeshAgent 를 사용하지 않고 레이저를 쏴서 이동 가능한지를 확인 한 후 일반적인 플레이어 위치 세팅으로 이동을 하는 것이다.

https://stackoverflow.com/questions/23451983/how-to-avoid-two-navmeshagent-push-away-each-other-in-unity

 

How to avoid two NavMeshAgent push away each other in Unity?

In my game, all player and monster characters have a NavMeshAgent component, when one character moves to another, it will push away the second one. I read the unity docs of NavMeshAgent, found the

stackoverflow.com

 

 

 

반응형