ํ๋ ์ด์ด๋ฅผ ์ถ์ ํ๋ ์ ์บ๋ฆญํฐ๋ฅผ ๋ง๋ค์ด ๋ณผ ๊ฒ์ด๋ค.
1. Character ํด๋์ค
Character ํด๋์ค๋ฅผ ์์๋ฐ๋ C++ ํด๋์ค๋ฅผ ์์ฑํ๋ค. Character ํด๋์ค๋ Pawn ํด๋์ค์ ์์์ผ๋ก ์ด์กฑ ๋ณดํ๊ณผ ๊ฐ์ ์๋ฐํ์ ์ฌ์ฉ๋๋ค.
์์ฑํ Character ํด๋์ค๋ฅผ ๋ฒ ์ด์ค๋ก ๋ธ๋ฃจํ๋ฆฐํธ ํด๋์ค๋ฅผ ์์ฑํ๋ค. ๊ทธ ํ ์ค์ผ๋ ํ ๋ฉ์, ๋จธํฐ๋ฆฌ์ผ, ์ ๋๋ฉ์ด์ ์ ์ฉ ๋ฐฉ๋ฒ์ ์ด ๊ธ์ ์ฐธ๊ณ ํ๋ฉด ๋๋ค.
2. AI Controller ํด๋์ค
์ธ๋ฆฌ์ผ ์์ง์์๋ ์บ๋ฆญํฐ๋ controller์ ์ง๋ฐฐ๋ฅผ ๋ฐ๋๋ค. ํ๋ ์ด์ด ์บ๋ฆญํฐ๋ Player Controller์ ๋น์๋๊ณ ๋์ผํ๊ฒ AI ์บ๋ฆญํฐ๋ AI Controller์ ๋น์๋๊ฒ ๋๋ค. ์ง๊ธ์ AI ์บ๋ฆญํฐ๋ฅผ ๊ตฌํ ์ค์ด๋ AI Controller๋ฅผ ์์๋ฐ๋ C++ ํด๋์ค๋ฅผ ์์ฑํ๋ค.
์์ฑํ AI ์ปจํธ๋กค๋ฌ๋ฅผ ๋ฒ ์ด์ค๋ก ํ๋ ๋ธ๋ฃจํ๋ฆฐํธ ํด๋์ค๋ฅผ ์์ฑํ๋ค. ์ด๋ ๊ฒ ์์ฑํ AI ์ปจํธ๋กค๋ฌ ๋ธ๋ฃจํ๋ฆฐํธ ํด๋์ค๋ ์ด์ ์ ์์ฑํ AI ์บ๋ฆญํฐ ๋ธ๋ฃจํ๋ฆฐํธ ํด๋์ค์์ ๋ฑ๋ก ๊ฐ๋ฅํ๋ค.
- Auto Possess AI
- Disabled: ๋นํ์ฑํ
- Placed In World: ํฐ์ด ๋ฐฐ์น๋ ๊ฒฝ์ฐ์๋ง AI ์ปจํธ๋กค๋ฌ ์์
- Spawned: ๋ก๋๋ ํ ํฐ์ด ์คํฐ๋ ๊ฒฝ์ฐ์๋ง AI ์ปจํธ๋กค๋ฌ ์์
- Placed In World Or Spawned: ํฐ์ ์์ฑ๋ ๋๋ง๋ค AI ์ปจํธ๋กค๋ฌ์ ์ํด ์๋์ผ๋ก ์์
3. NavMeshBoundsVolume
NavMeshBoundsVolume๋ AI๊ฐ ์์ง์ผ ์ ์๋ ์์ญ์ด๋ค. ๋งต์ ์ ์ฒด๋ฅผ ๊ฐ์ธ๋๋ก ์์น์ ํฌ๊ธฐ๋ฅผ ์กฐ์ ํ๋ฉด, ๋ค์๊ณผ ๊ฐ์ด ์ด๋ก์ ์์ญ ์์์ AI๊ฐ ์์ง์ด๊ฒ ๋๋ค.
์ถ๊ฐ๋ก ๋ค์๊ณผ ๊ฐ์ด Static Mesh์ ๋ด๋ถ์ธ๋ฐ๋ ์ํ์ง ์์ ๊ณณ๊น์ง NavMesh๊ฐ ์๊ธฐ๋ ๊ฒฝ์ฐ๊ฐ ์๋ค. Static Mesh๋ Collision์ด๋ Blocking Volume์ ์ด์ฉํ์ฌ Blocking์ด ์ค์ ๋ ์ํ์ด๋ค. ๊ทธ๋ฐ๋ฐ ์ ์ฐจ๋จ๋์ง ์๊ณ NavMesh๊ฐ ์๊ฒผ์๊น?
Collision๊ณผ Blocking Volume์ ๋ด๋ถ๋ผ๋ ๊ฐ๋ ์ด ์๊ณ ์ฐจ๋จ ๋ฉด์ด๋ค. ์ฆ, ๋ด๋ถ๋ ๋น์ด์๋ค. ๊ทธ๋์ ์ถ๊ฐ ์์ ์ด ํ์ํ๋ค.
1) MinRegionArea
NavMesh์ MinRegionArea ๊ฐ์ ์์ ํ๋ฉด, NavMesh ๋ด ์ฐ๊ฒฐ๋์ง ์์ ์์ ์์ญ๋ค ์ค ์ด ๊ฐ๋ณด๋ค ์์ ๊ฐ์ ์ ๊ฑฐํ ์ ์๋ค. ๊ทธ๋ฌ๋ ์ผ๋ถ ์ง์ญ์ ์ ๊ฑฐ๋์ง ์์ ์ ์๋ค. NavMesh๋ ํ์ผ ๊ทธ๋ฆฌ๋๋ก ๋ณ๋ ฌ๋ก ์ ์๋๋๋ฐ, ์์ญ์ด ํ์ผ ๊ฒฝ๊ณ์ ๊ฑธ์ณ ์์ผ๋ฉด ๊ทธ ์์ญ์ด ์ ๊ฑฐ๋์ง ์๋๋ค.
2) NavModifierVolume
NavModifierVolume์ ๋ฐฐ์นํ ํ NavArea_Null๋ก ์ค์ ํ๋ค. MinRegionArea๋ก ํด๊ฒฐ๋์ง ์์ ์์ญ์ NavModifierVolume์ผ๋ก ์ ๊ฑฐํ๋ฉด ๋๋ค.
4. ๋ธ๋๋ณด๋
๋ธ๋๋ณด๋๋ AI์ ๋๋ ์ญํ ์ ํ๋ฉฐ AI๊ฐ ์์์ผ ํ๋ ๋ชจ๋ ์์๋ ๋ธ๋๋ณด๋ ํค๋ฅผ ํตํด ์ฐธ์กฐํ ์ ์๋ค.
ํ๋ ์ด์ด ํธ๋ํน์ ์ํ ๋ธ๋๋ณด๋ ํค๋ฅผ ์ถ๊ฐํ๋ค.
- EnemyActor: ํ๋ ์ด์ด
- HasLineOfSight: AI๊ฐ ํ๋ ์ด์ด์ ๋ํ ์์ผ๋ฅผ ํ๋ณดํ๋์ง ์ฌ๋ถ๋ฅผ ํธ๋ํน ํ๋ ๋ฐ ์ฌ์ฉํ๋ค.
- PatrolLocation: AI๊ฐ ํ๋ ์ด์ด๋ฅผ ์ถ๊ฒฉํ๋ ์ค์ด ์๋ ๋ ์ด๋ ๊ฐ๋ฅํ ๋ ๋ฒจ ๋ด ์์น๋ฅผ ํธ๋ํน ํ๋ ๋ฐ ์ฌ์ฉํ๋ค.
5. ๋นํค์ด๋น์ด ํธ๋ฆฌ
๋นํค์ด๋น์ด ํธ๋ฆฌ๋ AI๊ฐ ์ง์ ํ ์คํ ์ดํธ์ ๋ ์ด์์์ ๊ตฌ์ฑํ๋ค.
๊ทธ๋ํ์์ ๋ฃจํธ๋ฅผ ๋๋๊ทธํ๋ฉด ์ปดํฌ์ง(Composite) ๋ ธ๋๋ฅผ ์ถ๊ฐํ ์ ์๋ค. ์ปดํฌ์ง์ ํ๋ฆ ์ ์ด์ ํํ์ด๋ฉฐ ์ฐ๊ฒฐ๋ ์์ ๋ถ๊ธฐ์ ์คํ ๋ฐฉ์์ ๊ฒฐ์ ํ๋ค.
- ๋ ํฐ(Selector): ์ผ์ชฝ์์ ์ค๋ฅธ์ชฝ์ผ๋ก ๋ถ๊ธฐ๋ฅผ ์คํํ๋ฉฐ, ๋ณดํต์ ๋ณต์์ ์๋ธํธ๋ฆฌ ์ค ํ๋๋ฅผ ์ ํํ๋ ๋ฐ ์ฌ์ฉ๋๋ค.
- ์ํ์ค(Sequence): ์ผ์ชฝ์์ ์ค๋ฅธ์ชฝ์ผ๋ก ๋ถ๊ธฐ๋ฅผ ์คํํ๋ฉฐ, ๋ณดํต์ ๋ณต์์ ์์์ ์์๋๋ก ์คํํ๋ ๋ฐ ์ฌ์ฉ๋๋ค.
- ๋จ์ ๋ณ๋ ฌ(Simple Parallel)
์ถ๊ฐ๋ก ์กฐ๊ฑด์ ์๋ธ ๋ ธ๋๋ผ๊ณ ๋ ์๋ ค์ง ๋ฐ์ฝ๋ ์ดํฐ(Decorator)๋ ์ปดํฌ์ง ๋๋ ํ์คํฌ ๋ ธ๋์ ๋ถ์ ์ ์๊ณ ํธ๋ฆฌ์ ์๋ ๋ถ๊ธฐ ๋๋ ๋จ์ผ ๋ ธ๋๋ฅผ ์คํํ ์ ์๋์ง ์ฌ๋ถ๋ฅผ ์ ์ํ๋ค.
๊ทธ์ค ๋ธ๋๋ณด๋(Blackboard) ๋ ธ๋๋ ํน์ ๋ธ๋๋ณด๋ ํค์ ๊ฐ์ด ์ค์ ๋์ด ์๋์ง ํ์ธํ๋ค.
- Nofity Observer(๊ด์ฐฐ์ ์๋ฆผ)
- On Result Change: ์กฐ๊ฑด์ด ๋ณ๊ฒฝ๋์์ ๋๋ง๋ค ์ฌ์์ํ๋ค.
- On Value Change: ๊ด์ฐฐ๋๋ ๋ธ๋๋ณด๋ ๊ฐ์ด ๋ฐ๋ ๋๋ง๋ค ์ฌ์์ํ๋ค.
- Observer Aborts(๊ด์ฐฐ์ง ์ค๋จ)
- None: ์๋ฌด๊ฒ๋ ์ค๋จํ์ง ์๋๋ค.
- Self: ์์ ๊ณผ ์ด ๋ ธ๋ ์๋ ์คํ ์ค์ธ ์๋ธ ํธ๋ฆฌ๋ ์ค๋จํ๋ค.
- Lower Priority: ์ด ๋ ธ๋์ ์ค๋ฅธ์ชฝ์ ์๋ ๋ชจ๋ ๋ ธ๋๋ฅผ ์ค๋จํ๋ค.
- Both: ์์ , ๊ทธ ์๋ ์คํ ์ค์ธ ์๋ธ ํธ๋ฆฌ, ์ด ๋ ธ๋ ์ค๋ฅธ์ชฝ์ ์๋ ๋ชจ๋ ๋ ธ๋๋ฅผ ์ค๋จํ๋ค.
- Key Query
- Is Set: ๊ฐ์ด ์ค์ ๋ ์ํ์ด๋ค. (true)
- Is Not Set ๊ฐ์ด ์ค์ ๋์ง ์์ ์ํ์ด๋ค. (false)
์ด์ ๋นํค์ด๋น์ด ํธ๋ฆฌ์ ํ๋ ์ ์ํฌ๋ฅผ ๊ตฌํํ ๊ฒ์ด๋ค. ๋ ธ๋์ ์ฐ์ธก ์๋จ ์ซ์๋ ์คํ ์์๋ฅผ ๋ํ๋ธ๋ค. ์ผ์ชฝ์์ ์ค๋ฅธ์ชฝ, ์์์ ์๋๋ก ์คํ๋๊ธฐ ๋๋ฌธ์ ๋ ธ๋์ ๋ฐฐ์ด์ด ์ค์ํ๋ค. ๋ณดํต AI์๊ฒ ์ค์๋๊ฐ ๋์ ์ก์ ์ ์ผ์ชฝ์ ๋ฐฐ์นํ๊ณ ๋ ์ค์ํ ์ก์ ์ ์ค๋ฅธ์ชฝ์ ๋ฐฐ์นํ๋ ๊ฒ์ด ์ผ๋ฐ์ ์ด๋ค. ์์ ๋ถ๊ธฐ ์ค ํ๋๋ผ๋ ์คํจํ๋ฉด ์ ์ฒด ๋ถ๊ธฐ๊ฐ ์คํ์ ์ค์งํ๊ณ ํธ๋ฆฌ๋ฅผ ๊ฑฐ์ฌ๋ฌ ๋ค์ ์ฌ๋ผ๊ฐ๋ค.
- AI Root: ์ค์ ๋ฃจํธ๋ก์ ์์ ๋ถ๊ธฐ ์ฌ์ด๋ฅผ ์ ํํ๋ค.
- Chase Player ์ํ์ค: HasLineOfSight(ํ๋ ์ด์ด ๋ฐ๊ฒฌ)๊ฐ Is Set์ผ ๋ ๋ถ๊ธฐ๋ฅผ ์คํํ๋ค.
- BTT_ChasePlayer: ์ด๋ ์๋๋ฅผ ๋ณ๊ฒฝํ๊ณ , AI Move To ๋ ธ๋๋ฅผ ์ด์ฉํด ํ๋ ์ด์ด๋ฅผ ์ถ๊ฒฉํ๋ ์ปค์คํ ํ์คํฌ์ด๋ค.
- Patrol ์ํ์ค: HasLineOfSight(ํ๋ ์ด์ด ๋ฐ๊ฒฌ)๊ฐ Is Not Set์ผ ๋ ๋ถ๊ธฐ๋ฅผ ์คํํ๋ค. ๋งต์์ ๋๋ค ์์น๋ฅผ ์ฐพ์ ์ด๋ํ๊ณ ๊ทธ ์์น์์ ์ ์ ๋๊ธฐํ๋ค๊ฐ, ๋ค์ ๋ฐ๋ณตํ๋ ์ํ์ค์ด๋ค.
- BTT_FindRandomPatrol: PatrolLocation(๋๋ค ์์น)๋ฅผ ์ฐพ๋ ์ปค์คํ ํ์คํฌ์ด๋ค.
- Move To: PatrolLocation(๋๋ค ์์น)์ผ๋ก ์ด๋ํ๋๋ก ์ง์ํ๋ ํ์คํฌ์ด๋ค.
- Wait: PatrolLocation(๋๋ค ์์น)์์ 4~5์ด ๋์ ๋๊ธฐํ๋ค.
BTT_ChasePlayer ๊ตฌํ์ ๋ค์๊ณผ ๊ฐ๋ค.
BTT_FindRandomPatrol ๊ตฌํ์ ๋ค์๊ณผ ๊ฐ๋ค.
๋ฌผ๋ก BTT๋ C++ ๊ตฌํ์ด ๊ฐ๋ฅํ๋ค.
6. AIPerceptionComponent
๋จผ์ ์์ฑํ BT๋ฅผ ์ ์ฉํด์ผ ํ๋ค.
UPROPERTY(EditDefaultsOnly)
TArray<UBehaviorTree*> BehaviorTree;
RunBehaviorTree(BehaviorTree);
AIPerceptionComponent๋ ์ถ๊ฐํ๋ค. ์ด ์ปดํฌ๋ํธ๋ AI ํผ์ ์ ์์คํ ์์ ์๊ทน ๋ฆฌ์ค๋๋ฅผ ์์ฑํ๊ณ ์ฌ์ฉ์๊ฐ ๋ฐ์ํ๋๋ก ๋ฑ๋ก๋ ์๊ทน์ ์์งํ๋ ๋ฐ ์ฌ์ฉ๋๋ค. ์ด ๊ธฐ๋ฅ์ ํตํด AI๊ฐ ํ๋ ์ด์ด๋ฅผ ๋ณด๊ณ ๋ฐ์ํ๋๋ก ๊ตฌ์ฑ์ด ๊ฐ๋ฅํ๋ค. ์๊ทน ์ค ์๊ฐ์ ํ์ฉํ ์์ ์ด๊ธฐ ๋๋ฌธ์ AIPerceptionSignt๋ ์ถ๊ฐํ๋ค.
UPROPERTY()
UAIPerceptionComponent* AIPerceptionComponent;
UPROPERTY()
UAISenseConfig_Sight* AIPerceptionSignt;
AEnemyAIController::AEnemyAIController()
{
AIPerceptionComponent = CreateDefaultSubobject<UAIPerceptionComponent>(TEXT("AIPerception"));
AIPerceptionSignt = CreateDefaultSubobject<UAISenseConfig_Sight>("Sight");
AIPerceptionComponent->ConfigureSense(*AIPerceptionSignt);
AIPerceptionComponent->SetDominantSense(AIPerceptionSignt->GetSenseImplementation());
AIPerceptionSignt->SightRadius = 1500.f;
AIPerceptionSignt->LoseSightRadius = 2000.f;
AIPerceptionSignt->PeripheralVisionAngleDegrees = 60.0f;
AIPerceptionSignt->DetectionByAffiliation.bDetectEnemies = true;
AIPerceptionSignt->DetectionByAffiliation.bDetectNeutrals = true;
AIPerceptionSignt->DetectionByAffiliation.bDetectFriendlies = false;
AIPerceptionComponent->OnTargetPerceptionUpdated.AddDynamic(this, &AEnemyAIController::TargetPerceptionUpdated);
}
void AEnemyAIController::TargetPerceptionUpdated(AActor* Actor, FAIStimulus Stimulus)
{
if (Actor->ActorHasTag("Player") && Stimulus.WasSuccessfullySensed())
{
GetBlackboardComponent()->SetValueAsBool("HasLineOfSight", true);
GetBlackboardComponent()->SetValueAsObject("EnemyActor", Actor);
GetWorld()->GetTimerManager().ClearTimer(LineOfSightTimerHandle);
}
else
{
GetWorld()->GetTimerManager().ClearTimer(LineOfSightTimerHandle);
GetWorld()->GetTimerManager().SetTimer(LineOfSightTimerHandle, FTimerDelegate::CreateLambda([&]()
{
GetBlackboardComponent()->SetValueAsBool("HasLineOfSight", false);
GetBlackboardComponent()->SetValueAsObject("EnemyActor", NULL);
GetWorld()->GetTimerManager().ClearTimer(LineOfSightTimerHandle);
}), 2, false);
}
}
- ์์ ๊ฐ์ง(DetectionbyAffiliation): ๊ฐ์ ์์ ํ์๋ค๊ณผ ํจ๊ป ์๋ ํ์๋ค์ ๊ณต๊ฒฉํ๋ ํ ๊ธฐ๋ฐ AI๋ฅผ ๊ตฌ์ฑํ ์ ์๋ค. ์กํฐ๋ ๋ํดํธ๋ก ์์์ด ํ ๋น๋์ง ์์ ์ค๋ฆฝ์ผ๋ก ๊ฐ์ฃผ๋๋ค.
- ์ค๋ฆฝ ํ์ง(Detect Neutrals): ํ์ฌ ๋ธ๋ฃจํ๋ฆฐํธ๋ฅผ ํตํด ์์์ ํ ๋นํ ์ ์์ด ์ค๋ฆฝ ํ์ง ํ๋๊ทธ๋ฅผ ํ์ฑํํ๊ณ ์กํฐ ํ๊ทธ ์ง์ ์ ๋์ ์ฌ์ฉํ์ฌ ํ๋ ์ด์ด๋ฅผ ํ๋จํ ๊ฒ์ด๋ค.
- TargetPerceptionUpdated()
- ํ๋ ์ด์ด ์๊ฐ ์ ๋ณด๊ฐ ๋ค์ด์จ๋ค๋ฉด, HasLineOfSight๋ฅผ true๋ก ๋ณ๊ฒฝํ๊ณ EnemyActor๋ฅผ ํ๋ ์ด์ด๋ก ๋ณ๊ฒฝํ๋ค.
- ์๊ฐ ์ ๋ณด๊ฐ ์๋ค๋ฉด, 2์ด ๋ค HasLineOfSight๋ฅผ false๋ก ๋ณ๊ฒฝํ๊ณ EnemyActor๋ฅผ NULL๋ก ๋ณ๊ฒฝํ๋ค. 2์ด์ ํ ์ ์ฃผ๋ ์ด์ ๋ ํ๋ ์ด์ด๊ฐ ์ฌ๋ผ์ง ๋ ๋ฐ๋ก ์ถ์ ์ ํฌ๊ธฐํ๋ค๋ฉด ์ด์ํ๊ธฐ ๋๋ฌธ์ 2์ด์ ๋๋ ์ด๋ฅผ ์ถ๊ฐํ๋ค.
์ถ๊ฐ๋ก AI๊ฐ ํ๋ ์ด์ด๋ฅผ ๊ฐ์งํ๊ธฐ ์ํด ํ๋ ์ด์ด ๋ธ๋ฃจํ๋ฆฐํธ ํด๋์ค์์ Tag๋ฅผ ์ค์ ํด์ผ ํ๋ค.
์ด์ ์ ์บ๋ฆญํฐ์ ์์ผ์ ํ๋ ์ด์ด๊ฐ ๋ค์ด์ค๋ฉด ๋ฐ๋ผ๊ฐ๋ค.