AIController를 상속받는 cpp클래스를 만들고 이를 기반으로 BP를 만들어서
AI가 빙의할 폰이나 캐릭터의 BP로 가서 디테일탭의 폰 세션에서 AI컨트롤러를 설정해준다
그러면 게임이 시작될때 이를 위한 AI컨트롤러 객체가 자동으로 월드에 배치된다.
AI의 기억과 같은 느낌
AI에서 사용하는 많은 변수를 저장하며 이를 CPP에서 접근하여 지정가능하다.
블랙보드 에디터에서 키를 설정 할 수 있고,
if(AIBehavior != nullptr)
{
RunBehaviorTree(AIBehavior);
GetBlackboardComponent()->SetValueAsVector(TEXT("PlayerLocation"), PlayerPawn->GetActorLocation());
}
그 키의 값을 SetValueAs(타입) 계열 함수로 설정해줄수있음
https://bbagwang.com/uncategorized/ue4-%EC%97%90%EC%84%9C%EC%9D%98-behavior-tree/
https://wergia.tistory.com/147
행동 트리를 블루프린트처럼 노드 단위로 가져와 사용할 수 있다.
언리얼 엔진에서 행동트리를 사용하기 위한 5가지의 기본 노드를 제공한다.
컴포짓 : https://api.unrealengine.com/KOR/Engine/AI/BehaviorTrees/NodeReference/Composites/index.html
데코레이터 : https://api.unrealengine.com/KOR/Engine/AI/BehaviorTrees/NodeReference/Decorators/index.html
서비스 : https://api.unrealengine.com/KOR/Engine/AI/BehaviorTrees/NodeReference/Services/index.html
태스크 : https://api.unrealengine.com/KOR/Engine/AI/BehaviorTrees/NodeReference/Tasks/index.html
해당 분기가 실행되는 기본 규칙을 정의한다.
데코레이터를 통해 분기로 들어가는 조건을 변경하거나 중간에 실행이 취소되게 만들거나,
서비스를 덧붙여서 컴포짓 노드의 자손이 실행되는 동안 서비스가 작동되도록 만들 수 있다.
자식 노드를 실행하여 하나라도 true를 리턴하면 true를 리턴한다
무언가 하나라도 성공할 때 트리에서 나오게 된다.
모든 자식 노드가 true를 리턴할때 true를 리턴하게 된다
무언가 실패할 때 트리에서 나오게 된다
단순병렬노드라고도 하며 전체 노드트리와 동시에 하나의 태스크를 실행할 수 있다.
Finish Mode설정을 통해 메인 테스크가 완료되면 서브트리를 중단시키고
즉시 노드를 완료시킬지, 아니면 서브트리가 완료될때까지 딜레이 시킬지 설정할 수 있다.
컴포짓 노드의 자식노드들은 왼쪽에서 오른쪽부터 수행하며
모든 노드를 수행했고, 트리에서 빠져나오지 않을 시, 다시 첫번째 자식 노드로 돌아가게됨
위 사진의 보라색으로 표현된, 무엇인가를 수행하는 노드
시퀀스의 A로 표시된 부분에서 끌어오거나, 마우스 우클릭-Task에서 가져올수있음
태스크 노드에도 데코레이터가 붙을 수 있다
기본으로 제공하는 Task 노드 중 하나
지정된 좌표로 이동하는 기능을 수행한다
블랙보드의 observe blackboar value를 체크해주면
플레이어의 위치가 변경될때마다 바로바로 값을 업데이트하여 이동한다
시퀀스와 셀렉터를 마우스 우클릭 - 데코레이터 추가를 누르면 데코레이터를 서브노드로 추가할수있다.
미리 정의된 여러 데코레이터를 붙일 수 있으며 이를 통해 if문과 같은 작업을 하게된다.
위 예의 내용은 블랙보드 데코레이터(블랙보드의 특정 Key가 Set되어있는지, 아닌지 판단하는 데코레이터)를 사용하여
PlayerLocation이라는 키가 Set되어있으면 실행한다는 의미이다.
CPP 구현예
ClearValue()함수로 키의 값을 Clear 할 수 있다.
이 함수는 FName()로 직접 키의 이름을 지정하거나
GetSelectedBlackBoardKey()를 사용하여 에디터 내에서 드롭박스로 선택된 키를 지정할수있다
데코레이터를 클릭하고 디테일 패널을 보면 여러 프로퍼티를 선택할 수 있는데,
관찰자 중단(Observer aborts)에서 보면 드롭다운에서 4가지의 속성 중 하나를 선택할 수 있는데
Self
이 조건이 갑자기 거짓이 되면 하던일을 멈추고 부모노드로 돌아가 다시 평가함
Lower Priority
형제 노드가 true가 되면 현재 하던일을 멈추고 참이 된 형제노드를 실행
Both
둘다
BT_Decorator을 상속받아 새 C++만들기
protected:
virtual bool CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const override;
//if문처럼 사용하고 싶다면 이 함수를 오버라이드 하여 사용
UBTDecorator_bFire::UBTDecorator_bFire()
{
NodeName = TEXT("발사가능?");
}
bool UBTDecorator_bFire::CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const
{
Super::CalculateRawConditionValue(OwnerComp, NodeMemory);
AShooterCharacter* ShooterCharacter = Cast<AShooterCharacter>(OwnerComp.GetAIOwner()->GetPawn());
bool isFire = ShooterCharacter->Gun->Ammo > 0;
return isFire;
}
프로젝트이름.Build.cs로 가서 "GameplayTasks" 모듈 추가**
BTTask_ 로 시작하는 클래스들 중 원하는 클래스를 상속받아 cpp파일 생성**
(BlackBoard관련 프로퍼티가 필요하다면 BlackBoard를 상속받는 등)
에디터에서 보기 편하도록 생성자를 생성하고,
UBTTask_ClearBlackboardBase::UBTTask_ClearBlackboardBase()
{
NodeName = TEXT("Clear BlackBoard Value");
}
를 통해 이름을 보기 쉽게 바꿔준다.
컴파일하면 BT 에디터 내에서 끌어올 수 있다
커스텀 Task노드 구현하기
UTaskNode에 정의되어 있는 함수가 4종류 있다.
EBTNodeResult::Type UBTTask_ClearBlackboardBase::ExecuteTask(UBehaviorTreeComponent& OwnerComp,
uint8* NodeMemory)
{
Super::ExecuteTask(OwnerComp, NodeMemory);
OwnerComp.GetBlackboardComponent()->ClearValue(GetSelectedBlackboardKey());
return EBTNodeResult::Succeeded;
}
EBTNodeResult::Type UBTTask_Shoot::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
Super::ExecuteTask(OwnerComp, NodeMemory);
AShooterCharacter* AIShooterCharacter = Cast<AShooterCharacter>(OwnerComp.GetAIOwner()->GetPawn());
if(!AIShooterCharacter)
{
return EBTNodeResult::Failed;
}
AIShooterCharacter->Shoot();
return EBTNodeResult::Succeeded;
}
컴포짓 노드에 분기가 실행되는 동안 정해진 빈도에 맞춰서 실행된다
틱과 비슷하지만 주기 등을 직접 설정해 줄 수 있기 때문에 효율적
보통 검사를 하고 그 검사를 바탕으로 블랙보드의 내용을 업데이트 하는 데 사용된다.
새 서비스 노드를 만들기 위해서 BTService 클래스를 상속받아 새 클래스를 만든다.
블랙보드의 값을 수정해야 한다면 BlackBoardBase를 상속받자
Task 노드와 마찬가지로, 필요한 함수를 오버라이드하여 기능을 구현하자
UBTService_PlayerLocationIfSeen::UBTService_PlayerLocationIfSeen()
{
NodeName = TEXT("플레이어가 발견되면 플레이어 위치 업데이트");
}
void UBTService_PlayerLocationIfSeen::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds);
const AAIController* ThisAiController = OwnerComp.GetAIOwner();
const APawn* PlayerPawn = UGameplayStatics::GetPlayerPawn(GetWorld(),0);
if(!ThisAiController || !PlayerPawn)
{
return;
}
if(ThisAiController->LineOfSightTo(PlayerPawn, FVector::ZeroVector))
{
OwnerComp.GetBlackboardComponent()->SetValueAsVector(GetSelectedBlackboardKey(),PlayerPawn->GetActorLocation());
}
else
{
OwnerComp.GetBlackboardComponent()->ClearValue(GetSelectedBlackboardKey());
}
}