AISpawnSystem을 게임 내에서 AI 캐릭터를 ObjectPooling으로 관리하는 컴포넌트입니다. 주요 기능과 역할을 다음과 같습니다.
- AI 풀 관리
- AI 스폰
- AI 동적 생성 및 풀링
- 동적 생성된 AI 캐릭터 관리
AI 풀 관리
AISpawnSystem은 GameMode의 PostInitializeComponents 함수에서 컴포넌트를 초기화하면서 초기 AIPool을 생성합니다.
// AProjectReplicaGameMode.h
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "ProjectReplica.h"
#include "GameFramework/GameModeBase.h"
#include "ProjectReplicaGameMode.generated.h"
class UPRAISpawnSystemComponent;
UCLASS(minimalapi)
class AProjectReplicaGameMode : public AGameModeBase
{
GENERATED_BODY()
public:
AProjectReplicaGameMode();
protected:
virtual void PostInitializeComponents() override;
#pragma region AISpawnSystem
private:
/** AI를 월드에 Spawn하고 관리하는 ActorComponent 클래스입니다. */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "AISpawnSystem", meta = (AllowPrivateAccess = "true"))
UPRAISpawnSystemComponent* AISpawnSystem;
public:
/** AISpawnSystem을 반환하는 함수입니다. */
class UPRAISpawnSystemComponent* GetAISpawnSystem() const { return AISpawnSystem; }
#pragma endregion
// AProjectReplicaGameMode.cpp
// Copyright Epic Games, Inc. All Rights Reserved.
#include "ProjectReplicaGameMode.h"
#include "Components/PRAISpawnSystemComponent.h"
AProjectReplicaGameMode::AProjectReplicaGameMode()
{
// AISpawnSystem
AISpawnSystem = CreateDefaultSubobject<UPRAISpawnSystemComponent>(TEXT("AISpawnSystem"));
}
void AProjectReplicaGameMode::PostInitializeComponents()
{
Super::PostInitializeComponents();
// AISpawnSystem을 초기화하여 초기 AIPool을 생성합니다.
GetAISpawnSystem()->InitializeAISpawnSystem();
}
// PRAISpawnSystemComponent.h
public:
/** AISpawnSystem을 초기화하는 함수입니다. */
UFUNCTION(BlueprintCallable, Category = "PRAISpawnSystem")
void InitializeAISpawnSystem();
// UPRAISpawnSystemComponent.cpp
void UPRAISpawnSystemComponent::InitializeAISpawnSystem()
{
// AIPoolSettings 데이터 테이블을 기반으로 AIPool을 초기화합니다.
if(AIPoolSettingsDataTable != nullptr)
{
TArray<FName> RowNames = AIPoolSettingsDataTable->GetRowNames();
for(const FName& RowName : RowNames)
{
FPRAIPoolSettings* AIPoolSettings = AIPoolSettingsDataTable->FindRow<FPRAIPoolSettings>(RowName, FString(""));
if(AIPoolSettings != nullptr)
{
CreateAIPool(*AIPoolSettings);
}
}
}
}
초기 AIPool은 FPRAIPoolSettings(AIPool의 설정을 나타내는) 구조체를 행 구조로 생성한 데이터 테이블을 기반으로 생성됩니다. 해당 구조체는 생성할 AI 캐릭터의 클래스와 생성할 AI 캐릭터의 수인 PoolSize로 이루어져 있습니다.
/**
* AIPool의 설정을 나타내는 구조체입니다.
*/
USTRUCT(Atomic, BlueprintType)
struct FPRAIPoolSettings : public FTableRowBase
{
GENERATED_BODY()
public:
FPRAIPoolSettings()
: AICharacterClass(nullptr)
, PoolSize(0)
{}
FPRAIPoolSettings(TSubclassOf<APRAICharacter> NewAICharacterClass, int32 NewPoolSize)
: AICharacterClass(NewAICharacterClass)
, PoolSize(NewPoolSize)
{}
public:
/** AI 캐릭터의 클래스 레퍼런스입니다. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PRAIPoolSettings")
TSubclassOf<APRAICharacter> AICharacterClass;
/** AICharacterPool의 크기입니다. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PRAIPoolSettings", meta = (ClampMin = "1"))
int32 PoolSize;
};
데이터 테이블의 FPRAIPoolSettings 구조체를 바탕으로 생성할 AI 캐릭터를 PoolSize만큼 생성하고 초기화한 AI 캐릭터를 AIPool에 보관합니다. 이 때 생성되는 AI 캐릭터들은 각각 고유의 번호를 가지게 됩니다. 이 번호를 AIListIndex라고 하며 AI 캐릭터가 활성화되면 AISpawnSystem의 AcitvateAIList TMap의 해당 AI 캐릭터의 클래스를 Key값에 해당하는 Value의 FPRActivateAIList의 IndexesTArray에 저장되어 활성화된 AI 캐릭터를 AIListIndex로 알 수 있게 됩니다.
// UPRAISpawnSystemComponent.cpp
void UPRAISpawnSystemComponent::CreateAIPool(FPRAIPoolSettings AIPoolSettings)
{
if(GetWorld() != nullptr && AIPoolSettings.AICharacterClass != nullptr)
{
// AIPool에 추가할 AIList를 초기화하고 AI 캐릭터를 생성하여 추가합니다.
FPRAIList AIList;
for(int32 Index = 0; Index < AIPoolSettings.PoolSize; Index++)
{
APRAICharacter* SpawnAICharacter = SpawnAndInitializeAI(AIPoolSettings.AICharacterClass, Index);
if(SpawnAICharacter)
{
// 초기화된 AICharacter를 AIList에 추가합니다.
AIList.List.Emplace(SpawnAICharacter);
}
}
// 초기화된 AIList를 AIPool에 추가하고 ActivateAIList를 생성합니다.
AIPool.Emplace(AIPoolSettings.AICharacterClass, AIList);
CreateActivateAIList(AIPoolSettings.AICharacterClass);
}
}
// UPRAISpawnSystemComponent.h
/** 활성화된 AI의 Index의 목록입니다. */
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "PRAISpawnSystem", meta = (AllowPrivateAccess = "true"))
TMap<TSubclassOf<APRAICharacter>, FPRActivateAIList> ActivateAIList;
/**
* 활성화된 AI 캐릭터의 Index를 보관한 목록을 나타내는 구조체입니다.
*/
USTRUCT(Atomic, BlueprintType)
struct FPRActivateAIList
{
GENERATED_BODY()
public:
/** 활성화된 AI 캐릭터의 Index를 보관한 목록입니다. */
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "PRActivateAIList")
TArray<int32> Indexes;
};
AI 스폰
InitializeAISpawnSystem 함수와 CreateAIPool 함수로 AIPool에서 사용하는 AI 캐릭터들은 월드에 비활성화 상태로 스폰되어있습니다. 따라서 ActivateAI 함수로 활성화할 AI 캐릭터의 클래스와 위치, 회전값을 지정하여 스폰합니다.
// PRAISpawnSystemComponent.h
/**
* 인자에 해당하는 AI 캐릭터 클래스를 AICharacterPool에서 찾아 존재할 경우 활성화하고 인자로 받은 좌표와 회전값을 적용하는 함수입니다.
*
* @param AICharacterClass AICharacterPool에서 찾을 AI 캐릭터의 클래스
* @param SpawnLocation 적용할 AI 캐릭터의 좌표
* @param SpawnRotation 적용할 AI 캐릭터의 회전값
* @return AICharacterPool에서 찾은 AI 캐릭터
*/
UFUNCTION(BlueprintCallable, Category = "PRAISpawnSystem")
APRAICharacter* ActivateAI(TSubclassOf<APRAICharacter> AICharacterClass, FVector SpawnLocation = FVector::ZeroVector, FRotator SpawnRotation = FRotator::ZeroRotator);
// PRAISpawnSystemComponent.cpp
APRAICharacter* UPRAISpawnSystemComponent::ActivateAI(TSubclassOf<APRAICharacter> AICharacterClass, FVector SpawnLocation, FRotator SpawnRotation)
{
// AIPool에서 해당 AI 캐릭터 클래스의 목록을 얻습니다.
FPRAIList* AICharacterList = AIPool.Find(AICharacterClass);
if(AICharacterList == nullptr)
{
// 지정된 AI 캐릭터 클래스가 없습니다.
return nullptr;
}
APRAICharacter* ActivateableAICharacter = nullptr;
// AICharacterList에서 활성화 되지 않은 AI 캐릭터를 찾아 활성화합니다.
for(const auto& AICharacter : AICharacterList->List)
{
if(IsValid(AICharacter) && !IsActivateAI(AICharacter))
{
ActivateableAICharacter = AICharacter;
break;
}
}
// AI 캐릭터를 활성화하고 Spawn할 위치와 회전값을 적용합니다.
ActivateableAICharacter->Activate();
ActivateableAICharacter->SetActorLocationAndRotation(SpawnLocation, SpawnRotation);
// 해당 AI 캐릭터 클래스로 처음 활성화하는 경우 ActivateAICharacterList를 생성합니다.
if(!IsCreateActivateAIList(AICharacterClass))
{
CreateActivateAIList(AICharacterClass);
}
// 활성화된 AI 캐릭터의 Index를 ActivateAIList에 추가합니다.
ActivateAIList.Find(AICharacterClass)->Indexes.AddUnique(ActivateableAICharacter->GetAIListIndex());
return ActivateableAICharacter;
}
AI 동적 생성 및 풀링
AIPool의 모든 AI 캐릭터가 활성화되었거나 PoolSize 이상의 AI 캐릭터를 스폰할 때 AI 캐릭터를 동적으로 생성합니다. 동적으로 생성한 AI 캐릭터의 AIListIndex를 지정하기 위해 AIPool에 존재하는 AI 캐릭터들의 AIListIndex를 보관한 UsedAIListIndexes TMap을 참고하여 사용할 수 있는 AIListIndex를 지정합니다.
// UPRAISpawnSystemComponent.h
/**
* 이전에 사용된 AI 캐릭터의 Index를 보관한 목록을 나타내는 구조체입니다.
*/
USTRUCT(Atomic, BlueprintType)
struct FPRUsedAIList
{
GENERATED_BODY()
public:
/** 이전에 사용된 Index를 추적하기 위한 Set입니다. */
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "PRActivateAIList")
TSet<int32> Indexes;
};
private:
/**
* 사용 가능한 AIListIndex를 찾아 반환하는 함수입니다.
*
* @param UsedIndexes 이미 사용 중인 Index 목록
* @return 사용 가능한 Index
*/
UFUNCTION(BlueprintCallable, Category = "PRAISpawnSystem")
int32 FindAvailableIndex(const TSet<int32>& UsedIndexes);
private:
/**
* AI 캐릭터를 PoolSize보다 많이 동적으로 생성할 때 사용한 AIListIndex의 목록입니다.
* 동적으로 생성하는 AI 캐릭터의 AIListIndex에 오류가 생기지 않도록 하기 위해서 사용합니다.
*/
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "PRAISpawnSystem", meta = (AllowPrivateAccess = "true"))
TMap<TSubclassOf<APRAICharacter>, FPRUsedAIList> UsedAIListIndexes;
// UPRAISpawnSystemComponent.cpp
APRAICharacter* UPRAISpawnSystemComponent::ActivateAI(TSubclassOf<APRAICharacter> AICharacterClass, FVector SpawnLocation, FRotator SpawnRotation)
{
//...
// AICharacterList의 모든 AI 캐릭터가 활성화 되었을 경우 새로운 AI 캐릭터를 생성합니다.
if(ActivateableAICharacter == nullptr)
{
// Critical Section 시작
FCriticalSection CriticalSection;
CriticalSection.Lock();
// 해당 AI 캐릭터 클래스의 UsedAIListIndexes가 생성되었는지 확인하고, 없으면 생성합니다.
if(!IsCreateUsedAIListIndexes(AICharacterClass))
{
FPRUsedAIList NewUsedAIListIndexes;
for(const auto& AICharacter : AICharacterList->List)
{
NewUsedAIListIndexes.Indexes.Add(AICharacter->GetAIListIndex());
}
UsedAIListIndexes.Emplace(AICharacterClass, NewUsedAIListIndexes);
}
// USedAIListIndexes에서 해당 AI 캐릭터 클래스가 사용중인 AIListIndex 목록을 얻습니다.
FPRUsedAIList* UsedAICharacterListIndexes = UsedAIListIndexes.Find(AICharacterClass);
if(UsedAICharacterListIndexes == nullptr)
{
// 지정된 AI 캐릭터 클래스가 없습니다.
return nullptr;
}
// 사용 가능한 AIListIndex를 구합니다.
const int32 NewAIListIndex = FindAvailableIndex(UsedAICharacterListIndexes->Indexes);
// Critical Section 끝
CriticalSection.Unlock();
// 새로운 AI 캐릭터를 생성하고 초기화하며 사용 가능한 AIListIndex를 설정합니다. OnDynamicAIDeactivate를 바인딩합니다.
ActivateableAICharacter = SpawnAndInitializeAI(AICharacterClass, NewAIListIndex);
ActivateableAICharacter->OnDynamicAIDeactivate.AddDynamic(this, &UPRAISpawnSystemComponent::OnDynamicAIDeactivate);
// 사용 가능한 AIListIndex를 사용중인 AIListIndex 목록에 추가합니다.
UsedAICharacterListIndexes->Indexes.Add(NewAIListIndex);
// 새로 생성한 AI 캐릭터를 AICharacterList에 추가합니다.
AICharacterList->List.Emplace(ActivateableAICharacter);
}
// AI 캐릭터를 활성화하고 Spawn할 위치와 회전값을 적용합니다.
ActivateableAICharacter->Activate();
//...
}
int32 UPRAISpawnSystemComponent::FindAvailableIndex(const TSet<int32>& UsedIndexes)
{
// 사용되지 않은 가장 작은 Index를 찾아 반환합니다.
int32 NewIndex = 0;
while(UsedIndexes.Contains(NewIndex))
{
NewIndex++;
}
return NewIndex;
}
동적 생성된 AI 캐릭터 관리
초기 AIPool에 생성된 AI 캐릭터들은 OnAIDeactivate 델리게이트에 비활성화하는 함수를 바인딩하여 비활성화를 실행합니다. 동적으로 생성한 AI 캐릭터는 추가적으로 OnDynamicAIDeactivate 델리게이트에 동적으로 생성한 AI 캐릭터를 비활성화하는 함수를 바인딩하여 실행합니다.
// APRAICharacter.h
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAIDeactivate, APRAICharacter*, AICharacter);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnDynamicAIDeactivate, APRAICharacter*, AICharacter);
public:
/** AI가 비활성화될 때 실행하는 델리게이트입니다. */
FOnAIDeactivate OnAIDeactivate;
/** 동적으로 생성한 AI가 비활성화될 때 OnAIDeactivate와 함께 실행하는 델리게이트입니다. */
FOnDynamicAIDeactivate OnDynamicAIDeactivate;
// UPRAIUSpawnSystemComponent.h
public:
/**
* 인자로 받은 AI 캐릭터를 비활성화하는 함수입니다.
* AI 캐릭터의 OnAIDeactivate 델리게이트 바인딩합니다.
*/
UFUNCTION(BlueprintCallable, Category = "PRAISpawnSystem")
void OnAIDeactivate(APRAICharacter* PooledAICharacter);
private:
/**
* 동적으로 생성한 AI 캐릭터를 비활성화 하는 함수입니다.
* AI 캐릭터의 OnDynamicAIDeactivate 델리게이트에 바인딩합니다.
*
* @param PooledAICharacter 비활성화할 AI 캐릭터
*/
UFUNCTION(BlueprintCallable, Category = "PRAISpawnSystem")
void OnDynamicAIDeactivate(APRAICharacter* PooledAICharacter);
/**
* 동적으로 생성한 AI 캐릭터를 제거하는 함수입니다.
*
* @param PooledAICharacter 제거할 AI 캐릭터
*/
UFUNCTION(BlueprintCallable, Category = "PRAISpawnSystem")
void OnDynamicAIDestroy(APRAICharacter* PooledAICharacter);
/** 동적으로 생성한 AI 캐릭터를 비활성화하는 딜레이 시간입니다. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PRAISpawnSystem", meta = (AllowPrivateAccess = "true", ClampMin = "0"))
float DynamicDeactivateDelay;
// UPRAISpawnSystemComponent.cpp
void UPRAISpawnSystemComponent::OnAIDeactivate(APRAICharacter* PooledAICharacter)
{
if(IsActivateAI(PooledAICharacter))
{
// 비활성화된 AI 캐릭터의 Index를 ActivateAIList에서 제거합니다.
ActivateAIList.Find(PooledAICharacter->GetClass())->Indexes.Remove(PooledAICharacter->GetAIListIndex());
}
}
void UPRAISpawnSystemComponent::OnDynamicAIDeactivate(APRAICharacter* PooledAICharacter)
{
if(DynamicDeactivateDelay > 0)
{
// 타이머로 딜레이 만큼 시간이 지난 후 OnDynamicAIDestroy 함수를 실행합니다.
FTimerHandle DynamicDeactivateDelayTimerHandle;
FTimerDelegate DynamicDeactivateDelayDelegate = FTimerDelegate::CreateUObject(this, &UPRAISpawnSystemComponent::OnDynamicAIDestroy, PooledAICharacter);
GetWorld()->GetTimerManager().SetTimer(DynamicDeactivateDelayTimerHandle, DynamicDeactivateDelayDelegate, DynamicDeactivateDelay, false);
}
else
{
// 딜레이가 없을 경우 타이머를 실행하지 않고 바로 OnDynamicAIDestroy 함수를 실행합니다.
OnDynamicAIDestroy(PooledAICharacter);
}
}
void UPRAISpawnSystemComponent::OnDynamicAIDestroy(APRAICharacter* PooledAICharacter)
{
// 사용 중인 AIListIndexes를 가지고 있는지 확인합니다.
if(IsCreateUsedAIListIndexes(PooledAICharacter->GetClass()))
{
// 해당 AI 캐릭터 클래스의 UsedAIListIndex를 얻습니다.
FPRUsedAIList* NewUsedAIList = UsedAIListIndexes.Find(PooledAICharacter->GetClass());
// 사용 중인 AIListIndex를 제거합니다.
NewUsedAIList->Indexes.Remove(PooledAICharacter->GetAIListIndex());
// 해당 AI 캐릭터 클래스의 AIPool이 생성되었는지 확인합니다.
if(IsCreateAIPool(PooledAICharacter->GetClass()))
{
// AIPool에서 해당 AI 캐릭터를 제거하고 파괴합니다.
AIPool.Find(PooledAICharacter->GetClass())->List.Remove(PooledAICharacter);
PooledAICharacter->Destroy();
}
}
}
'Project > Replica' 카테고리의 다른 글
[Project Replica] Vaulting / Vault / 장애물 뛰어넘기 (0) | 2024.04.15 |
---|---|
[Project Replica] DamageSystem 대미지 시스템 (0) | 2024.01.11 |
[Project Replica] 극한회피 Extreme Dodge/TimeStopSystem (2) | 2023.10.26 |
[Project Replica] 범위 피해 스킬 Judgement Cut (0) | 2023.10.18 |
[Project Replica] 검기 스킬 Slash Projectile Skill (1) | 2023.10.16 |