본문 바로가기

언리얼5/공부기록

UE Ai Task Unreal C++에서 구현방법

BTTaskNode 클래스를 상속받는 PatrolClass 생성 및 초기화


BTTaskNode를 C++프로젝트에서 사용하기위해서는 3가지 모듈을 Build.cs에서 추가해야한다.

 

"NavigationSystem", "AIModule", "GameplayTasks"

PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput", "UMG", "NavigationSystem", "AIModule", "GameplayTasks" });

 

블랙보드에서 정의한 키값의 접근을 가독성을 위해서 전처리기로 정의하는편이 좋다.

#define BBKEY_HOMEPOS TEXT("HomePos")
#define BBKEY_PATROLPOS TEXT("PatrolPos")
#define BBKEY_TARGET TEXT("Target")

 

블랙보드 키값 초기화 

Blackboard->SetValueAsVector(BBKEY_HOMEPOS, GetPawn()->GetActorLocation());

 

PatrolClass 구현

#include "AI/UBTTask_Patrol.h"
#include "ABAI.h"
#include "AIController.h"
#include "NavigationSystem.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "Interface/CharacterAIInterface.h" //상위 레이어 접근

UBTTask_Patrol::UBTTask_Patrol()
{
}
//오버라이딩 함수
EBTNodeResult::Type UBTTask_Patrol::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
	EBTNodeResult::Type Result = Super::ExecuteTask(OwnerComp, NodeMemory);
	
    //컨트롤러의 폰 레퍼런스 캐싱
	APawn* ControllingPawn = OwnerComp.GetAIOwner()->GetPawn();
	if (nullptr == ControllingPawn)
	{
		return EBTNodeResult::Failed;
	}
	//네비시스템 버전업으로 바뀐 기능 언리얼 5.3에서도 정상동작한다.
	UNavigationSystemV1* NavSystem = UNavigationSystemV1::GetNavigationSystem(ControllingPawn->GetWorld());
	if (nullptr == NavSystem)
	{
		return EBTNodeResult::Failed;
	}
	//상위레이어의 캐릭터를 간접접근하기위해 인터페이스 사용
    //Ai 인터페이스의 역할은 각기 다른 폰의 이동범위의 호출
	ICharacterAIInterface* AIPawn = Cast<IABCharacterAIInterface>(ControllingPawn);
	if (nullptr == AIPawn)
	{
		return EBTNodeResult::Failed;
	}

	FVector Origin = OwnerComp.GetBlackboardComponent()->GetValueAsVector(BBKEY_HOMEPOS);
	float PatrolRadius = AIPawn->GetAIPatrolRadius();
	//이동 목적지를 저장할 지역변수 선언
	FNavLocation NextPatrolPos;
	
    //인자값(라디어스의 중점 기준,범위길이,랜덤한 위치 데이터)
    //home에 저장된값이 Origin으로 중점이된다,인터페이스에서 구한 폰의 이동범위값, 저장
	if (NavSystem->GetRandomPointInNavigableRadius(Origin, PatrolRadius, NextPatrolPos))
	{
    	//초기화된 목적지 위치값을 블랙보드 키값에 저장
		OwnerComp.GetBlackboardComponent()->SetValueAsVector(BBKEY_PATROLPOS, NextPatrolPos.Location);
		return EBTNodeResult::Succeeded;
	}

	return EBTNodeResult::Failed;
}

 

비헤비어 트리 enter -> 마우스 우클릭 -> tasks ->  Patrol테스크 생성

 

BT 서비스 클래스

정찰중에 플레이어를 감지할수있는 기능을 구현
Detecting을 상시적으로 체크하기위해서는 서비스 노드로 구현해야한다.

 

서비스노드 내부 구조

서비스는 TickNode함수가 존재하며 

Interval(주기)를 가지고 TickNode를 호출한다



BTService_Detect

#include "AI/BTService_Detect.h"
#include "ABAI.h"
#include "AIController.h"
#include "Interface/ABCharacterAIInterface.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "Physics/ABCollision.h"
#include "DrawDebugHelpers.h"

UBTService_Detect::UBTService_Detect()
{
	//비헤비어내에서 탐색되는 이름
	NodeName = TEXT("Detect");
    //Tick노드 호출주기
	Interval = 1.0f;
}

void UBTService_Detect::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
	Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds);
	
    //컨트롤러폰 캐싱
	APawn* ControllingPawn = OwnerComp.GetAIOwner()->GetPawn();
	if (nullptr == ControllingPawn)
	{
		return;
	}
	
    //기준값을 구하기위해 폰 위치값 저장
	FVector Center = ControllingPawn->GetActorLocation();
	//폰을 포함시키는 월드의 레퍼런스 캐싱
	UWorld* World = ControllingPawn->GetWorld();
	if (nullptr == World)
	{
		return;
	}
	//Ai 인터페이스
	IABCharacterAIInterface* AIPawn = Cast<IABCharacterAIInterface>(ControllingPawn);
	if (nullptr == AIPawn)
	{
		return;
	}
	//감지범위 구하기
	float DetectRadius = AIPawn->GetAIDetectRange();
	
    //#### 충돌감지 #####
	
    //충돌된 대상의 정보'들'을 저장할 변수
	TArray<FOverlapResult> OverlapResults;

	FCollisionQueryParams CollisionQueryParam(SCENE_QUERY_STAT(Detect), false, ControllingPawn);
		//bResult는 충돌이 감지되면 true를 반환
		bool bResult = World->OverlapMultiByChannel(
		OverlapResults, //충돌된 대상의 정보'들'을 정의할 변수
		Center,//충돌구체 기준값
		FQuat::Identity,
		CCHANNEL_ABACTION,//적용할 체널액션
		FCollisionShape::MakeSphere(DetectRadius), //구체를 DetectRadius를 반지름 기준으로 생성
		CollisionQueryParam
	);

	if (bResult)
	{
    	
		for (auto const& OverlapResult : OverlapResults)
		{
            //충돌 대상 폰으로 캐스팅 및 레퍼런스 캐싱
			APawn* Pawn = Cast<APawn>(OverlapResult.GetActor());
			//플레이어 인지 확인
			if (Pawn && Pawn->GetController()->IsPlayerController())
			{
                //블랙보드 타겟을 플레이로 초기화
				OwnerComp.GetBlackboardComponent()->SetValueAsObject(BBKEY_TARGET, Pawn);
				//디버깅용 구체 그리기
				DrawDebugSphere(World, Center, DetectRadius, 16, FColor::Green, false, 0.2f);
				
                //플레이어 기준으로 점 그리기
				DrawDebugPoint(World, Pawn->GetActorLocation(), 10.0f, FColor::Green, false, 0.2f);
				//컨트롤대상(몬스터)에서 부터 플레이어까지 선그리기 
				DrawDebugLine(World, ControllingPawn->GetActorLocation(), Pawn->GetActorLocation(), FColor::Green, false, 0.27f);
				return;
			}
		}
	}
	//리턴이 안됬다면 플레이어 탐지 실패 타겟을 null로 정의
	OwnerComp.GetBlackboardComponent()->SetValueAsObject(BBKEY_TARGET, nullptr);
    //플레이어 탐지 범위를 빨간색으로 그리기
	DrawDebugSphere(World, Center, DetectRadius, 16, FColor::Red, false, 0.2f);
}

 

비헤비어 트리 디자인

비헤비어트리 Enter -> 대상 시퀀스 마우스 우클릭 ->Add Service -> Detect추가

 

중단자 사용법
비헤비어트리 Enter -> 대상 컴포짓 클릭 -> 디테일 최상단 확인

On Value Change

On Result Change 조건이 변경될 때만 재평가합니다.
On Value Change 관찰된 블랙보드 키가 변경될 때만 재평가합니다.



Observer aborts

None 아무것도 중단하지 않습니다.
Self 자신 및 이 노드 아래에서 실행 중인 모든 서브트리를 중단합니다.
Lower Priority 이 노드의 오른쪽에 있는 모든 노드를 중단합니다.
Both 자신, 이 노드 아래에서 실행 중인 모든 서브트리, 이 노드의 오른쪽에 있는 모든 노드를 중단합니다.

 

플레이어 바라보기

    
	float TurnSpeed = AIPawn->GetAITurnSpeed();
    //몬스터에서 플레이어를 향하는 벡터
	FVector LookVector = TargetPawn->GetActorLocation() - ControllingPawn->GetActorLocation();
	//벡터에서 필요없는 높이값 0으로 초기화
	LookVector.Z = 0.0f;
	//플레이어를 향하는 로테이터 생성 바라보는 기준은 X(정면)값이다.
	FRotator TargetRot = FRotationMatrix::MakeFromX(LookVector).Rotator();
	//턴시간을 기준으로 스무스하게 회전
	ControllingPawn->SetActorRotation(FMath::RInterpTo(ControllingPawn->GetActorRotation(), TargetRot, GetWorld()->GetDeltaSeconds(), TurnSpeed));