Chapter 13-8. 미니 RPG : 몬스터 AI
카테고리: Unity Lesson 2
태그: Unity Game Engine
인프런에 있는 Rookiss님의 [C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part3: 유니티 엔진 강의를 듣고 정리한 필기입니다. 😀
🌜 강의 들으러 가기 Click
Chapter 13. 미니 RPG 만들기
🚀 몬스터 AI
- 📜BaseController 👉 플레이어와 몬스터의 공통적인 속성과 기능 모음
- 📜PlayerController
- 📜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
으로 변경
- 플레이어가 사정거리내에 존재하면
- “Player”태그를 가진 오브젝트를 찾아
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
상태일 때 매프레임 실행할 일- ‘시야’ 사정거리보다 가까우면 UpdateIdle 를 통해
_lockTarget
에 플레이어 들어있는 상태- 플레이어를 향해
_destPos
업뎃하고 - 플레이어가 ‘공격’ 사정거리보다 가까우면 공격. 그리고 길 찾을 필요 없으니 return
- 플레이어를 향해
- 길 찾기
- 도착했다면
Idle
상태로 돌아가기 - 아니라면 플레이어 향해 바라보며 쫓아가야 함..
- 도착했다면
- ‘시야’ 사정거리보다 가까우면 UpdateIdle 를 통해
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 👉 몬스터의 공격 애니메이션 중 발생하는 이벤트
- 플레이어의 체력 깎기
- 플레이어가 아직 안 죽었다면
- 공격 사정거리 이내라면 다시 공격
- 아니라면 다시 쫓기
- 플레이어가 죽었다면
- 정지
- 플레이어가 아직 안 죽었다면
- 플레이어의 체력 깎기
✈ 이동시 밀리는 현상
📜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 를 사용하지 않고 레이저를 쏴서 이동 가능한지를 확인 한 후 일반적인 플레이어 위치 세팅으로 이동을 하는 것이다. -출처 : Rookiss님 답변-
🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄
댓글 남기기