본문 바로가기
프로그래밍/유니티

[Unity NavMesh] 목적지 설정 + α

by 김시루시루르 2021. 11. 23.

이 글은 네비메쉬의 목적지를 지정하는 코드와..

직전까지 썼었던 '움직이는 길을 네비메쉬로 베이크하기' 에서

"결국 막다른길에서 어떻게 움직임을 멈추는가?" 에 대한 이야기입니다.

 

제목 뭐라고 지을지 한참 고민했는데 대충 저런 내용이에요.


 

1. 네비게이션 메쉬 에이전트 목적지 설정

1
GetComponent<NavMeshAgent>().SetDestination(Vector3targetpos);
cs

SetDestination을 사용해서 목적지 위치를 Vector3 좌표로 설정해줄 수 있습니다.


2. 마우스 클릭 좌표로 이동하기

정확히 말하자면 이건 레테에서 쓰던 코드라, 마우스 클릭한 곳에 있는 큐브로 이동하는 코드입니다.

자세한 코드는 다음에 다른 구현을 전부 설명하면 하고.. 지금은 딱 이 요소만.

(심지어 더블터치를 써야해서 구성도 아래랑은 조금 달라요)

 

레테에선 클릭한 큐브의 좌표를 바로 받아와서 하게 되어있습니다.

어차피 네비메쉬는 베이크 된 길을 따라서 가므로,

큐브의 좌표가 조금 다르다고 한들 (큐브 피봇 위치와 베이크된 위치의 높이 차이라든가)

별 문제가 없습니다. '가장 가까운 곳으로 향한다'는 논리로 어쨌든 대충 잘 맞는 결과가 나오거든요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//클릭한 큐브 찾는 함수
private Transform GetClickedObj()
{
    Transform target = null;
    RaycastHit hit;
    Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
 
    //레이캐스트 충돌 발생 시
    if (Physics.Raycast(ray, out hit, rayDistance, layerMask))
    {
        //충돌한 오브젝트를 태그로 구분해서 OK일 경우
        if (hit.transform.tag.Contains("Land"))
            target = hit.transform;
    }
    //충돌이 없으면 null
    else
        target = null;
 
    //트랜스폼 리턴
    return target;
}
 
//위에서 찾은 큐브를 목적지로 설정
private Update()
{
    //클릭 시에 작동
    if (Input.GetMouseButtonDown(0))
    {
        GetComponent<NavMeshAgent>().SetDestination(GetClickedObj().position);
    }
}
cs

저는 먼저 클릭한 큐브의 Transform을 받아오는 코드를 작성했습니다.

여기선 안나오지만 저는 다른 데에도 같은 트랜스폼을 쓸 일이 있기도 하고

레테 특유의 맵 구성으로 오브젝트의 포지션을 받아오는 형식을 취했지만,

이런 특이한! 경우가 아니라면 여기서 그냥 벡터3를 받아와도 됩니다.

 

13번째줄에서 hit.transform이 아니라 hit.point로 바꿔주시면 벡터3를 가져옵니다.

물론 이걸 수정하실 경우 함수 형식이나 이런 것도 고쳐주셔야 하겠지만요...


α. 움직이는 길을 네비메쉬로 설정할 때 막다른길의 처리법

어쨌든 네비메쉬를 사용하므로 목적지는 위와 같이 해주면 됩니다.

그런데 제가 2번 글에서 썼던 글을 보면, "막다른 길을 만났을 때 되돌아온다"는 부분이 있습니다.

 

[Unity NavMesh] 움직이는 길을 네비게이션 메쉬 영역으로 설정하기 (2 - 이동 제한)

바로 전 글이었던 "움직이는 길을 네비게이션 메쉬 영역으로 설정하기 (1)"에서 이어지는것입니다. 거기서 언급했던 문제들이 있는데요... 2번에선 예정대로 그걸 써볼까 하고 [Unity NavMesh] 움직이

drybone-developer.tistory.com

이 코드에서 보이는 //막다른 길일 때 실행 부분이 그것인데...

네비메쉬에선 우선 자체적으로 목적지로 가는 걸음을 멈추게 하는 코드가 있어요

ResetPath 혹은 Stop이 그 예시인데, 차이야 어쨌든간에 둘 다 그 자리에서 길을 멈추게 됩니다.

그런데 다른 코드를 썼냐? 그것은 아래와 같은 이유가 있습니다.


α - 문제. ResetPath와 Stop으로 해결 불가

리셋패스나 스톱을 썼을 때 레테에서 발생한 문제는 뭐였냐면

Update 내에서 레이캐스트로 충돌을 감지해서 더 갈 수 있는지 없는지를 정하는 상황인데

만약 여기서 갈 수 없다고 판단해서 그 자리에 멈춰버린다?

그러면 캐릭터의 이동과 동시에 일종의 무한 루프에 빠져버리는 것이죠

심지어 이 상태에서 되돌아가려고 다른 목적지를 선택해도,

레이캐스트가 계산이 제대로 되는 충돌 영역까지 오기는 힘들었는데

캐릭터가 뒤를 돌거나 하는 데에 생각보다 많은 프레임이 필요하고,

그건 충돌 계산 자체가 Update에서 돌아가고 있으니 매 프레임 '멈춘다!' 계산을 하기 때문에

목적지 설정 → Update → 갈 수 없다는 계산 → 이동을 멈춤 → 목적지 설정 → …

이런 식이었어요.

 

즉 캐릭터의 위치가 변하지 않는 이상 레이캐스트 충돌 여부도 그대로 굳어버리기 때문에

목적지 설정을 엄청나게 해줘야 겨우 탈출할 수 있는거였죠

심지어 이렇게 두면 1프레임 움직일때마다 그대로 굳어버려서 ...

이걸 어떻게해서든 바꿔줄 필요가 있었습니다.


α - 해결. 레이캐스트

그래서 이번에도 레이캐스트로 해결을 하기로 했습니다 (잔머리...)

캐릭터가 현재 있는 큐브를 레이캐스트로 받아와 저장해뒀다가,

막다른길에 오게 되면 이 큐브를 목적지로 설정해주는겁니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
//현재 서 있는 큐브를 받아오기
RaycastHit hitCurCube;
Ray rayCurCube = new Ray(transform.position + new Vector3(0.1f), -transform.up);
Debug.DrawLine(rayCurCube.origin,
        rayCurCube.origin + rayCurCube.direction * .5f, Color.blue);
 
if (Physics.Raycast(rayCurCube, out hitCurCube, .5f))
{
    if (hitCurCube.transform.tag.Contains("Land"))
    {
        curOnCube = hitCurCube.transform.gameObject;
    }
}
cs

이 코드를 사용해서 현재 올라가있는 큐브를 저장해둡니다.

 

레이 시작점은 캐릭터의 피봇인데 (코드치기 쉬우려고)

캐릭터에게 어떤 콜라이더가 있다 하더라도,

콜라이더 내부에서 시작된 레이는 해당 콜라이더를 충돌로 인식하지 않는다는 점을 이용했습니다 큭큭

 

방향은 바로 수직 아래. 별다른 이동이나 각도가 없는 것이

캐릭터가 막다른 길에 다다랐을 때 가장 안정적으로 위치 파악이 가능하기 때문입니다.

 

그리고 필요할 때 이걸 불러다가 SetDestination을 해주면 완료.

레테에서는 이 글에서 쓴 것처럼 막다른 길 혹은 끊어진 길일 때 이걸 썼습니다.

1
GetComponent<NavMeshAgent>().SetDestination(curOnCube.transform.position);
cs

저는 이게 필요해서 또 ... 트랜스폼으로 받아오긴 했습니다만

위에서 말한것처럼 그냥 바로 포지션을 받아오셔서 쓰셔도 됩니다.


이제 다음엔 정말로 움직이는 길을 네비게이션 메쉬 영역으로 설정하기 (3)을 쓰겠습니다...

 

 

 

댓글