2024-07-03

몇번의 수정 끝에 몬스터 AI 를 완성했다.
Base State 를 상속 받는 Idle, Wander, Chase, Attack State 가 있고,
각각의 조건에 따라 상태 변환을 한다.
몬스터의 공격력과 체력, 방어력 등 능력치를 위한 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; } // 공격 범위
}
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;
}
몬스터의 상태에 따라 스크립트를 분류하여,
몬스터 오브젝트에 문제가 있을 때, 해당 상태만 확인하면 되기 때문에
훨씬 수월했다.
많은 FSM 코드와 유튜브 강의를 들으며,
FSM 과 꽤 친해진 것 같다.
NavMesh 와 함께 적용하다 보니, 버그도 많고 예외 처리도 많이 필요했지만,
덕분에 더 오랜시간 코드를 볼 수 있어 오히려 좋았던 것 같다.