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

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

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

바로 전 글이었던 "움직이는 길을 네비게이션 메쉬 영역으로 설정하기 (1)"에서 이어지는것입니다.

거기서 언급했던 문제들이 있는데요... 2번에선 예정대로 그걸 써볼까 하고

 

[Unity NavMesh] 움직이는 길을 네비게이션 메쉬 영역으로 설정하기 (1 - 기본 베이크)

움직이는 메쉬를 네비메쉬 (네브메쉬? 내비메쉬?) 영역으로 설정한다? 이걸 보신 분들은 대충 아 실시간 Bake 인 건가? 하실 수 있습니다. 하지만 유니티에 실시간 베이크따위는 존재하지 않습니

drybone-developer.tistory.com

 

1번 글에서 적은 문제는 이것들입니다.

근데 글이 너무 길어져서 1번 문제만 해결함 (미안해요!) 2번문제는 다음 글에...

그리고 글 쓸 때 컬러스크립터 폭 괜찮아보여서 냅뒀는데 완성하니까 전부 스크롤바생김 (미안해요!)


가지 말아야 할 곳을 가는 캐릭터

사실 1번 글에선 큐브는 없지만 메쉬 설정이 되어있는 부분을 걷는 바람에 하늘을 걷는다! 만 썼지만,

레테의 크기로 보아 몸 앞이 아니라 머리 앞에 큐브가 있는 상황에서도 이동을 제한해야 했습니다.

안그러면 레테의 머리가 큐브를 뚫고 지나가는셈(...)이 되니까요.

밟고 지나가는 큐브인데 뚫고 가는 건 좀 그렇지 않나? 싶어서

회의 후에 이런 상황에선 지나갈 수 없다고 정해서 작성된 스테이지가 조금 조정되기도 했습니다.

 

어쨌든 여기선 그렇게 가지 말아야 할 곳을 아무렇지 않게 지나다니는 캐릭터 막기 위한 방법입니다.

레이캐스트가 등장합니다. 1번 글에서 말한것처럼 A*알고리즘을 직접 짰다면 얘기는 달랐겠지만

저는 그러지 않았으니 다른 기능을 끌어와서 해결하기로 했습니다 (잔머리!)

레이캐스트를 쓴다고 했으니 대충 감 잡으신 분들도 계실듯 .. 그거 맞아요 (?)


1. 하늘을 걷는 경우

먼저번 글에서 임시 메쉬를 사용해서 네비게이션 메쉬를 미리 베이크했습니다.

그건 이렇게 큐브가 없음에도 불구하고 레테는 저 곳을 걸을 수 있다는 뜻이 됩니다.

메쉬는 베이크되어있으니까! (네비메쉬에선 오로지 메쉬만이 진리입니다 (?))

 

그래서 저는 뭘 해줬냐면

캐릭터의 발 밑에서 레이캐스트를 해서 충돌하는 게 없으면 직전 경로로 돌아가도록 했습니다.

아래는 그 코드입니다.

//발바닥레이
Ray rayRoad1 = new Ray(transform.position + transform.TransformVector(00.1f0.2f),
                        transform.TransformDirection(0-10));
Debug.DrawLine(rayRoad1.origin, rayRoad1.origin + rayRoad1.direction * 1, Color.red);
 
//막다른 길일때 실행
if (isEndRoad == true)
{
    //목적지를 재설정하기 (갈 수 있는 곳으로)
    charagent.SetDestination(curOnCube.transform.position);
    isEndRoad = false;
}
else if (!Physics.Raycast(rayRoad1, out _, 0.8f, layerMask))
{
    //막다른길이라고 알려주기
    isEndRoad = true;
}
cs

캐릭터의 발 밑에서 나갈 수 있도록 레이 시작점을 캐릭터의 위치에서 특정 수치를 더하게 했어요.

피봇이 아래쪽에 있었어서 조금만 더해줘도 괜찮았습니다.

 

막다른 길의 if문 실행 순서의 경우 순서를 레이캐스트 충돌 확인 → if문 실행으로 하면

중복 실행이 되거나 실행이 되지 않고 넘어가는 경우가 왕왕 있어서...

마침 저는 이걸 Update 안에 넣어서 돌리고 있었는데,

아예 다음 프레임으로 넘어가서 계산하게 하니까 꼬임 없이 되더라고요

레이의 시작 위치는 그림처럼 레테의 발보다 조금 앞에 있어서,

레테가 걸을 수 있는 큐브에서 발이 떨어지기 전에 되돌아올 수 있습니다.


1 - 문제. 레이캐스트 각도

근데 이 과정에서도 문제가 있었습니다. 이건.. 레테 특유의 문제긴 하겠지만

 

레이캐스트를 캐릭터 앞쪽 발 밑에서 하려고 시작점이랑 레이어마스크 다 조절했는데

못 가는 길을 못 가긴 하는데 가야 할 길도 못 가는거예요

위에서 보시면 아시겠지만 당시엔 레이캐스트를 직선으로 내리꽂았었는데

잘 되는 곳이 있고 안 되는 곳이 있고 그런거야

 

그래서 왜그럴까 하고 확인해봤더니, 이게 한 칸 한 칸 적당히 맞춰 붙인거다보니까

큐브 사이사이 조금씩 틈이 있는거고 그 사이에 레이캐스트가 꽂혀서 그런거였어요 ㅠㅠ

그래서 이걸 해결하기 위해 코드를 조금 고치게 됐습니다.

수직으로 내려오던 레이캐스트를 앞쪽으로 향하는 대각선으로 만들었어요.

각도와 길이는 큐브 틈을 가볍게 통과할 수 있을 수준이지만,

한 칸 떨어진 큐브까진 닿지 않는 선에서 조정했습니다.

//발바닥 레이를 대각선으로 수정
Ray rayRoad1 = new Ray(transform.position + transform.TransformVector(0.1f.2f),
    transform.TransformDirection(0-1.1f));
Debug.DrawLine(rayRoad1.origin, rayRoad1.origin + rayRoad1.direction * .8f, Color.red);
cs

시작점은 똑같고, 방향만 앞으로 조금 각도를 갖게 했어요.

나머지 코드는 전부 같아요.

수직 레이캐스트 (왼쪽), 대각선 레이캐스트 (오른쪽)

각도를 줘서 이런 식으로 레이캐스트가 되어, 큐브 사이의 틈에 충돌 없음 계산이 되는걸 막을 수 있습니다.


2. 머리 앞의 메쉬를 뚫고 가는 경우

이런 상황입니다.

이전 글에서 썼지만, 레테의 네비메쉬 에이전트 설정은 한 칸 한 칸 배치되는 스테이지 구성 때문에

전체적으로 1 미만의 값으로 설정되어있습니다. (그래야 베이크가 잘 나옴...)

그래서 레테가 지금 가려 하는 길엔 분명히 지나갈 수 있다는 메쉬가 있기때문에,

머리를 쿵 하고 부딪힐만한 큐브가 떡하니 버티고 있는데도 멀쩡히 저 길을 지나다니는 것이죠

 

이런건 이제.. 뭐.. 레테니까.. 발생하는 문제일수도 있어요 네비메쉬 사용이 이래가지고...

유니티가 보면 '이렇게 쓰라고 만든 네비메쉬가 아닌데...'하면서 개탄스러워할듯

하지만 어떻게 쓸 지는 내 마음이다 (북*의 권 얼굴 함)

 

해결법은 1번과 크게 다르지 않습니다. 오히려 더 쉬움!

//머리앞레이
Ray rayRoad2 = new Ray(transform.position + transform.TransformVector(01.1f0.3f),
        transform.TransformDirection(00, .1f));
Debug.DrawLine(rayRoad2.origin, rayRoad2.origin + rayRoad2.direction * .1f, Color.red);
cs

이 레이로 충돌을 체크하면 됩니다.

 

저는 1번의 하늘을 걷는 문제와 함께 체크를 하고싶었으므로, 같은 if문에 조건을 추가했습니다.

//대각선레이
Ray rayRoad1 = new Ray(transform.position + transform.TransformVector(0.1f.2f),
        transform.TransformDirection(0-1.1f));
Debug.DrawLine(rayRoad1.origin, rayRoad1.origin + rayRoad1.direction * .8f, Color.red);
 
//머리앞레이
Ray rayRoad2 = new Ray(transform.position + transform.TransformVector(01.1f0.3f),
        transform.TransformDirection(00.1f));
Debug.DrawLine(rayRoad2.origin, rayRoad2.origin + rayRoad2.direction * .1f, Color.red);
 
//막다른 길일때 실행
if (isEndRoad == true)
{
    //목적지를 재설정하기 (갈 수 있는 곳으로)
    charagent.SetDestination(curOnCube.transform.position);
    isEndRoad = false;
}
else if (!Physics.Raycast(rayRoad1, out _, .8f, layerMask)
    || (Physics.Raycast(rayRoad2, out _, .1f, layerMask))
{
    //막다른길이라고 알려주기
    isEndRoad = true;
}
cs

충돌했을 때 이동을 다시 설정해야 하는 레이캐스트와

충돌하지 않았을 때 이동을 다시 설정해야 하는 레이캐스트의 구별이 중요합니다 (헷갈리니까요)

위치와 길이는 이정도쯤. 이제 레이캐스트가 두개가 되었습니다.


2 - 빠뜨린 것. 또 다른 메쉬를 뚫고 갈 수 있다

그러나 조심해야 할 것은 머리 앞의 큐브만이 아니었습니다.

제가 빠뜨린 것은 바로.. 몸 앞을 가로막고 있는 큐브. 당당하게 길을 막는 녀석이죠

이 베이크 방법 상 이런 그림이 나올 수도 있습니다.

이 큐브가 사다리가 붙어있다면 차라리 낫겠죠 사다리 함수가 켜지면 어쨌든 위로 갈 수 있을테니까요

그런데 사다리가 없다.. 여긴 반드시 치워야한다 그렇다면?

건너편 길을 선택해도 이 큐브를 뚫고 갈 수 없게끔 해줘야합니다.

 

바로 위에 쓴 눈 앞의 큐브를 계산하는 코드와 비슷하게 몸 앞쪽의 큐브와 충돌하는 레이를 써줍니다.

이제 이렇게 세 가지 레이캐스트를 체크하는 함수가 완성되었습니다.

(여기서 추가된 레이는 '몸통앞레이'입니다!)

//대각선레이
Ray rayRoad1 = new Ray(transform.position + transform.TransformVector(0.1f.2f),
        transform.TransformDirection(0-1.1f));
Debug.DrawLine(rayRoad1.origin, rayRoad1.origin + rayRoad1.direction * .8f, Color.red);
 
//머리앞레이
Ray rayRoad2 = new Ray(transform.position + transform.TransformVector(01.1f0.3f),
        transform.TransformDirection(00.1f));
Debug.DrawLine(rayRoad2.origin, rayRoad2.origin + rayRoad2.direction * .1f, Color.red);
 
//몸통앞레이
Ray rayRoad3 = new Ray(transform.position + transform.TransformVector(00.5f0.3f),
        transform.TransformDirection(00.1f));
Debug.DrawLine(rayRoad3.origin, rayRoad3.origin + rayRoad3.direction * .1f, Color.red);
 
//막다른 길일때 실행
if (isEndRoad == true)
{
    //목적지를 재설정하기 (갈 수 있는 곳으로)
    charagent.SetDestination(curOnCube.transform.position);
    isEndRoad = false;
}
else if (!Physics.Raycast(rayRoad1, out _, .8f, layerMask)
    || (Physics.Raycast(rayRoad2, out _, .1f, layerMask)
    || Physics.Raycast(rayRoad3, out _, .1f, layerMask)))
{
    GameManager.instance.StopMoving();
    //막다른길이라고 알려주기
    isEndRoad = true;
}
cs

세 개의 레이가 막다른 길을 확인해주고 있습니다.


3. 결과

이 세 개의 레이캐스트로 위 이미지처럼 끊어진 길이나 막힌 길에서 멈출 수 있는 캐릭터가 되었습니다.


캐릭터를 멈추거나 되돌아가게 하는 방법은 아래 글을 참고해주세요.

 

[Unity NavMesh] 목적지 설정 + α

이 글은 네비메쉬의 목적지를 지정하는 코드와.. 직전까지 썼었던 '움직이는 길을 네비메쉬로 베이크하기' 에서 "결국 막다른길에서 어떻게 움직임을 멈추는가?" 에 대한 이야기입니다. 제목 뭐

drybone-developer.tistory.com

댓글