Behavior Tree : AI가 행동을 결정할 규칙을 정의내리는 방법 중 하나
Behavior Tree의 구성요소
Root : 시작점을 의미함
Selector : 선택하는 역할, Selector의 하위에 연결되어 있는 실제 행동들이 조건에 부합하지 않으면 다른 행동을 선택하게 함
Sequence : 하나의 행동이 끝나면 다른 행동을 연달아 실행되게 함
Parallel : 몇 가지가 됐건 연결된 작업들을 동시에 실행시킴
Task : 실제로 움직이는 등의 행동을 뜻 함
Service : 데이터를 갱신하는데 자주 사용됨. Task가 일어나는 동안 계속 해서 실행됨
Decorator : Task에 추가하는 것을 조건으로 부여하여 기능을 없애거나 추가시킬 수 있음
AI캐릭터를 하나 생성해주고 이 캐릭터 주위에 플레이어가 가까워지면 플레이어에게 다가가고 아니면 랜덤한 위치로 이동하는 비헤이비어 트리를 만들어보자.

비헤이비어 트리를 만들려면 콘텐츠 브라우저에서 우클릭 -> 인공지능 -> 비헤이비어 트리를 선택하면 된다. 이름을 BT_AICharcter라고 지어준다.

생성해준 다음 들어가보자.

들어가면 이벤트 그래프가 보이는데 여기서 Root에서 끌어와 Selector를 생성하고 다시 Selector에서 끌어와 Move To노드를 두 개 생성해준다.
그런데 Move To노드를 보면 대상이 Invalid로 뜬다. 이는 아직 블랙보드를 생성해주지 않았기 때문이다. 비헤이비어 트리는 블랙보드를 통해 데이터를 저장, 읽기가 가능하다.

상단에 새 블랙보드 버튼을 클릭하여 새로운 블랙보드를 생성해보자.
(혹은 콘텐츠 브라우저에서 우클릭 -> 인공지능 -> 블랙보드를 생성하고 비헤이비어트리의 디테일 바에서 블랙보드 에셋을 지정해줄 수도 있다.)

블랙보드 이름은 BB_AICharacter로 지어준다.

그럼 비헤이비어 창의 우측 상단에 블랙보드 버튼이 활성화된 것을 볼 수 있다.

새로운 키를 만들고 이름을 TargetPosition으로 지어준다. 그리고 타입을 벡터로 설정해준다. 이미 저장되어 있는 SelfActor는 이 블랙보드 데이터를 가진 액터가 자동으로 저장되는 키다.


다시 비헤이비어 트리로 돌아가서 두 개의 Move To노드를 눌러 우측 디테일 바의 블랙보드 항목에서 블랙보드 키를 TargetPosition으로 바꿔준다.

이렇게 되면 두 행동의 역할이 같아지기 때문에 조건을 부여해줘야 한다.
그래프 상단에 새 데코레이터 버튼을 클릭한 다음 BTDecorator_BlueprintBase를 선택해준다.

그럼 새로운 에셋을 저장하라고 하는데, 이름을 BTD_IsNearPlayer라고 지어주고 저장한다.

이렇게 하면 새로운 창이 뜨는데 일단 저장해주고 비헤이비어 트리로 돌아가자.

왼쪽 Move To노드를 우클릭하면 데코레이터 추가 항목이 있는데 이를 눌러주면 아까 생성한 BTD_IsNearPlayer가 존재한다. 이를 추가해준다.
다시 저장해준다.

이렇게 되면 BTD_IsNearPlayer상태일 때는 TargetPosition으로 Move To를 실행하게 되고 아닌 경우 오른쪽의 Move To노드를 실행하게 된다.
이 TargetPosition을 어떻게 설정하느냐에 따라서 달라지게 된다.
이렇게 되면 이 두 노드에서 TargetPosition을 설정하는 방법이 달라야 한다. 왜냐면 두 방식이 다르지 않으면 나눠놓은 의미가 없기 때문이다.

AI가 붙은 항목들은 AI컨트롤러가 조종할 때 우선 실행되는 버전이다.

먼저 BTD_IsNearPlayer를 설정해주도록 하자. BTD_IsNearPlayer를 더블클릭하여 들어간다.
여기서 왼쪽 내 블루프린트 창에서 함수 오버라이드를 선택해 PerformConditionCheck를 선택한다. 이 값이 true면 이걸 실행시키고 false면 실행시키지 못 하게 한다.

이제 플레이어가 가까이 있는지 검사를 해봐야 한다.
먼저 PerformConditionCheck노드를 끌어와 Sphere Overlap Actors을 검색하여 생성해준다.
이 노드는 구 형체를 오버랩시키고 이 구 형체와 겹치는 부분을 판정해서 어떠한 액터들이 들어있는지 확인하는 장치이다.
이 때 구 형체의 중심좌표는 바로 이 블루프린트를 가지고 있는 액터여야 한다.

Owner Actor에서 끌어와 Get Actor Location을 검색하여 노드를 생성한다. 이 노드를 Sphere Overlap Actors의 Spher Pos항목과 연결시켜준다.
다음으로 구 반경은 10m로 설정해줄 것이기 때문에 1000으로 설정해준다.

Object Types는 배열로 받기 때문에 이 항목에서 끌어와 Make Array를 검색하여 노드를 생성해준다. 그러면 어떠한 항목들을 받을지 선택할 수 있는데 우리는 폰 액터가 존재하는 지를 검색하기 때문에 Pawn을 선택한다.

그럼 우변의 Out Actors에서 검출된 액터가 배열로 나오게 된다. 그래서 이 액터 배열에 주인공이 있기만 하면 된다. 여기서 끌어와 Find Item을 검색하여 노드를 생성한다.
그리고 find노드 좌변 아랫쪽을 끌어와서 Get Player Pawn을 검색하여 생성한다.
만약 플레이어가 있는 걸 감지하면 배열의 인덱스를 반환하고 감지되지 않으면 -1을 반환하게 된다.

여기서 Sphere Overlap Actors의 우변을 끌어와서 Branch노드를 생성한다. == 노드를 생성하고 만약 Find노드의 검사 결과가 -1이면 Return Node의 값이 false여야 하고, 아니면 True여야 한다.

Return Node를 복사 붙여넣기하여 하나 만들어주고 True에 하나 False에 하나 연결해준다.
그 다음 False에 연결된 Return Node의 Return Value에 체크 표시를 해준다.

이젠 TargetPosition을 지정해보자
그래프에서 Get Blackboard를 검색하여 노드를 생성해주고 좌변을 PerformConditionCheck의 Owner Actor에서 끌어와 이 노드에 좌변으로 연결한다.

그 다음 Get Blackboard의 우변에서 끌어와 Set Value as Vector를 검색하여 노드를 생성한다. 이는 블랙보드의 안의 값을 벡터로 저장하는 것인데 무슨 값인지 알기 위해서 Key Name을 설정해주어야 한다. String To Name을 검색하여 노드를 생성한다.
이제 이 블랙보드에 해당 스트링 값을 가진 것을 Name으로 변환하여 이 변수에 벡터값을 저장할 수 있게된다.
우리가 바꿀값은 TargetPosition이기 때문에 TargetPosition으로 바꿔준다.

이 벡터엔 플레이어 Pawn의 위치가 들어가야 한다. Get Player Pawn의 노드를 생성하고 이 노드의 우변에서 Get Actor Location을 생성한 다음 다시 이 노드의 우변을 Set Value as Vector을 생성한 다음 다시 이 노드의 우변을 Set Value as Vector의 Vector Value에 연결한다.

이걸 Branch노드의 False와 연결해주고 다시 이 노드를 Return Node와 연결해준다.
그후 컴파일/저장해준다.
이제 BT_AICharacter로 다시 가보면 BTD_IsNearPlayer의 조건이 맞으면 타겟 포지션으로 움직이는 동작이 일어나게 된다. 여기서 타겟 포지션은 플레이어 폰의 위치로 설정되어 있다.
그럼 오른쪽 Move To에도 TargetPosition을 알려줘야 하는데 오른쪽 Move To노드를 클릭하고 새 서비스를 클릭한다. 이 서비스는 Move To노드가 실행될 때 같이 실행되는 서비스를 뜻한다.

이름을 BTS_FindingRandom으로 이름을 지어주고 생성한다.



이제 함수 오버라이드를 해줘야 하는데 활성화가 되었을 때부터 구현하기 위해서 함수 오버라이드를 선택하여 Receive Activation을 눌러주면 Event Receive Activation노드가 생성된다.
여기서 TargetPosition을 랜덤하게 생성해줘야 한다.
이를 위해선 내비게이션이란 걸 알아야 한다.
내비게이션이란 전체 맵에서 어느 한 위치로 갈 때 어떤 경로로 가야 최적의 경로인지 알려주는 내비게이션 시스템이라고 이해하면된다. 장애물이 있으면 그걸 피해가는 역할도 한다.

내비메시 바운드 볼륨이라는 액터를 하나 추가한다.

생성된 액터를 클릭하면 정육면체 모양이 생긴다.

이걸 트랜스폼 항목에서 위치를 0,0,0으로 이동시켜주고, 알파벳p를 눌러보면 영역이 표시되는 것을 볼 수 있다.

이 영역은 AI내비게이션 시스템이 움직일 수 있는 영역을 의미한다. 다시 p버튼을 누르면 꺼진다.

이 영역의 크기를 키우기 위해선 스케일을 키우거나 브러시 세팅에서 거리를 늘려주면 된다.
x와 y를 5000으로 늘려준다.
레벨을 저장해주고 BTS_FindingRandom으로 넘어간다.

Event Receive Activation은 오른쪽 Move To노드가 실행될 때 같이 실행되는 노드이므로 우변에서 끌어와 Get Random을 검색하면 GetRandomLocationNavigableRadius가 나온다. 이를 선택하여 노드를 생성한다.
이 노드는 특정 위치에서부터 반경을 설정하여 그 안에서 랜덤으로 포지션을 정해주는 역할을 한다.
이 랜덤한 포지션은 내비게이션 영역 안에 있어야 정상적으로 작동을 한다.
반경은 20m인 2000으로 설정한다.
기준은 Owner Actor이 기준이 되어야 하기 때문에 Owner에서 끌어와 Get Actor Location 노드를 생성한다.
이 노드의 Return Value를 Origin과 연결해준다.

노드의 우변을 보면 Random Location은 생성된 랜덤한 위치고 Return Value는 생성된 랜덤한 위치가 간혹 아예 갈 수 없는 위치를 가리킬 때가 있다. 그럴때 False가 반환된다.
Branch노드를 생성하여 위의 사진과 같이 배치한다.
그리고 Get Blackboard노드를 생성하고 Event Receive Activation의 Owner항목에서 끌어와 Target과 연결한다.

그리고 Return Value에서 끌어와 Set Value as Vector를 검색하여 생성하고 Key Name을 TargetPosition으로 지정해주기 위해서 String To Name노드를 생성하여 스트링 값을 TargetPosition으로 설정해준다. 그리고 Key Name과 연결해준다.

그 후 브랜치의 True에서 Set Value as Vector과 연결해주면 된다.
마지막으로 랜덤 위치를 생성하는 노드에서 Random Location을 Vector Value에 연결해준다.
이제 액터가 랜덤한 위치로 이동하면 다시 새로운 랜덤위치를 받아서 움직이게 해야한다.

다시 함수 오버라이드를 선택해 ReceiveTick를 선택한다.

그 후에 Tick노드의 Owner Actor항목에서 끌어와 Get Actor Location 노드를 생성한다.

현재 액터의 위치를 블랙보드에 저장된 위치와 비교해주어야 한다.
현재 액터의 블랙보드를 가져오는 Get Owners Blackboard노드를 생성해주고 Return Value에서 끌어와 Get Value as Vector노드를 생성하고 연결해준다. Key Name엔 똑같이 TargetPosition으로 지정해준다.

Get Value as Vector의 값과 Get Actor Location의 값을 빼주면 이 둘의 거리가 나온다. 빼기 노드를 생성하여 이 둘을 연결해준다. 그 다음 빼기 노드에서 끌어와 Vector Length노드를 생성한다. 이 노드는 벡터의 길이를 반환한다.

이 길이가 만약 10보다 작으면 다시 랜덤 위치를 설정해주도록 해야한다.
< 노드를 생성하여 연결해주고 값을 10으로 설정한다.

Branch노드를 생성하고 Branch의 좌변을 Tick노드와 연결시켜준다.


랜덤한 위치를 다시 찾는 건 아까 만들어 두었던 것과 동일하니 방금 것을 복사하여 붙여넣어 True에 연결해준다.

그리고 Get Actor Location과 Get Blackboard노드는 tick노드에서 Owner Actor와 연결해준다.
컴파일/저장해주고 BT_AICharacter로 돌아간다.

이제 오른쪽 Move To노드를 우클릭하여 서비스 추가를 선택하고 방금 만든 BTS_FindingRandom을 선택해준다.

그럼 랜덤한 위치를 선택하고 움직이는 동작을 수행하게 된다.
저장을 해주면 끝이다.