유니티 RPG - 26. 몬스터 AI - Attack 상태 구현
Intro
이번 포스팅에서는
플레이어를 따라다니며 공격하는 몬스터의 Attack 상태를 구현해보았습니다.
먼저 공격 애니메이션을 추가해주었습니다.
공격 애니메이션을 실행하기위한 Attack 트리거를 만들어 Transition 조건으로 걸어줍니다.
Monster
Monster 클래스에서 몬스터의 공격속도, 공격이 준비되었는지 여부를 판단할 변수를 만들어줍니다.
public bool isAttackReady; // 공격가능여부
public float attackDelay; // 공격속도
그리고 isAttackReady를 true로 바꿔주기위한 함수를 만들어주었습니다.
public void ReadyToAttack()
{
isAttackReady = true;
}
이 함수는 Attack 상태에서 공격후 공격불가에서 공격가능으로 바꿔주기위한 함수입니다.
전체코드
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
/*
Monster 클래스는 몬스터의 공통데이터를 관리
*/
public class Monster : MonoBehaviour
{
#region **Monster Stats**
[Header("#Monster Stats")]
public int maxHp;
public int curHp;
public float speed;
public float maxDistance; // 플레이어와의 거리(복귀하기위한 최대거리)
public float idleThreshold; // 복귀후 처음 위치와의 차이
public float attackDelay; // 공격속도
#endregion
private Vector3 startPosition; // 몬스터의 첫 위치
public bool isReset; // 원점으로 복귀했는지 여부
public bool isAttackReady; // 공격 가능 여부
protected PlayerController targetPlayer; // 타깃 플레이어
private BoxCollider scanRange; // 플레이어 탐지범위
private BoxCollider hitBoxCol; // 몬스터 히트박스
private Animator anim; // 몬스터 애니메이터
private NavMeshAgent nav; // 몬스터 네비게이션
#region ** Properties **
public Animator Anim => anim;
public NavMeshAgent Nav => nav;
public PlayerController Target => targetPlayer;
public Vector3 StartPosition => startPosition;
#endregion
protected void Awake()
{
Init();
}
private void Init()
{
targetPlayer = GameManager.Instance.player;
startPosition = transform.position;
isAttackReady = true;
anim = GetComponent<Animator>();
nav = GetComponent<NavMeshAgent>();
hitBoxCol = GetComponent<BoxCollider>();
scanRange = GetComponent<BoxCollider>();
}
// 공격 가능상태로 전환
public void ReadyToAttack()
{
isAttackReady = true;
}
}
TurtleShell
TurtleShell 클래스에서는 공격속도를 설정하고,
플레이어와 가까워졌을때 Attack 상태에돌입 혹은 공격중 다시 플레이어가 멀어졌을때 쫓아가기위한 Chase 상태 돌입을 설정합니다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/*
Monster - TurtleShell
- 가지는 상태 : Idle, Chase, Attack, Die
*/
public class TurtleShell : Monster
{
// TurtleShell이 가지는 상태
private enum States
{
Idle,
Chase,
Attack,
Die
}
private States curState; // 현재 상태
private StateMachine<TurtleShell> stateMachine;
private void Awake()
{
// 부모(Monster)의 초기화
base.Awake();
InitValues();
}
private void InitValues()
{
// TurtleShell 능력치 초기화
maxHp = 100;
curHp = maxHp;
speed = 1f;
maxDistance = 5f;
idleThreshold = 0.3f;
attackDelay = 2f;
// 초기상태는 Idle
curState = States.Idle;
// StateMachine 객체 생성(Idle상태)
stateMachine = new StateMachine<TurtleShell>(new IdleState<TurtleShell>(this));
Nav.speed = speed;
}
private void Update()
{
stateMachine.curState.OnStateUpdate();
// 공격 사거리
float attackRange = Vector3.Distance(transform.position, Target.transform.position);
// 복귀완료 -> Idle상태 톨입
if (isReset)
{
ChangeState(States.Idle);
isReset = false;
}
// 공격범위에 들어서면 Attack 상태 돌입
if(attackRange <= Nav.stoppingDistance)
{
ChangeState(States.Attack);
}
// 공격상태에서 플레이어가 멀어지면 Chase 상태 돌입
else if(curState == States.Attack && attackRange > Nav.stoppingDistance)
{
ChangeState(States.Chase);
}
}
// Scan Range에 플레이어가 들어왔을경우
private void OnTriggerEnter(Collider other)
{
if (!other.CompareTag("Player"))
return;
ChangeState(States.Chase);
}
// 상태 변경
private void ChangeState(States nextState)
{
curState = nextState;
switch(curState)
{
case States.Idle:
stateMachine.ChangeState(new IdleState<TurtleShell>(this));
break;
case States.Chase:
stateMachine.ChangeState(new ChaseState<TurtleShell>(this));
break;
case States.Attack:
stateMachine.ChangeState(new AttackState<TurtleShell>(this));
break;
case States.Die:
stateMachine.ChangeState(new DieState<TurtleShell>(this));
break;
}
}
}
TurtleShell의 공격 사거리는 Nav Mesh Agent의 Stop Distance를 적절히 설정하여
플레이어와 TurtleShell 사이의 거리가 Stop Distance보다 작을때로하였습니다.
AttackState
AttackState 에서는 몬스터가 플레이어를 바라보고 공격하는 로직을 작성했습니다.
다만 공격속도에 따른 공격을 수행하도록 Invoke()를 통해 공격속도에따라 공격이 가능해지도록 했습니다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/*
Monster State - Attack (공격상태)
*/
public class AttackState<T> : BaseState<T> where T : Monster
{
public AttackState(T monster) : base(monster) { }
public override void OnStateEnter()
{
monster.Anim.SetBool("Walk", false);
}
public override void OnStateUpdate()
{
// 위치 고정
monster.Nav.SetDestination(monster.transform.position);
// 플레이어 바라보기
monster.transform.LookAt(monster.Target.transform);
// 공격준비가 되었을 때
if(monster.isAttackReady)
{
monster.Anim.SetTrigger("Attack");
// 공격성공시 처리
// 공격속도에 따른 공격가능여부 설정
monster.isAttackReady = false;
monster.Invoke(nameof(monster.ReadyToAttack), monster.attackDelay);
}
}
public override void OnStateExit()
{
}
}
댓글남기기