[TIL] Unity - 몬스터 AI (3)

MINO·2024년 7월 3일
post-thumbnail

2024-07-03


몬스터 AI + FSM

몇번의 수정 끝에 몬스터 AI 를 완성했다.

Base State 를 상속 받는 Idle, Wander, Chase, Attack State 가 있고,
각각의 조건에 따라 상태 변환을 한다.


Scriptable Object

몬스터의 공격력과 체력, 방어력 등 능력치를 위한 SO 를 생성하였다.
몬스터를 오브젝트 풀에서 꺼낼 때,
몬스터의 종류에 따라 해당 SO 로 능력치를 초기화 해주기 위해 사용하였다.

public class MonsterSO : ScriptableObject
{
    [field:Header("Monster Stats")]

    [field: SerializeField] public float HP { get; private set; }
    [field: SerializeField] public float STR { get; private set; }  // 공격력
    [field: SerializeField] public float DEF { get; private set; } // 방어력
    [field: SerializeField] public float LCK { get; private set; } // 크리티컬 관련 스탯

    [field: Header("Monster AI")]
    [field: SerializeField] public float WalkSpeed { get; private set; } // 순찰 시 이동 속도
    [field: SerializeField] public float WanderRadius { get; private set; } // 순찰 반경
    [field: SerializeField] public float WanderWaitTime { get; private set; } // 순찰 대기 시간

    

    [field: Header("Monster Attack State")]
    [field: SerializeField] public float RunSpeed { get; private set; } // 공격 시 이동 속도
    [field: SerializeField] public float DetectRadius { get; private set; } // 타겟 감지 반경
    [field:SerializeField]  public float AttackRate { get; private set; } // 공격 속도
    [field: SerializeField] public float AttackDistance { get; private set; } // 공격 범위   
}

Monster.cs

  • Start() - 몬스터를 처음 오브젝트 풀에서 소환했을 때
    • Idle 상태로 지정 / 애니메이션 속도 초기화 / 랜덤 위치로 탐색
  • OnEnable() - 오브젝트 풀에서 재소환 됐을 때
    • Idle 상태로 지정 / 능력치 초기화 / 랜덤 위치로 탐색
    • 피격 시, 빨간색으로 일시적으로 바꿔둔 채로 반환되었을 때 복구
  • Update() - 각 상태의 Update 문을 수행
  • LookAt() - 몬스터가 목적지 방향을 바라보게 해주는 메서드
  • Targeting() - 몬스터가 타겟 방향을 바라보게 해주는 메서드
  • GetRandomNavMeshPosition() - 탐색 범위 중 유효한 랜덤 값을 지정해 이동

 private void Start()
 {
     stateMachine.ChangeState(stateMachine.IdleState);
     SetAnimationSpeed();
     GetRandomNavMeshPosition();
 }

 private void OnEnable() // 반환되었다가, 다시 생성했을 때
 {
     stateMachine.ChangeState(stateMachine.IdleState);
     spriteRenderer.color = Color.white;
     isDead = false;
     stats.InitializeStats();
     GetRandomNavMeshPosition();
 }

 private void Update()
 {
     stateMachine.Update();
 }
 
public void LookAt()
{
	if (agent.destination.x < transform.position.x) // 목적지가 타겟보다 왼쪽에 있다면, 왼쪽을 바라보게
		spriteRenderer.flipX = true;

	else // 그렇지 않다면 오른쪽을 바라보게
		spriteRenderer.flipX = false;
}

public void Targeting()
{
	if (target.transform.position.x < transform.position.x) // 목적지가 타겟보다 왼쪽에 있다면, 왼쪽을 바라보게
		spriteRenderer.flipX = true;

	else // 그렇지 않다면 오른쪽을 바라보게
		spriteRenderer.flipX = false;
}

public void GetRandomNavMeshPosition()
{
	if (stateMachine.currentState != stateMachine.IdleState)
		return;

	if (agent.enabled != true)
		return;
        
	Vector3 randomDirection = Random.insideUnitSphere * stateMachine.monster.stats.wanderRadius.curValue;
	randomDirection += transform.position;

	NavMeshHit hit;
	NavMesh.SamplePosition(randomDirection, out hit, stateMachine.monster.stats.wanderRadius.curValue, NavMesh.AllAreas);
	stateMachine.monster.agent.SetDestination(hit.position);

	stateMachine.ChangeState(stateMachine.WanderState);
}

트러블 슈팅

몬스터가 종종 다른 색으로 생성되는 버그가 발견되었다.

몬스터 피격 시, DamageFlash() 라는 메서드를 코루틴으로 호출하여
빨간색으로 표시해주고, 다시 원래 색으로 복구시켜준다.

그러나, 몬스터가 오브젝트 풀에 반환되어 원래 색으로 복구 시켜주는 메서드가 실행이 안된 것으로 보인다.

따라서, OnEnable 시에 SpriteRenderer 의 색상을 White 로 수정해주는 코드를 추가해줘
해결할 수 있었다.

public IEnumerator DamageFlash()
{
    spriteRenderer.color = new Color(0.65f, 0.01f, 0.01f);

    yield return new WaitForSeconds(0.5f);

    spriteRenderer.color = Color.white;
}

결과물

몬스터의 상태에 따라 스크립트를 분류하여,
몬스터 오브젝트에 문제가 있을 때, 해당 상태만 확인하면 되기 때문에
훨씬 수월했다.


TIL 마무리

많은 FSM 코드와 유튜브 강의를 들으며,
FSM 과 꽤 친해진 것 같다.

NavMesh 와 함께 적용하다 보니, 버그도 많고 예외 처리도 많이 필요했지만,
덕분에 더 오랜시간 코드를 볼 수 있어 오히려 좋았던 것 같다.

profile
안녕하세요 게임 개발하는 MINO 입니다.

0개의 댓글