[Unity Course 2] 10. 내비게이션 고급 기법
위키북스 출판사 이재현 저자님의 '절대강좌! 유니티' 책을 참고하여 필기한 내용입니다.
동적 장애물
지금까지 드럼통을 폭파시켜도 미리 베이크 했던 내비메시가 여전히 그 지점은 지나갈 수 없어서
이와 같이 동적으로 변경되는 장애물에 NavMeshObstacle 컴포넌트를 활용하면 쉽게 해결 가능
NavMeshObstacle 컴포넌트

Barrel 프리팹에 Navigation Static 옵션을 해제

Navigation을 다시 Bake

Barrel 프리팹을 더블클릭하여 open 하고 Nav Mesh Obstacle이라는 컴포넌트를 추가한다.
속성값은 다음과 같이 변경
프리팹 에디트 모드 창의 Auto Save 옵션이 체크되어 있어서 자동으로 저장됨


드럼통이 사라지면 직선으로 추적해온다
그러나 드럼통이 촘촘히 연결되어 설치된 경우 드럼통사이를 관통해 지나가려는 경로를 산출한다.

우리가 원하는건 촘촘히 배치된 드럼통을 크게 돌아서 추적하는 형태이기 때문에 다음과 같은 설정을 변경한다

Nav Mesh Obstacle에 Carve 속성을 활용하여 이러한 현상을 해결할 수 있음
Carve 속성은 실시간 내비메시가 변경된다
| Carve옵션 | 설명 |
| Move Threshold | 속성값의 거리만큼 이동했을 때 내비메시를 갱신 |
| Time To Stationary | 동일 위치에서 일정 시간동안 정지했을 때 내비메시를 갱신 |
| Carve Only Stationary | 정지 상태에만 내비메시를 갱신 |
Off Mesh Link Generation
: 서로 분리된 메시를 연결, 추적이 가능하게 연결고리를 생성할 수 있음
계단 모델을 설치

설치한 계단은 아랫부분이 바닥에 묻히게 높이를 조절

옵션에서 Navigation Static, Off Mesh Link Generation을 체크한다.

계단을 선택 후 Navigation 뷰에 Object 탭 설정이 위와 같은지 확인 꼭 두개의 속성이 체크되어 있어야함
Bake 하면 계단 위도 올라갈 수 있음을 확인
계단위에서 뛰어내릴 수 있게 하기 위해선 Drop Height 속성을 5로 설정한다.


Drop Height : Off Mesh Link 가 생성되는 최대 높이를 지정하는 것 Drop Height 이하에서만 링크가 생성됨
Jump Distance 속성은 같은 높이에서 장애물을 뛰어 넘을 수 있는 거리를 설정하는 옵션

Stair 에는 Collider 컨트롤러가 없어서 주인공이 점프하여 이동 불가하기 때문에 Mesh Collider 컴포넌트를 연결하고 Convex 속성을 체크하여 폴리곤 수를 낮춘다.
Navigation에서 Bake탭의 Agent Radius 값을 0.2로 수정하면 off Mesh Link가 촘촘하게 생성됨

사용자 정의 Off Mesh Link
자동으로 생성된 Off Mesh Link 는 Drop Height 높이 조건만 만족하면 무조건 생성됨
그러나 게임내 시나리오상 특정 지점에서만 링크를 허용해야 된다면 앞서 적용한 방식은 적합하지 않음
계단 상단에서 바닥으로 연결하는 하나의 사용자 정의 Off Mesh Link 를 생성하기
하이러키뷰의 Stair를 선택하고 Generate OffMeshLinks 속성을 언체크 하고 다시 Bake하기

Stair 하위에 StartPos, EndPos 빈 오브젝트 생성후 MyGizmos 스크립트를 연결한 후 계단의 StartPos 는 계단 위, EndPos는 뛰어내릴 위치에 배치한다.

Stair에 Off Mesh Link 컴포넌트를 연결하고 방금 만든 StartPos, EndPos를 연결한다.
Off Mesh Link 컴포넌트 : 내비메시를 베이크하지 않아도 자동으로 생성되며 기본 양방향으로 연결되어 있음.이 뜻은 밑에서도 경사로 위로 뛸 수 있음을 의미
Bidirectional 옵션을 언체크하면 단방향으로만 링크가 생성됨
Activated는 런타임 시 Off Mesh Link를 활성/비활성화하는 옵션
Auto Update Positions : 런타임 시 링크의 위치가 변경될 때 이를 반영하여 링크의 위치를 갱신하는 것
자연스러운 회전 처리
현재 몬스터의 회전이 부자연스럽기 때문에 빠르게 회전 처리하고 이동하도록 스크립트에서 직접 회전 로직 구현
MonsterCtrl.cs 의 Awake 함수 수정, Update함수 생성
private void Awake()
{
// 몬스터의 Transform 할당
monsterTr = GetComponent<Transform>();
// 추적 대상인 Player의 Trasnform 할당
playerTr = GameObject.FindWithTag("PLAYER").GetComponent<Transform>();
// NavMEshAgent 컴포넌트 할당
agent = GetComponent<NavMeshAgent>();
// NavMeshAgent 의 자동 회전 기능과 비활성화
agent.updateRotation = false;
// Animator 컴포넌트 할당
anim = GetComponent<Animator>();
// BloodSprayEffect 프리팹 로드
bloodEffect = Resources.Load<GameObject>("BloodSprayEffect");
}
private void Update()
{
// 목적지까지 남은 거리로 회전 여부 판단
if(agent.remainingDistance >= 2.0f)
{
// 에이전트의 이동 방향
Vector3 direction = agent.desiredVelocity;
// 회전 각도(쿼터니언) 산출
Quaternion rot = Quaternion.LookRotation(direction);
// 구면 선형보간 함수로 부드러운 회전 처리
monsterTr.rotation = Quaternion.Slerp(monsterTr.rotation,
rot,
Time.deltaTime * 10.0f);
}
}
직접 회전 처리를 하기 위해 NavMeshAgent.updateRoatate 속성은 false로 지정하여 비활성화 시킨다.
NavMEshAgent.remainingDistance는 목적지까지 남은 거리가 얼마인지를 가리키는 속성
거리가 2미터 이상일 때 회전 처리하는 로직
private void Update()
{
// 목적지까지 남은 거리로 회전 여부 판단
if(agent.remainingDistance >= 2.0f)
{
// 에이전트의 이동 방향
Vector3 direction = agent.desiredVelocity;
// 회전 각도(쿼터니언) 산출
Quaternion rot = Quaternion.LookRotation(direction);
// 구면 선형보간 함수로 부드러운 회전 처리
monsterTr.rotation = Quaternion.Slerp(monsterTr.rotation,
rot,
Time.deltaTime * 10.0f);
}
}
이처럼 이동, 회전 처리를 직접 구현해야 되는 경우는 updatePosition 속성과 updateRotation 속성을 활용하도록하기
* NavMeshAgent 컨트롤할 때 유용한 속성
| 속성 | 설명 |
| updatePosition | 위치를 자동으로 이동시키는 옵션 |
| updateRotation | 자동으로 회전시키는 옵션 |
| remainingDistance | 목적지까지 남은 거리 |
| velocity | 에이전트의 현재 속도 |
| desireVelocity | 장애물 회피를 고려한 이동방향 |
| pathPending | 목적지까지의 최단거리 계산이 완료됐는지 여부 |
| isPathState | 계산한 경로의 유효성 여부(동적, 장애물, OffMeshLink) |
Area Mask 의 활용
게임의 시나리오에 따라서 이동 시 힘이 덜 드는 평평한 길과 진흙 길과 수영해서 가야하는 물이 있다고 가정하면 평평한 길이 가장 가중치가 적게 든다.
경로의 가중치
내비게이션 뷰의 Areas 탭을 클릭하면 Area Mask 와 Cost를 설정할 수 있음

위와 같이 Cost를 설정


Road 길이 가중치가 가장 적기 때문에 최단 거리인 Road로 오는 것을 확인할 수 있다.