[내일배움캠프 Day41] 언리얼 AI 구현

2025. 2. 17. 21:04·내일배움캠프/TIL

지난주 목, 금 여행 일정으로 인해 TIL은 작성하지 못했습니다. 이번 팀 프로젝트에서 AI 구현을 맡게 되어서 과거 '이득우의 언리얼 C++ 게임 개발의 정석' 책을 읽고 정리한 내용을 작성해 보았습니다.

 

AI 컨트롤러와 비헤이비어 트리

AIController와 내비게이션 시스템

NPC에 인공지능을 추가해 스스로 영역을 정찰하고 플레이어를 감지하면 쫓아가서 공격하도록 만들 수 있다.

AIController을 부모클래스로 하는 ABAIController 클래스 생성하고

ABCharacter.cpp

#include "ABAIController.h"

AABCharacter::AABCharacter()
{
    ...
        
    AIControllerClass = AABAIController::StaticClass();
    AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned; 
	// world에 배치되거나 스폰되면 AIController 자동으로 빙의
}

 

ABCharacter마다 ABAIController 액터가 생성되고 플레이어가 조종하는 캐릭터를 제외한 모든 캐릭터는 이 ABAIController의 지배를 받는다.

 

NPC가 스스로 움직일 수 있는 환경 구축

  • 내비게이션 메시 영역 생성
  • ABAIController에 빙의한 폰에게 목적지 알려줘 스스로 움직이도록 명령 추가
    • GetRandomPointInNavigableRadius : 이동 가능한 목적지를 랜덤으로 가져옴
  • AIController에 타이머 설치해 3초마다 폰에게 목적지로 이동하는 명령
    • SimpleMoveToLocation : 목적지로 폰 이동시킴

 

비헤이비어 트리 시스템

NPC의 복잡한 행동 패턴을 체계적으로 설계

  • 블랙보드 : 인공지능의 판단에 사용하는 데이터 집합
  • 비헤이비어 트리: 블랙보드 데이터에 기반해 설계한 비헤이비어 트리 정보 저장

태스크를 통해 동작 명령을 내린다. 단, 컴포짓 노드를 거친다

컴포짓 노드 : 왼쪽부터 오른쪽으로 태스크 실행

  • 셀렉터: 태스크 중 하나가 성공하면 실행 중단
  • 시퀀스: 태스크 중 하나가 실패하면 실행 중단

ABAIController.cpp

#include "ABAIController.h"
#include "BehaviorTree/BehaviorTree.h"
#include "BehaviorTree/BlackboardData.h"

AABAIController::AABAIController()
{
	static ConstructorHelpers::FObjectFinder<UBlackboardData> BBObject(TEXT("/Game/Book/AI/BB_ABCharacter.BB_ABCharacter"));
	if (BBObject.Succeeded())
	{
		BBAsset = BBObject.Object;  // 블랙보드
	}

	static ConstructorHelpers::FObjectFinder<UBehaviorTree> BTObject(TEXT("/Game/Book/AI/BT_ABCharacter.BT_ABCharacter"));
	if (BTObject.Succeeded())
	{
		BTAsset = BTObject.Object;  // 비헤이비어 트리
	}
}

void AABAIController::OnPossess(APawn* InPawn)
{
	Super::OnPossess(InPawn);

	if (UseBlackboard(BBAsset, Blackboard)) // 블랙보드 사용, 비헤이비어 트리 실행
	{
		if (!RunBehaviorTree(BTAsset)) 
		{
			ABLOG(Error, TEXT("AIController coudn't run behavior tree!")); 
		}
	}
}

ABAIController 가동 시 비헤이비어 트리 애셋과 같은 폴더에 위치한 블랙보드 애셋, 비헤이비어 트리가 함께 동작

 

비헤이비어 트리는 태스크를 실행할 때 태스크 클래스의 ExecuteTask 멤버 함수를 실행한다.

ExecuteTask 함수 반환값

  • Aborted : 태스크 실행 중 중단(결과적으로 실패)
  • Failed : 태스크 수행 실패
  • Succeeded : 태스크 수행 성공
  • InProgress : 태스크 계속 수행(실행 결과 향후 알려줄 예정)

 

BTTask_FindPatrolPos.cpp

#include "BTTask_FindPatrolPos.h"
#include "ABAIController.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "NavigationSystem.h"

UBTTask_FindPatrolPos::UBTTask_FindPatrolPos()
{
	NodeName = TEXT("FindPatrolPos"); // 해당 이름으로 노드 표시
}

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

	auto ControllingPawn = OwnerComp.GetAIOwner()->GetPawn();
	if (nullptr == ControllingPawn) return EBTNodeResult::Failed;

	// 월드의 내비게이션 시스템 가져오기
	UNavigationSystemV1* NavSystem = UNavigationSystemV1::GetNavigationSystem(ControllingPawn->GetWorld());
	if (nullptr == NavSystem) return EBTNodeResult::Failed;

	FVector Origin = OwnerComp.GetBlackboardComponent()->GetValueAsVector(AABAIController::HomePosKey);
	FNavLocation NextPatrol;

	// 월드의 내비게이션 범위 중 Origin을 기준으로 반지름만큼의 범위를 한정지어 랜덤한 좌표를 가져온다
	if (NavSystem->GetRandomPointInNavigableRadius(FVector::ZeroVector, 500.0f, NextPatrol))
	{
		// 해당 액터를 NextPatrol(랜덤좌표)으로 이동
		OwnerComp.GetBlackboardComponent()->SetValueAsVector(AABAIController::PatrolPosKey, NextPatrol.Location);
		return EBTNodeResult::Succeeded;
	}

	return EBTNodeResult::Failed;
}

 

NPC 추격 기능 구현

  1. 블랙보드에 Object 타입으로 Target 변수 생성
    • NPC가 플레이어 발견 시 플레이어의 정보 저장
  2. 셀렉터 컴포짓을 활용해 로직 확장
    • NPC가 추격과 정찰 중 하나를 선택해 행동하도록 명령
    • 추격에 우선권 : Target을 향해 이동하도록
  3. 서비스 노드 클래스(부모클래스 BTService) 생성
    • 서비스 노드 : 독립적으로 동작 X , 컴포짓 노드에 부착되는 노드, 반복적인 작업 실행 적합
    • 연결된 컴포짓 노드가 활성화되면 주기적으로 TickNode 함수 호출
  4. TickNode 함수에 탐지 영역 구현
  5. 비헤이비어 트리 에디터에서 컴포짓에 Detect 서비스 부착
  6. ABAIController에 Target 키 등록
  7. IsPlayerController 함수 사용
    • 캐릭터가 감지되면 Target을 플레이어 캐릭터로 지정
    • 플레이어 캐릭터 감지 시 녹색 구체와 연결선 그림

 

BTService_Detect.cpp

#include "BTService_Detect.h"
#include "ABAIController.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "DrawDebugHelpers.h"

UBTService_Detect::UBTService_Detect()
{
	NodeName = TEXT("Detect");
	Interval = 1.0f;  // TickNode 주기 설정
}

void UBTService_Detect::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
	Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds);

	APawn* ControllingPawn = OwnerComp.GetAIOwner()->GetPawn();
	if (nullptr == ControllingPawn) return;

	UWorld* World = ControllingPawn->GetWorld();
	FVector Center = ControllingPawn->GetActorLocation();
	float DetectRadius = 600.0f; // 반경 6미터 주위 탐지

	if (nullptr == World) return;
	TArray<FOverlapResult> OverlapResults;
	// 월드에 충돌체로 생성해서 충돌된 오브젝트들을 첫번째 인자에 넣어준다
	// 무시할 액터(여기서는 자기자신)를 세번째 인자에
	FCollisionQueryParams CollisionQueryParam(NAME_None, false, ControllingPawn); // 자기 자신을 무시하겠다
	bool bResult = World->OverlapMultiByChannel(
		OverlapResults,
		Center,
		FQuat::Identity,
		ECollisionChannel::ECC_GameTraceChannel2,
		FCollisionShape::MakeSphere(DetectRadius),
		CollisionQueryParam
	);

	DrawDebugSphere(World, Center, DetectRadius, 16, FColor::Red, false, 0.2f);
}

 

  • 데코레이터 노드 삽입
    • 데코레이터 노드 : 블랙보드 값을 기반으로 특정 컴포짓의 실행 여부 결정
    • Target 키 값 유무로 추격, 정찰 구분
  • 데코레이터 속성 지정
    • 노티파이 옵저버 값을 On Value Change로 변경
      • 키 값의 변경이 감지되면 현재 컴포짓 노드 실행 취소
    • 관찰자 중단 값 설정(Self)
    • 블랙보드 키값 설정(추격->Is Set, 정찰->Is Not Set)

 

NPC의 공격

플레이어가 범위 이내에 있는지 판단하도록 데코레이터 클래스 생성

공격 로직

  1. FinishiLatentTask 함수 선언
    • ExecuteTask의 결과값을 InProgress로 반환한 후 공격이 끝나면 태스크 종료 알림 제공(공격 태스크가 공격 애니메이션이 끝날 때까지 대기해야 하는 지연 태스크이므로)
  2. Tick 기능 활성화
    • FinishLatentTask 호출할 수 있도록
    • Tick에서 조건을 파악한 후 태스크 종료 명령 내림
  3. ABController 클래스의 Attack 함수 접근 권한을 public으로 변경
    • AI컨트롤러에서도 공격 명령을 내리기 위함
  4. 공격 종료 알림을 받도록 델리게이트 선언
    • 공격 종료 시 호출할 로직을 캐릭터에 구현
    • 태스크에서 람다 함수를 델리게이트에 등록
    Tick 함수 로직에서 델리게이트 반환값을 받으면 FinishiLatentTask 함수를 호출하고 태스크 종료
  5. 비헤이비어 트리에 Attack 태스크 삽입

 

BTTask_Attack.cpp

#include "BTTask_Attack.h"
#include "ABAIController.h"
#include "ABCharacter.h"

UBTTask_Attack::UBTTask_Attack()
{
	bNotifyTick = true; // 기본은 false, true 설정시 TickTask 실행 -> Finish를 계속 체크
	IsAttacking = false; // 아직 공격중이 아니다
}

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

	auto ABCharacter = Cast<AABCharacter>(OwnerComp.GetAIOwner()->GetPawn());
	if (nullptr != ABCharacter) return EBTNodeResult::Failed;

	ABCharacter->Attack();
	IsAttacking = true;
	// 람다식. ABCharacter이 AttackEnd Delegate를 호출하면 IsAttacking을 false로 
	ABCharacter->OnAttackEnd.AddLambda([this]()->void {
		IsAttacking = false;
	});

	// 일단 InProgress에 머물게한다. 공격 끝나기전까지 계속 지연시켜줌
	return EBTNodeResult::InProgress; 
}

// 매 Tick마다 공격태스크 끝났는지 체크
void UBTTask_Attack::TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
	Super::TickTask(OwnerComp, NodeMemory, DeltaSeconds);
	FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded());

	if (!IsAttacking) // 공격이 끝났으면 작업의 끝을 알린다
	{
		FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded);
	}
}

 

NPC가 공격과 동시에 플레이어를 향해 회전하는 기능을 추가

 

 

출처 : 이득우의 언리얼 C++ 게임 개발의 정석

'내일배움캠프 > TIL' 카테고리의 다른 글

[내일배움캠프 Day42] WIL  (5) 2025.02.18
[내일배움캠프 Day42] 알고리즘 수업 Wrap-Up  (0) 2025.02.18
[내일배움캠프 Day38] 8주차 과제 진행  (0) 2025.02.12
[내일배움캠프 Day37] TWeakObjectPtr  (0) 2025.02.11
[내일배움캠프 Day35] 알고리즘 수업 1주차 과제  (3) 2025.02.07
'내일배움캠프/TIL' 카테고리의 다른 글
  • [내일배움캠프 Day42] WIL
  • [내일배움캠프 Day42] 알고리즘 수업 Wrap-Up
  • [내일배움캠프 Day38] 8주차 과제 진행
  • [내일배움캠프 Day37] TWeakObjectPtr
개발자 밍
개발자 밍
dev0404 님의 블로그 입니다.
  • 개발자 밍
    Developer
    개발자 밍
  • 전체
    오늘
    어제
    • 분류 전체보기 (88)
      • 강의 (8)
        • UE Climbing System (3)
        • UE Dungeon (1)
        • HCI (4)
      • 책 (18)
        • 객체지향의 사실과 오해 (5)
        • Effective C++ (3)
        • 이득우의 게임 수학 (4)
        • 이것이 취업을 위한 컴퓨터 과학이다 (4)
        • 리뷰 (2)
      • C++ (2)
      • 알고리즘 (2)
      • 자료구조 (1)
      • Unreal (4)
      • 내일배움캠프 (52)
        • TIL (52)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    c++
    그래픽스
    Effective
    알고리즘
    자료구조
    게임수학
    컴퓨터 구조
    컴퓨터구조
    객체지향
    내일배움캠프
    언리얼
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.1
개발자 밍
[내일배움캠프 Day41] 언리얼 AI 구현
상단으로

티스토리툴바