[UE5] 언리얼5 C++ - AI SubEnemy Spawn

AnJH·2024년 6월 5일

BehaviorTree_AI Enemy Spawn

MainAIController를 통해 움직이는 Enemy가 일정 조건을 갖추면 SubAIController를 통해 움직이는 Enemy를 Spawn 하려고 한다.

  • SubAIController.h
#include "CoreMinimal.h"
#include "AIController.h"
#include "SubAIController.generated.h"

UCLASS()
class THELASTSURVIVOR_API ASubAIController : public AAIController
{
	GENERATED_BODY()
	
public:
	virtual void BeginPlay() override;

	virtual void Tick(float DeltaSeconds) override;

private:
	bool bAttack = false;
	int32 attackDelay = 2;
};
  • SubAIController.CPP
#include "SubAIController.h"
#include "MainEnemy.h"
#include "Kismet/GameplayStatics.h"

void ASubAIController::BeginPlay()
{
	Super::BeginPlay();
}

void ASubAIController::Tick(float DeltaSeconds)
{
	Super::Tick(DeltaSeconds);

	APawn* PlayerPawn = UGameplayStatics::GetPlayerPawn(GetWorld(), 0);
	SetFocus(PlayerPawn);
	MoveToActor(PlayerPawn, 130);

	if (APawn* ControlledPawn = GetPawn()) {
		float distance = FVector::Distance(this->GetPawn()->GetActorLocation(), PlayerPawn->GetActorLocation());
		if (distance < 250.0f && !bAttack) {
			bAttack = true;
			FTimerHandle AttackTimerHandle;
			FTimerDelegate Callback = FTimerDelegate::CreateLambda([this]() {bAttack = false; });
			GetWorld()->GetTimerManager().SetTimer(AttackTimerHandle, Callback, attackDelay, false);
			
			AMainEnemy* SubSpider = Cast<AMainEnemy>(ControlledPawn);
			if (SubSpider) {
				SubSpider->Attack();
			}
		}
	}
}

SubAIController에서는 MainAIController와 다르게 BehaviorTree를 사용하지 않는다. Controller 내부에서 AI를 조종함으로써 해당 Controller를 사용하는 Enemy는 같은 맵에 여러마리가 존재할 수 있다.

Tick()함수를 통해 움직이기에 Attack이 여러번 실행되지 않도록 Bool타입의 변수를 통해 제어해주었다.

  • SubEnemy.CPP
AIControllerClass = ASubAIController::StaticClass();
AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;

이전에는 MainAIController에서 선언해둔 Controller를 사용하였다. 허나 Spawn될 Enemy는 MainEnemy 클래스를 상속받는 것은 동일하나 생성자에서 위처럼 SubAIController를 사용할 것이라 지정해주어야 한다.

  • MainAIController.CPP
void AMainAIController::SpawnSpider()
{
	if (SpiderClass && bCanSpawn) {
		FActorSpawnParameters SpawnParams;
		SpawnParams.Owner = this;
		SpawnParams.Instigator = GetPawn();

		FVector SpawnLocation = GetSpawnLocation();
		FRotator SpawnRotation = FRotator::ZeroRotator;

		GetWorld()->SpawnActor<AMainEnemy_SubSpider>(SpiderClass, SpawnLocation, SpawnRotation, SpawnParams);
		bCanSpawn = false;
		FTimerHandle SpawnTimerHandle;
		FTimerDelegate Callback = FTimerDelegate::CreateLambda([this]() {bCanSpawn = true; });
		GetWorld()->GetTimerManager().SetTimer(SpawnTimerHandle, Callback, 10.0f, false);
	}
}

FVector AMainAIController::GetSpawnLocation()
{
	if (FMath::RandRange(0, 2) < 1) {
		return FVector(-1500.0f, 1100.0f, 100.0f);
	}
	else {
		return FVector(-1500.0f, -1100.0f, 100.0f);
	}
	
}

MainAIController를 사용하는 Enemy와의 전투 중에 SubEnemy를 소환하는 것이기에 Enemy를 Spawn하는 함수는 MainAIController에 작성해두었다. 원하는 조건이 아닌 경우에서 Spawn되는 것을 방지하기 위해 Bool타입의 변수로 이를 막아주었고, 10초마다 1마리씩 소환되도록 하였다.

  • BTService_CanSpawn.CPP
#include "BTService_CanSpawn.h"
#include "AIController.h"
#include "MainAIController.h"

UBTService_CanSpawn::UBTService_CanSpawn()
{
	NodeName = TEXT("SpawnEnemy");
	Interval = 10.0f;
	bCallTickOnSearchStart = true;
}

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

	AAIController* AIController = OwnerComp.GetAIOwner();
	if (AIController) {
		AMainAIController* MyAIController = Cast<AMainAIController>(AIController);
		if (MyAIController) {
			UE_LOG(LogTemp, Warning, TEXT("spawn"));
			MyAIController->SpawnSpider();
		}
	}
}

SubEnemy를 10초에 한 번씩 Spawn하기 위해 Service노드를 만들어주었다. Interval에 TickNode가 실행될 간격을 지정해주고, bCallTickOnSearchStart를 true로 함으로써 해당 노드를 만나면 Tick노드를 바로 한 번 실행하도록 하였다.

  • BehaviorTree

Target을 발견한 이후, Enemy와의 전투가 시작되면 Service노드가 실행되도록 Tree를 구성하였다.

0개의 댓글