MainAIController를 통해 움직이는 Enemy가 일정 조건을 갖추면 SubAIController를 통해 움직이는 Enemy를 Spawn 하려고 한다.
#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;
};
#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타입의 변수를 통해 제어해주었다.
AIControllerClass = ASubAIController::StaticClass();
AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
이전에는 MainAIController에서 선언해둔 Controller를 사용하였다. 허나 Spawn될 Enemy는 MainEnemy 클래스를 상속받는 것은 동일하나 생성자에서 위처럼 SubAIController를 사용할 것이라 지정해주어야 한다.
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마리씩 소환되도록 하였다.
#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노드를 바로 한 번 실행하도록 하였다.

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