플레이어를 추적하는 Enemy Tracking State를 만들어보겠다
Enemy Roaming State
우선 EnemyRoamingState가 첫 State로써 시작한다
public override void StateUpdate()
{
if (Vector3.Distance(enemyManager.PlayerTransform.position, enemyManager.transform.position) < enemyManager.NoticeDistance)
{
enemyAI.ChangeState(EnemyState.Tracking);
return;
}
{ ... }
Distance를 통해서 거리를 확인하고 Tracking으로 전환하게 했다
전에 포스팅이랑 다르게 개인적으로 만들어낸 적시야 탐지 코드를 사용하지 않았는데 이유가 있다
적 시야 탐지 코드 => 달라진 점 : 각 z,x 부호 판별 후 값을 -1,1 더해줘서 값을 키워야 함
1.0~0.0 사이인 경우가 많아서 아주 작은 숫자로인해 부호 판별이 제대로 되지 않는다
그렇기 때문에, 각 z, x 를 음수인지 양수인지 판단을 먼저하고 음수면 -1 해주고 양수면 +1 해줌으로써 값을 크게한다음 비교를 해준다
=> 근데 그렇게 하면 코드 가독성이 너무 떨어지고 if문을 상당히 많이 써야한다
굉장히 난잡해보임
나중에 성능문제 생기면 프로파일링해서 Distance의 연산을 생략 할 필요가 있다면 sqrmagnitude를 이용해서 거리계산을 하겠다
근데 대략적인 거리 계산만 하면 되는 것 아닌가? 라는 생각이 들었다
사실 Distance는 정확한 거리계산은 해주는데 연산 비용이 좀 있다
그래서 주로 쓰는 방법인데 sqrMagnitude로 거리 계산을 대략적으로 하고 비교하고자 하는것을 제곱해주는 것이다
if((enemyManager.transform.position - enemyManager.PlayerTransform.position).sqrMagnitude <
enemyManager.NoneTrackingDistance * enemyManager.NoneTrackingDistance)
{
enemyAI.ChangeState(EnemyState.Tracking);
return;
}
대략적인 거리계산이 돼서 Tracking으로 넘어가는데 지장이 없고 계산 비용 또한 적다
이렇게 하면 이제 EnemyTrackingState.cs를 짜줘야한다
public override void StateEnter()
{
enemyManager.Animator.SetBool(enemyManager.AnimIDRun, true);
enemyManager.EnemyNavAgent.updateRotation = false;
setPath = true;
}
Tracking을 하는 과정이니 걷는것이아니고 뛰는 애니메이션으로 바꿨고
저기 NavMeshAgent가 관리하는 updateRotation 기능은 꺼주고 직접 Rotate기능을 추가 했다
저 기능으로 인해 Enemy가 Tracking하는 과정에서 굉장히 부자연스러운 회전( Angular Speed 탓도 있음) 을 보여주며 일정 범위를 플레이어가 벗어났을때 Enemy가 플레이어를 바라보는 것이 아니라 그냥 목적지만 바라보고있다
그래서 꺼주고 개인적으로 코드를 추가 해줬다
public override void StateUpdate()
{
// 플레이어 방향을 구하기
Vector3 dir = (enemyManager.PlayerTransform.position - enemyManager.transform.position).normalized;
dir = new Vector3(dir.x, 0, dir.z);
Quaternion rotation = Quaternion.LookRotation(enemyManager.EnemyNavAgent.desiredVelocity);
{ ... }
우선 플레이어의 방향을 구하고 현재 NavMeshAgent의 속도를 바라보게끔 했다
Angular Speed보다 훨씬 자연스럽다
rotation 하는 기능은 밑에 있다
왜 즉시 해주지 않았냐면 플레이어가 일정 범위를 벗어나면 추적을 멈추어도 플레이어를 바라보게하는 자연스러운 Enemy AI를 구현하기 위함이다
{ ... }
// 플레이어 방향을 구하기
Vector3 dir = (enemyManager.PlayerTransform.position - enemyManager.transform.position).normalized;
dir = new Vector3(dir.x, 0, dir.z);
Quaternion rotation = Quaternion.LookRotation(enemyManager.EnemyNavAgent.desiredVelocity);
if (setPath)
{
if (enemyManager.EnemyNavAgent.pathPending == false)
{
Debug.Log("Tracking");
//Tracking한 시간
enemyManager.EnemyNavAgent.SetDestination(enemyManager.PlayerTransform.position);
}
setPath = false;
}
{ ... }
setPath는 Enter부분으로 인해 true로 시작한다
pathPending은 현재 path 계산중인지 아닌지 판별한다
false면 계산중이지 않다는 것이기 때문에 정확한 목적지 판별 및 오류 방지를 위해 false일 경우에만 SetDestination을 하게끔 했다
그리고 SetDestinaiton은 Enemy 수가 많아질수록 성능 비용이 크다
한번 setPath 해준 뒤 어떤 조건이 달성되지않는다면 바로 false를 걸어줬다
바로 다음 코드다
{ ... }
if (enemyManager.EnemyNavAgent.remainingDistance < enemyManager.NoneTrackingDistance)
{
setPath = true;
enemyManager.EnemyNavAgent.isStopped = false;
}
else
{
enemyManager.EnemyNavAgent.isStopped = true;
enemyManager.Animator.SetBool(enemyManager.AnimIDRun, false);
// 멀리 간 플레이어가 일정 시간 안돌아오면 복귀
waitCounter += Time.deltaTime;
if(waitCounter > waitPlayerTime)
{
enemyManager.EnemyNavAgent.isStopped = false;
enemyAI.ChangeState(EnemyState.Roaming);
}
rotation = Quaternion.LookRotation(dir);
}
enemyManager.transform.rotation = Quaternion.Lerp(enemyManager.transform.rotation, rotation,
Time.deltaTime * enemyManager.RotationSpeed);
setPath를 계속 해주다가 어느순간 목적지로부터 남아있는거리가 추적 가능한 거리를 벗어났을때
isStopped을 통해 Enemy를 멈추고 대기시간을 부여한다
그 대기시간동안 rotation은 플레이어를 바라보는 방향으로 해주고 대기시간이 지나고 나면 다시 Roaming State로 전이 한다
그러한 과정 뒤에 마지막에 rotation을 부드럽게 해주는 코드를 짰다