본문 바로가기

언리얼 개발자

[Unreal] 비동기[Async] PathFinding

https://onecoke.tistory.com/entry/Unreal-PathFinding-Following-Component-%EA%B5%AC%ED%98%84

 저번에는 길 찾기를 요청하는 부분과 길을 따라 이동을 시키는 부분들을 구현했었습니다.

이번에 구현할 것은 '길 찾기 요청' 부분을 비동기로 처리하는 것입니다. 즉, NavigationSystem에 길 찾기를 요청한 타이밍과 길 찾기 결과를 받는 타이밍이 같지 않게 됩니다. 비동기로 길 찾기를 수행하는 기능은 이미 엔진에서 제공하고 있으므로 저희는 사용만 하면 됩니다.

 

[ Sync PathFinding ]

 

[ Async PathFinding ]

[ 비동기 길 찾기 장단점 ]

 먼저 길 찾기를 비동기로 처리했을 때 느꼈던 장점단점에 대해서 간략히 적고 가겠습니다.

[ 장점 ]

    * 동시에 많은 길 찾기가 수행되었을 때 게임이 끊기는 문제를 해결할 수 있다.

[ 단점 ]

    * 비동기 처리로 인해 추가적인 예외 처리들이 필요해진다.

    * 길 찾기 동기 처리와 섞어서 사용할 경우 좀 더 복잡해진다.

 

 수 많은 캐릭터들의 길 찾기를 수행해야하는 경우 비동기 길 찾기를 추천합니다.

 

[ 핵심 함수 ]

 기능을 사용하기 위한 언리얼의 핵심 함수는 아래의 두 함수입니다.

1. uint32 UNavigationSystemV1::FindPathAsync()

2. void UNavigationSystemV1::AbortAsyncFindPathRequest()

* FindPathAsync() 함수는 길 찾기를 비동기로 요청하는 함수이고 요청에 대한 ID를 발급해줍니다.

* AbortAsyncFindPathRequest() 함수는 비동기 길 찾기 요청을 취소시킬 때 사용하며 1번 함수에서 발급했던 ID를 통해 취소를 시킵니다.

* 만약 비동기 요청 자체에 실패했을 경우 리턴하는 ID는 INVALID_NAVQUERYID를 리턴합니다.

 

 FindPathAsync 함수에는 길 찾기 결과를 받기 위해서 인자에 델리게이트, FNavPathQueryDelegate를 요구합니다.

 FNavPathQueryDelegate 는 세 가지 변수를 보내줍니다.

1. uint32 : 비동기 길 찾기 요청 ID

2. ENavigationQueryResult::Type : 길 찾기 결과

3. FNavPathSharedPtr : 경로 데이터

 

[ 사용법 ]

 저희는 저렇게 세 인자를 받는 콜백 함수를 만든 후 FindPathAsync() 함수 델리게이트에 콜백 함수를 연결해주면 됩니다.

 위의 예시는 OnPathFindingAsync() 함수를 만든 후에 FNavPathQueryDelegate::CreateUObject()를 통해 델리게이트를 만들어 전달해주고 있습니다.

 'PFQuery'의 자료형은 FPathFindingQuery로, 길 찾기에 필요한 데이터들이 모여있습니다. 데이터를 채우는 방식은

1. 깃 허브에 올린 코드의 URequestMoveComponent::BuildPathfindingQuery()

2. 엔진의 AAIController::BuildPathfindingQuery() 

둘 중 아무 함수나 참고하시면 됩니다.

 

 OnPathFindingAsync() 에서 결과를 받았다면 아래와 같이경로 이동 기능을 담당하는 PathFollowingComponent에 전달해주면 됩니다.

  빨강색 네모칸에 있는 것처럼 길 이동을 요청해주시면 됩니다. m_CachedPathFollowingComponent는 엔진의 PathFollowingComponent 와 같다고 보시면 됩니다.

 노란색 박스 내에 있는 코드가 의미하는 구조체는 FAIMoveRequest 입니다. FAIMoveRequest와 관련해서 추가 설명을 하자면, 언리얼에서 제공하는 PathFollowingComponent는 이동 요청을 받을 때 두 개의 인자를 요구합니다.

1. FAIMoveRequest : 이동에 필요한 추가적인 데이터가 모여있는 구조체

ex) 목적지, 목적지에 Nav 검사 여부, 도착했다고 판단할 거리, etc..

2. FNavPathSharedPtr : 경로 데이터

 

 FAIMoveRequest 데이터를 채우는 함수는

1. 깃 허브에 올린 코드의 URequestMoveComponent 내에 있는 아무 MoveTo 함수

2. AAIController::MoveToLocation(), AAIController::MoveToActor()

위의 함수들을 참고하시면 됩니다.

 

[ 결과 ]

 * 테스트 환경

1. 동시 1600번의 길 찾기 요청

2. 실제 캐릭터가 이동하지 않도록 처리

 * 왼쪽이 동기, 오른쪽이 비동기 처리이며 화면 좌상단에 각각 빨간색, 초록색 로그가 뜰 때마다 길 찾기를 수행.

 * 왼쪽 화면이 상대적으로 더 끊기는 것을 확인할 수 있습니다.

 

[ 마치며 ]

 비동기 길 찾기 기능은 엔진 기능을 보거나 구글링을 통해 쉽게 사용할 수 있으나 이 기능을 사용하면서 생기는 예외처리들이 골치 아플 때가 많았습니다. 작업하면서 느꼈던 문제들을 간략하게 써놓으려고 합니다.

 

1. 비동기 길 찾기가 완료된 후에 기존 이동을 종료시키기

    : 비동기 길 찾기 요청과 동시에 기존 이동을 종료시키게 되면 비동기 길 찾기 완료 전까지 이동 인풋이 없어 캐릭터가 순간 Velocity가 낮아지면서 움직임이 끊기게 됩니다.

 

2. 연속적인 비동기 길 찾기 & 이동에 대한 처리

    : A 이동을 완료한 후 B 이동을 수행해야할 때 비동기로 처리되는 경우 중간에 이동 인풋이 비어 캐릭터의 움직임이 끊기게 됩니다. 결과가 오기 전까지 특정 방향으로 인풋을 강제로 주는 등 매끄러운 이동을 위한 처리들이 필요합니다. 캐릭터의 움직임이 끊기는 원인은 1번에서 말한 Velocity가 낮아지는 원인과 동일합니다.

 

3. 비동기 요청 Abort 를 잊지 말기

    : 비동기 길 찾기 요청과 결과를 받는 사이에 새로운 요청이 올 수 있다. 기존의 비동기 요청을 잊지 말고 지워야 합니다

    : 객체의 삭제 때도 반드시 비동기 요청이 있다면 Abort 해주기. 하지 않을 시 크래쉬를 발생시킨 범인이 됩니다..

 

4. Abort 예외 조심하기

    : 비동기 길 찾기 결과 콜백을 받기 전에 Abort를 해도 이미 처리가 되어 결과 콜백이 오는 경우가 있습니다. 콜백으로 오는 Query ID가 내가 요청한 Query ID와 동일한지 비교해주어야 합니다.

 

5. 협업을 위한 기능 제공

    : 나만 쓰면 상관 없겠지만, 협업자가 이 기능을 가져다 쓸 때에는 여러가지 제약이 생길 수 밖에 없습니다. 편의성을 위해 정보를 최대한 제공해야 합니다. 아래의 두 콜백은 필요했고 콜백을 통해 제공할 필요한 데이터는 다를 수 있습니다.

        - 길 찾기 완료 콜백

            * 비동기 길 찾기 요청 ID

            * 길 찾기 결과 ( 성공 / 실패 )

            * 이동 요청 결과 ( 성공 / 실패 )

            * 이동 ID ( 이동 요청 성공 시 )

            * 경로 데이터 ( 필요에 따라 )

 

        - 이동 완료 콜백

            * 이동 ID -> PathFollowingComponent에서 발급

            * 이동 결과 ( 도착 / 실패 ( 실패 원인 ), etc.. ) -> PathFollowingComponent에 Result 구조체

 

[ Git 주소 ]

https://github.com/skyjpower/FunctionTestProject