
AI 개발 로드맵에 따라 구현에 돌입했다. 오늘은 C++로 몬스터의 기본 뼈대를 만들고, AI가 플레이어를 '보고' '생각'하게 만드는 핵심 로직을 완성하는 데 집중했다. AI 컨트롤러, 인지(Perception), 행동 트리(BT), 블랙보드(BB)라는 네 가지 개념이 어떻게 유기적으로 맞물려 돌아가는지 몸으로 부딪히며 배웠다.😅 수많은 오류와 씨름한 끝에, 마침내 AI가 나를 발견하고 쫓아와 공격 애니메이션을 재생했을 때의 성취감이 있었다.
AIMonsterBase 클래스를 구현하여 AI의 기본 데이터 구조를 확립한다.PawnSensing를 넘어 AIPerceptionComponent를 사용하여 정교한 시각 인지 시스템을 구축한다.AI가 움직이기 위해서는 이 세 가지 요소가 긴밀하게 협력해야 한다.
AI의 두뇌인 AI 컨트롤러다. 여기서 AIPerceptionComponent를 이용해 시각 센서를 만들고, 플레이어를 감지했을 때와 시야에서 놓쳤을 때의 로직을 처리했다. 특히 감지된 대상이 진짜 플레이어인지 Cast로 확인하는 과정이 핵심이었다. 다른 AI나 중립 NPC를 공격하는 문제를 막아주기 때문이다.
#pragma once
#include "CoreMinimal.h"
#include "AIController.h"
#include "Perception/AIPerceptionTypes.h"
#include "AIC_Monster.generated.h"
UCLASS()
class SPARTA_TPROJECT_02_API AAIC_Monster : public AAIController
{
GENERATED_BODY()
public:
AAIC_Monster();
private:
UPROPERTY(VisibleAnywhere)
class UAIPerceptionComponent* AIPerceptionComponent; // AI의 감각(시각, 청각 등)을 담당
UPROPERTY(VisibleAnywhere)
class UAISenseConfig_Sight* SightConfig; // 시각 센서의 상세 설정 (반경, 각도 등)
UFUNCTION()
void OnTargetPerceptionUpdated(AActor* Actor, FAIStimulus Stimulus); // 감지 정보가 업데이트될 때 호출될 함수
};
// AIC_Monster.cpp
#include "AIC_Monster.h"
#include "Perception/AIPerceptionComponent.h"
#include "Perception/AISenseConfig_Sight.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "Sparta_TProject_02/Sparta_TProject_02Character.h" // 플레이어 클래스 헤더
AAIC_Monster::AAIC_Monster()
{
// Perception Component와 Sight Config 생성
AIPerceptionComponent = CreateDefaultSubobject<UAIPerceptionComponent>(TEXT("AIPerceptionComponent"));
SightConfig = CreateDefaultSubobject<UAISenseConfig_Sight>(TEXT("SightConfig"));
if (SightConfig)
{
// 시각 센서의 상세 설정
SightConfig->SightRadius = 1500.0f;
SightConfig->LoseSightRadius = 2000.0f;
SightConfig->PeripheralVisionAngleDegrees = 90.0f;
SightConfig->DetectionByAffiliation.bDetectEnemies = true;
SightConfig->DetectionByAffiliation.bDetectNeutrals = true;
SightConfig->DetectionByAffiliation.bDetectFriendlies = true;
// 설정한 센서를 Perception Component에 등록
AIPerceptionComponent->ConfigureSense(*SightConfig);
AIPerceptionComponent->SetDominantSense(SightConfig->GetSenseImplementation());
}
// 감지 이벤트가 발생하면 OnTargetPerceptionUpdated 함수를 호출하도록 연결
AIPerceptionComponent->OnTargetPerceptionUpdated.AddDynamic(this, &AAIC_Monster::OnTargetPerceptionUpdated);
}
void AAIC_Monster::OnTargetPerceptionUpdated(AActor* Actor, FAIStimulus Stimulus)
{
// 감지된 대상이 플레이어인지 '반드시' 확인!
ASparta_TProject_02Character* PlayerCharacter = Cast<ASparta_TProject_02Character>(Actor);
if (Stimulus.WasSuccessfullySensed())
{
if (PlayerCharacter) // 플레이어가 맞을 때만 아래 로직 실행
{
// 블랙보드의 TargetActor 키에 플레이어 정보를 기록
GetBlackboardComponent()->SetValueAsObject(TEXT("TargetActor"), Actor);
}
}
else
{
// 시야에서 사라지면 블랙보드의 TargetActor 키를 비움
GetBlackboardComponent()->ClearValue(TEXT("TargetActor"));
}
}
AI가 꿈쩍도 하지 않는 문제 😱
분명 로그는 뜨는데 AI가 제자리에 서 있는 문제가 있었다. 원인은 행동 트리와 블랙보드의 연결 고리가 끊어져 있었기 때문이었다. 행동 트리 에디터의 디테일 패널에서 Blackboard Asset을 지정해주지 않아, Move To 태스크가 TargetActor라는 기억을 읽어오지 못하고 있었다. AI의 뇌와 기억을 연결하는 이 작은 설정 하나가 모든 것을 멈추게 했다.
AI가 아군 좀비에게 돌진하는 문제
플레이어뿐만 아니라 다른 좀비에게도 이동하고 공격하는 문제가 발생했다. OnTargetPerceptionUpdated 함수에서 감지된 Actor가 플레이어 클래스인지 Cast로 확인하는 필터링 로직을 추가하여 해결했다. AI에게 '적'이 누구인지 명확히 알려주는 과정이 필수적이었다.
공격 애니메이션이 뚝 끊기는 문제
BTT_Attack 태스크에서 Montage Play 노드를 일반 실행 핀으로 연결했더니, 애니메이션이 시작되자마자 태스크가 종료되어 움직임이 꼬였다. Montage Play 노드의 On Completed 델리게이트 핀을 Finish Execute에 연결하여, 애니메이션이 모두 끝나야 태스크가 종료되도록 비동기적으로 처리해야 한다는 것을 배웠다.
| 개념 | 설명 | 비고 |
|---|---|---|
| AI 컨트롤러 | AI 캐릭터의 행동을 결정하는 '뇌' 역할을 하는 클래스. Perception, 행동 트리 실행 등을 담당한다. | 🧠 AI의 뇌 |
| AIPerception | 시각, 청각 등 AI의 '감각'을 담당하는 컴포넌트. 특정 대상을 감지하고 정보를 컨트롤러에 전달한다. | 👀 AI의 눈과 귀 |
| 블랙보드 | AI가 기억해야 할 데이터를 저장하는 '메모리' 또는 '메모장'. (예: TargetActor, LastKnownLocation) | 📝 AI의 기억 |
| 행동 트리 | 블랙보드의 데이터를 바탕으로 AI가 어떤 행동을 할지 결정하는 '의사결정 순서도'. | 🗺️ AI의 생각 흐름 |
| 애님 몽타주 | 코드나 블루프린트에서 직접 제어할 수 있는 특수 애니메이션. 공격, 스킬 사용 등 이벤트 기반 동작에 필수적이다. | 🎬 특별 행동 스크립트 |