안녕하세요. '언리얼로 만들어보는 RPG'의 필자입니다. 7~10일 정도의 텀으로 글을 작성하려고 했으나 그건 욕심이 좀 컸던거 같습니다. 필자가 너무 게을러서 그런거겠지만 거두절미하고 바로 본론으로 들어가서 이번 포스팅을 시작해 봅시다!

 

 저번 작성글에서는 AIController와 AnimInstance를 구성하려고 했었으나, AIController를 설명하다가 언리얼에서 AI를 구성할때 사용하는 시스템들에 대한 소개글로 마무리를 지었습니다. 원래 흐름대로라면 AnimInstance를 해야겠지만 노선을 좀 변경해서 행동트리를 먼저 하도록 하겠습니다.

 

 이번 글을 작성하기전에 간략한 견적을 잡고 가도록 하겠습니다.

1. 적대상대가 없을시 임의 위치 산정해내는 Task 작성

2. 몬스터의 상태변경을 관리하는 Task 작성

3. 몬스터의 이동에 관여하는 Task 작성

4. 공격방식을 선택하는 Task 작성

 

 Task 노드의 작업은 위 4가지 정도로 생각이 되고 행동트리에서 사용할 변수들은 블랙보드에 하나하나 담아가 보도록 하겠습니다.

1-1) 작업을 통해 완성된 행동트리

 앞으로 작업을 통해서 만들게 될 행동트리의 결과물이 바로 위 사진입니다. 보시면 CustomMove라는 Task노드가 존재합니다. 언리얼에서 기본적으로 MoveTo라는 노드를 제공해 주나 작업당시 필자는 변화를 좀 주고싶었습니다. 자세한 내용은 차근차근 알아가 보도록 하겠습니다.

EMonsterState

1-2) 몬스터의 상태를 결정해줄 ENUM

UChangeState.h

2-1) ChangeState 헤더파일

 몬스터의 상태를 변경하는 ChangeState 부터 시작해보도록 하겠습니다. 헤더파일에서는 변경될 상태를 설정할 State변수와 블랙보드의 변수이름을 지정하는 StateName 변수가 있습니다. StateName변수의 경우 필자가 처음 작업한것이다 보니 아쉬운 부분이 많은데 그 내용은 CPP쪽에서 풀겠습니다.

UChangeState.cpp

2-2) ChangeState CPP파일

 ExecuteTask 함수에서는 컨트롤러, 블랙보드, 몬스터클래스 여부를 확인한 다음에 하나라도 통과를 못하면 즉시 결과를 실패처리 합니다. 필자도 약간 과하지 않나 싶기는 하지만 나중에 제거하자는 마음으로 남겨두고 그대로 잊었습니다. 사람인데 어떻게 하겠습니까?

 

 아무튼 블랙보드에 있는 변수설정은 타입별로 함수를 불러서 해줘야 하는데 이름과 변수를 인자로 받아들입니다. 아까 언급한 StateName이 여기서 불편함을 유발하게 되는것이죠. StateName은 에디터에서 작업자가 설정해 주어야 하는데 원하는 변수의 이름을 정확히 적지 않는다면 문제가 발생하기 마련입니다. 이런 문제는 블랙보드에 있는 변수를 직접 선택할 수 있도록 해줌으로써 해결이 가능한데 당시의 필자는 거기까진 찾아보지 않았다는게 안타까운 점이죠.

UBTTask_BlackboardBase.h

2-3) KeySelector의 존재!

UBTTask_MoveTo.cpp

2-4) KeySelector에서 선택 가능한 변수 필터추가

 2-3 사진을 보시면 FBlackboardKeySelector 라는 자료형을 볼 수 있습니다. 해당 자료형은 아까 언급한대로 블랙보드내에 있는 변수를 선택할 수 있도록 해주는 자료형입니다. 이 자료형을 이용했었다면 작업자가 일일히 블랙보드 변수의 이름을 적는 불필요한 작업을 하지 않을 수 있었겠죠.

 

 그리고 이 자료형을 쓰는게 좋은 이유는 2-4 사진에 나와있는것처럼 해당 KeySelector가 어떤 유형의 변수를 선택 할 수 있는지에 대해서 지정해 줄 수 있기때문 입니다. 필터 기능까지 이용한다면 더더욱 작업자의 실수로 인한 문제발생의 여지가 줄어드리라 믿습니다. 이 글을 보시는 여러분도 필자와 같이 이름을 입력하는게 아닌 KeySelector를 사용하면 좋겠습니다.

USetDestination.h

3-1) 뭔가 이상하다

 필자는 헤더파일을 보고 정신이 혼미해질 수 밖에 없었습니다. 몬스터의 상태를 바꾸는 Task를 작성해 두고서 임의위치를 산정하는 Task에서 상태를 바꾸려고 블랙보드에서 가져올 변수 이름을 받으려고 저러고 있습니다. 아 정말 아찔하네요 StateName 변수는 실제로 쓰지도 않고있으니 무시하시면 됩니다.

USetDestination.cpp

3-2) 굉장히 비효율적입니다.

 현재 사용하고 있는 방식은 0~360 사이의 각을 구해서 해당 방향벡터를 만들고 '정찰 범위/2 ~ 정찰 범위' 만큼의 값을 곱해서 해당 위치로 가는 그런 형태를 취하고 있습니다. 작동이야 하겠지만 좀 불필요한 내용이 많은것 같기도 합니다. 도달가능한 위치를 구하는건 GetRandomReachablePointInRadius라는 함수를 통해서 얻을 수 있기도 하고 말이죠

 

 그래서 이왕 마음에 안드는거 위에서 언급한 FBlackboardKeySelector를 이용해서 필자가 생각하기에 흡족한 방식으로 다시 작성해보기로 했습니다.

(New)USetDestination.h

3-3) 기존 헤더와는 다르게 뭔가 많아졌습니다.

 우선 DestKey는 블랙보드에서 목적지의 용도로 사용할 FVector형 변수를 선택하는데 쓸것입니다. 밑의 OriginKey의 경우 기준점을 스폰 당시의 위치로 하거나 현재 몬스터의 위치로 하거나 두가지 선택권을 가지게 할 것입니다. 마지막 변수는 정찰 범위를 의미합니다.

 

 그럼 이제 흔하지 않은 함수 InitializeFromAsset가 있습니다. 이친구의 역할은 필자가 블랙보드로부터 변수를 가져다 쓸것이기 때문에 행동트리에 블랙보드가 설정이 안돼있는 만일의 경우 생기는 문제를 막기위해서 사용합니다. 자세한 내용은 밑의 사진에서 확인해보겠습니다.

(New)USetDestination.cpp

3-4) 주석이 뭔가 짤린거 같은건 기분탓입니다.

 아까 언급한 함수는 노드를 초기화 하는과정에서 블랙보드가 없다면 경고 로그를 찍게됩니다. 이런식으로 블랙보드의 변수를 가져다 써야하는 노드이니 잊지말고 넣으라고 알려주는 작업이 되는것입니다.

 

 이 Task 노드의 가장 핵심부분인 실행 함수를 한번 살펴보도록 합시다. 여기서도 도움이 될만한 부분이 있는데요 빨간색으로 밑줄이 쳐있는 if문을 보시면 블랙보드 셀렉터가 선택한 변수가 어떤 타입인지 비교를 할 수 있습니다.

 

 이를 통해서 필자는 FVector형일때는 Origin변수에 블랙보드로부터 해당키 이름을 통해 값을 가져와서 대입하고 만약 아니라면 위에서 설정한것처럼 ACharacter형태의 Object만 설정이 가능하니 해당 액터로부터 위치를 가져와 대입합니다.

 

 여기서도 필자가 미흡하게 넘어간 부분이 존재하는데 만약 도달가능한 임의위치를 찾지 못했다면 현재 몬스터의 위치를 목적지로 설정해주는게 생길지 모르는 문제들을 막아줄 것 같습니다.

 

 이번 글은 여기까지만 작성하고 마무리를 하기전에 실제로 작성한 Task 노드를 사용하려고 빌드를 하면 성공이 뜨는걸 확인하실 겁니다. 하지만 에디터에서 확인해보면 노드는 보이지도 않고 혹시몰라 컴파일을 하면 LNK 2001, 2019 등의 링크에러가 뜨는걸 확인하실텐데요. 이유는 간단하게도 모듈을 추가해주지 않아서 입니다.

 

 UNavigationSystemV1클래스는 "NavigationSystem"모듈을 추가해주어야 하고 BTTaskNode를 상속받아 쓰려면 "AIModule"을 추가해야합니다. 다음글에는 남은 두개의 Task노드를 정리해보도록 하겠습니다.

Posted by 별수집가
,