미니 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
'공부 > 인프런 - Rookiss' 카테고리의 다른 글
Part 3-13-8. 미니 RPG : 레벨업 (0) | 2023.09.02 |
---|---|
Part 3-13-7. 미니 RPG : Destory (0) | 2023.09.01 |
Part 3-13-5. 미니 RPG : 체력 게이지 (0) | 2023.08.31 |
Part 3-13-4. 미니 RPG : 공격 (0) | 2023.08.31 |
Part 3-13-3. 미니 RPG : 타겟 락온 (0) | 2023.08.31 |