언리얼 AI

MoOrY·2023년 2월 4일
0

언리얼 엔진

목록 보기
8/41

AI 컨트롤러

AIController를 상속받는 cpp클래스를 만들고 이를 기반으로 BP를 만들어서
AI가 빙의할 폰이나 캐릭터의 BP로 가서 디테일탭의 폰 세션에서 AI컨트롤러를 설정해준다
그러면 게임이 시작될때 이를 위한 AI컨트롤러 객체가 자동으로 월드에 배치된다.

블랙보드

AI의 기억과 같은 느낌
AI에서 사용하는 많은 변수를 저장하며 이를 CPP에서 접근하여 지정가능하다.

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가지의 기본 노드를 제공한다.

  • 컴포짓Composite
    분기의 루트를 정의하고, 그 분기가 어떻게 실행되는지에 대한 기본 규칙을 정의
  • 태스크Task
    비헤이비어트리의 잎에 해당하고, 어떠한 작업을 하며, 출력연결이 없다.
  • 데코레이터Decorator
    조건문이라고도 한다.
    다른 노드에 붙어 트리 내 분기 or 노드 하나라도 실행 여부를 결정짓는다.
  • 서비스Service
    컴포짓 노드에 붙어, 그 분기 실행 도중 정해진 빈도에 따라 실행된다.
    보통 블랙보드 업데이트나 검사를 하는 데 사용된다.
  • 루트Root
    비헤이비어 트리의 시작점이다.
    하나의 연결만 가질 수 있으며, 데코레이터나 서비스를 붙일 수 없다.
    디테일 패널에 비헤이비어 트리 프로퍼티가 표시되며, 블랙보드 애셋을 설정가능

컴포짓 : 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

컴포짓 노드

해당 분기가 실행되는 기본 규칙을 정의한다.
데코레이터를 통해 분기로 들어가는 조건을 변경하거나 중간에 실행이 취소되게 만들거나,
서비스를 덧붙여서 컴포짓 노드의 자손이 실행되는 동안 서비스가 작동되도록 만들 수 있다.

Selector

자식 노드를 실행하여 하나라도 true를 리턴하면 true를 리턴한다
무언가 하나라도 성공할 때 트리에서 나오게 된다.

Sequence

모든 자식 노드가 true를 리턴할때 true를 리턴하게 된다
무언가 실패할 때 트리에서 나오게 된다

심플 페러렐(Simple Parallel)

단순병렬노드라고도 하며 전체 노드트리와 동시에 하나의 태스크를 실행할 수 있다.

  • 적을 향해 이동하면서 사격한다던지

Finish Mode설정을 통해 메인 테스크가 완료되면 서브트리를 중단시키고
즉시 노드를 완료시킬지, 아니면 서브트리가 완료될때까지 딜레이 시킬지 설정할 수 있다.

컴포짓 노드의 자식노드들은 왼쪽에서 오른쪽부터 수행하며
모든 노드를 수행했고, 트리에서 빠져나오지 않을 시, 다시 첫번째 자식 노드로 돌아가게됨

Task노드

위 사진의 보라색으로 표현된, 무엇인가를 수행하는 노드
시퀀스의 A로 표시된 부분에서 끌어오거나, 마우스 우클릭-Task에서 가져올수있음
태스크 노드에도 데코레이터가 붙을 수 있다

MoveTo노드

기본으로 제공하는 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;
}

커스텀 Task노드 만들기

  • 프로젝트이름.Build.cs로 가서 "GameplayTasks" 모듈 추가**

  • BTTask_ 로 시작하는 클래스들 중 원하는 클래스를 상속받아 cpp파일 생성**
    (BlackBoard관련 프로퍼티가 필요하다면 BlackBoard를 상속받는 등)

  • 에디터에서 보기 편하도록 생성자를 생성하고,

UBTTask_ClearBlackboardBase::UBTTask_ClearBlackboardBase()
{
   NodeName = TEXT("Clear BlackBoard Value");
}

를 통해 이름을 보기 쉽게 바꿔준다.

컴파일하면 BT 에디터 내에서 끌어올 수 있다

커스텀 Task노드 구현하기
UTaskNode에 정의되어 있는 함수가 4종류 있다.

  • ExecuteTask //Task가 실행될때 호출
  • AbortTask //조건이 거짓이 되어 Task를 멈출때 호출
  • TickTask //매 프레임마다 실행(첫번째 틱에는 실행안함)
  • OnMessage //각 Task가 수행해야 할 성격에 맞는 함수를 골라 Override하여 사용하면 된다.

지정한 키의 값을 Clear하는 Task노드 예

EBTNodeResult::Type UBTTask_ClearBlackboardBase::ExecuteTask(UBehaviorTreeComponent& OwnerComp,
                                                               uint8* NodeMemory)
{
   Super::ExecuteTask(OwnerComp, NodeMemory);

   OwnerComp.GetBlackboardComponent()->ClearValue(GetSelectedBlackboardKey());

   return EBTNodeResult::Succeeded;
}

AI가 조종하고 있는 Pawn의 함수를 호출하는 예

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를 상속받자

  • OnBecomeRelevant (from UBTAuxiliaryNode)
  • OnCeaseRelevant (from UBTAuxiliaryNode)
  • TickNode (from UBTAuxiliaryNode)
  • OnSearchStart

Task 노드와 마찬가지로, 필요한 함수를 오버라이드하여 기능을 구현하자

서비스에서 AI컨트롤러에 접근해 LineOfSightTo를 실행하고, 블랙보드의 값을 Set하고 Clear하는 예제

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());
 }
}
profile
필기용 블로그입니다.

0개의 댓글