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

[Unity] A* Pathfingind Project - 길찾기 에셋

by 김시루시루르 2022. 4. 18.

레테 2.0 업데이트를 하면서 이동 방법을 전부 갈아치웠습니다.

블로그를 보시면 아시겠지만 전에는 NavMesh 기능에서 잔머리를 굴려서 했었는데요

지금은 그때 사고 못 썼다던 에셋을.. 드디어 적용을 했어요 전에 비해 엄청 깔끔하고 좋아졌습니다

다만 이동의 모든 구간을 내가 제어하는 게 아니라 에셋이 제어해서

에셋 코드 수정을 거치지 않은 지금으로서는 애니메이션은 좀 버그가 생기는듯

그래도 .. 정말.. 좋다!

 

 

A* Pathfinding Project Pro | AI | Unity Asset Store

Get the A* Pathfinding Project Pro package from Aron Granberg and speed up your game development process. Find this & other AI options on the Unity Asset Store.

assetstore.unity.com

이 글은 간단한 후기글이자 프로젝트에서 뭘 어떻게 설정하고 다듬어서 썼는지에 대한 얘기입니다

근데 이런건 문서화가 되게 잘 돼있어서 굳이 필요없을것같기도 하고...

문득 ... 내 프로젝트 정리가 너무 대충 된거같아서 그거 다시 하느라 대충 쓰고 나중에 수정될지도

 

쓰다보니 너무 ... 미묘해져서 하는 말인데 홍보비 안 받았어요 (줬으면 좋겠네요)

 


 

왜 이 에셋인가…

일단 제가 이 에셋을 산 이유.

네비메쉬에서는 실시간 베이크가 불가능한데 ... 실시간 베이크 혹은 그에 준하는 뭔가를 하고싶었어요

무엇보다 레테는 맵이 움직이고 그게 게임의 아이덴티티였거든요

그런데 캐릭터의 조작을 키패드 이런걸로 하자니 또 게임과는 반하는 것이었음

왜냐하면 전에도 어디선가 말한거같은데 큐브 뒤에 숨겨두거나 이런게 많았고

큐브 조작을 다른 게 아니라 터치로 조작하는데 이동을 키패드로? 라는건 좀.. 이상했기에...

 

그래서 캐릭터는 무조건 A* 알고리즘으로 움직여야 할 ... 필요가 있었고,

거기에 더해 길 자체가 이동하며 맵과 메쉬가 갱신되기때문에 실시간 베이크가 필요했어요

그런데 A* 알고리즘을 짜는것부터 시작하자니 지원사업 기간 내에 맞출 시간은 안 될 것 같고 해서

에셋을 검색하던 중에 이걸 보게됐고요

 

영상에서도 나오지만 Play 상태에서... 지나갈 수 있는 길 위에 갑자기 장애물이 생기거나 할 때에

갈 수 있는 메쉬 표시가 변경되는게 바로 보이더라고요!!! 아 이거다 싶었죠

그래서 샀습니다... ... ... 남이 만들고 꾸준히 업데이트 해주는 에셋을 쓴다는건 좋구나

(이상 업뎃 안 되는 에셋 샀다가 유니티 버전 달라서 버그 나서 어쩌구 했던 사람이)

 

 

A* Pathfinding Project

Ary and the Secret of Seasons Take control of the seasons and use them to solve puzzles, defeat enemies, and discover the beautiful world of Valdi in this award-winning indie adventure. Ary and the Secret of Seasons won the Best Unity Game award at gamesco

arongranberg.com

이 에셋의 가장 큰 장점... 문서화가 잘 돼 있다! 끝내주는 설명서.

위 페이지 메뉴에서 Documentation 을 통해 들어가면 진짜 잘 된 문서화가 나옵니다.

처음엔 이거 대충 보고 하다가 왜 안되는거임? 뭐임? 어케하는거임? 이러고 헤매다가 아무것도 못했는데

다시 해보자고 마음먹고 문서 정독부터 시작하다가 갑자기 깨달음을 얻어서

그 뒤로는 빠르게 진행됐네요 (헤맸던 시절이 어이없을 만큼의 속도였어요 ㅋㅋ)

 

그리고 길찾기에 있어서 정말 다양한 상황에 쓸 수 있달까?

일단 저한테는 실시간 길찾기가 된다는게 진짜 ... 대박이었습니다 ...

 

전에 어디선가 에셋을 사기 전에 생각해야 할 것은

이 기능을 직접 구현하는데에 드는 시간과 노력 vs 에셋을 구매하고 적용하는데에 드는 시간과 노력

... 같은 거라고 얘기를 들은 적이 있는데

저는 이걸 네비메쉬로 구현할때도 생각보다 오래 걸렸고 (일단 경우의 수를 다 계산해야 한다는 점이...)

그래도 깔끔하게 안 됐던걸 생각하면 제게 이 에셋의 경우는 역시 후자가 효율이 좋았다고 할 수 있네요

그래도 전자의 경우를 하면서 배움이 아주 없었던 것은 아니니 또 괜찮기도 해

근데 거대한 상업에서 이렇게 하면 망하겠지 (...)

 


 

에셋의 원리 (내가 이해한)

일정 영역을 지정한 행과 열로 나누고, 생긴 칸의 중심에 레이캐스트를 합니다.

그래서 "갈 수 있음"으로 지정한 메쉬가 해당 레이에 충돌하면 거기를 경로에 추가하는 식.

여기선 칸을 나누는 형태와 추가된 경로를 그래프라고 하고, 레이캐스트 하는 걸 '스캔'이라고 해요.

목적지 지정은 스크립트로 하고, 이동 스크립트와 보간, 설정에 따라 캐릭터를 이동시킵니다.

 


 

그래프

그래프 형태에는 그리드, 육각 등 여러가지가 있는데, 그건 문서에서 설명하고 있고...

레테에서는 레이어드-그리드 그래프를 사용했어요. 그리드 그래프가 겹쳐진 형태입니다.

그리드 그래프에서는 겹쳐진 길..을 계산하질 못한다길래.

가령 이런 것입니다. 일반 그리드 그래프를 쓰면 이 밑에는 갈 수 없는 길이 되어버려요.

그래프 갱신 기능으로 위쪽 큐브를 치우면 그만이긴 한데, 그러면 큐브가 저런 식의 길은

완전히 지나갈 수 없게 되어버리기때문에 ... 레이어드-그리드 그래프를 사용했습니다.

문서에 나와있는 그래프 설정 으로 설정을 마치고 스캔을 하면 됩니다.

레테는 큐브가 그리드 한 칸에 맞도록 설정했습니다.

위 이미지에서 보이는 유독 외곽선이 짙은 부분은 보이는 옆 큐브와 높이가 다른 경우.

그래프 사용 시 주의하셔야 할 점.

그래프에서 말하는 레이의 길이는 위에서부터 그래프 베이스까지~ 이기때문에,

그래프 베이스는 캐릭터가 다닐 수 있는 가장 낮은 곳에 맞춰주셔야 합니다.

낮은 곳이라곤 해도 기본 레이 길이 100이니까 뭐 ...

그리고 대충 충돌만 나오면 되는거라 굳이 메쉬 끝까지 맞춰주실 필요는 없어요 위에 살짝 걸치기만 해도 OK.

그런데 아무래도 ... 면적이 좁아서인가? 원인은 잘 모르겠으나 하여튼 이런 오류가 있었어요

스캔을 해도 거기에 그게 있다고 계산을 안하는? 못하는?

그랬는데 이건 그냥 모든 오브젝트에 실시간 그래프 업데이트를 넣어줬더니

Awake 하면서 이게 자동으로 실행되는지 플레이 상태에선 제대로 되더라고요


실시간 베이크

이 에셋을 산 가장 큰 이유! 흑흑 정말 감동의눈물콸콸이라니까

런타임에서 그래프를 갱신하는 방법은 여럿 있습니다. 문서를 참고하세요.

 

레테에서 택한 건 DynamicGridObstacle이에요.

이 스크립트가 붙은 객체가 생성되거나 할 때 이 객체 주변의 그래프를 다시 불러오는건데,

그래서 위에 말했던 면적이 좁아지는 문제가 Play 상태에서 이 스크립트의 Awake에 그래프를 갱신한거죠.

또 다른 곳에서 호출해서 그래프를 갱신할 수 있는 메서드가 있기 때문에,

레테에서는 큐브를 움직이는 오브젝트가 큐브 오브젝트의 해당 스크립트를 참조해서 그래프를 갱신하게 합니다.

진짜 쥑이는 기능이다 (눈물찍!)


캐릭터

캐릭터 컨트롤러가 필수적이라 네비메쉬 + 리지드바디(콜리더)를 쓰던 저는

충돌 스크립트를 전부 바꿔야 할 필요가 있었습니다.

캐릭터 컨트롤러에 맞게 ... OnTriggerEnter 로 ...

뭐 이건 별로 어려운 건 아니니깐


이동

이동 방법에는 여러가지가 있는데, 각각에 대해 자세한 설명은 문서에서 설명하고 있으니 패스.

레테에서는 AiLerp를 사용했어요. 이게 가장 이상적인 이동법이라고 생각했거든요.

 

그래프에서 Connections를 4 또는 8로 정할 수 있는데 4로 하면 직각느낌도 살고 좋더라고요

레이어드 그래프에서는 4밖에 안돼서 선택지가 없기도 했지만 (ㅋㅋ) 맨처음 그리드로 했을때부터 4였습니다.

레테가 큐브의 가운데를 걷게 하고 싶어서 큐브를 5×5 그리드로 쪼개기도 하고 별 짓을 다 했는데

(별개로 이 방법은 걸을때는 가운데를 걸을지언정 길 끝에 도달하면 끝으로 달려가버려서 실패)

Seeker에서 설정을 바꿀 수가 있더라고요. 하... 사용설명서를 잘 읽자...

 

여튼. 그러게 해서 했습니다.

이동 스크립트에서 목적지를 설정하는 스크립트는 원래 쓰던 거에 붙여서 바꿨고요


막다른 길

그런데 이게 애니메이션을 "목적지에 도착할때까지 계속 걷기"로 했더니

막다른 길, 레테에서는 이제 끊어진 길에 도착해서 움직이지 못할때에도 계속 걷더라고요

왜냐하면 실질 목적지는 이 곳이 아니니까 (하...)

그런데 또 그걸 체크할 수 있는 변수가 있더라고 (우아악-!)

 

그래서 처음엔 이렇게 썼어요

1
2
3
4
5
6
if (aiLerp.reachedDestination || aiLerp.reachedEndOfPath)
{
    Destroy(cloneClickEff);
    charaAni.SetBool(animatorIdWalk, false);
    footstepEff.Stop();
}
cs

reachedDestination은 목적지 도달을 체크하는 거고, 이게 true면 목적지에 도착했단 뜻이니

애니메이션을 꺼주고 ... 목적지 표시 이펙트도 삭제합니다. 효과음도 멈추고.

목적지에 도착한 게 아닐때는 reachedEndOfPath를 쓰면

목적지에 안 왔는데 길이 끊겨서 더 나아가지 못하는 상태인가?를 체크할 수가 있더라고요

 

그랬는데 또 문제가 있었음

reachedEndOfPath는 움직임이 멈추고 나서인가? 그때에야 '목적지' 참조가  바뀐다는거예요

그래서 캐릭터가 시작-A-B(끊어진길) 순으로 이동을 하고 이 함수를 Update 문에서 돌린다고 치면

A까지는 작동을 잘 해요 왜냐하면 A는 목적지에 도달을 했으니까

그런데 B로 가려고 목적지를 넣어주고 이동을 시작하는 순간 이 if문으로 빠진단 말이죠

왜냐하면 목적지 갱신이 안 된 상태에서 reachedEndOfPath 계산이 들어가버리는거야

그래서 목적지는 A인상태에서 이동을 시작한 프레임에 업데이트문이 돌아

aiLerp.reachedEndOfPath == true가 되어버리는 이런 ... 상황이 발생해버리는 것입니다

 

그래서 또 수정을 하고 또 수정을 하고 ... 그렇게 여러번 고친 결과

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private IEnumerator DetinationCheck()
{
    yield return new WaitForSeconds(1f);
 
    while (!aiLerp.reachedDestination)
    {
        if (aiLerp.reachedDestination)
            break;
        else if (aiLerp.reachedEndOfPath)
            break;
 
        yield return null;
    }
 
    Destroy(cloneClickEff);
    charaAni.SetBool(animatorIdWalk, false);
footstepEff.Stop();
}
cs

이런식으로 딜레이를 주는 방법을 썼습니다.

이건 이동 타겟을 잡은 뒤 실행되는 코루틴인데, 코드 시작 앞에 1초 정도 딜레이를 줘서

이 코드가 실행되는건 실질적으로 타겟을 설정하고 이동을 시작한, 1초 정도 뒤입니다.

캐릭터의 이동 속도를 생각했을때 대략 한 칸을 움직이는데 1초가 좀 안 걸리므로 1초라는 기준.

어쨌든 이때에 목적지에 도달했는지를 체크하고, 목적지나 막다른 길에 도달했다면

걷기를 멈추고 애니메이션을 멈추고 효과음을 끄고... 뭐 그러는거죠

 


이거 외에 딱히 더 손 댄 게 없는 것 같네 기억을 못하는건가? 근데 실제로 별 거 안 바꾸긴 했어요

다음에 또 뭐 생각나면 써보고.

어쨌든 정말 최고의 에셋 추천합니다

댓글