오늘은 AI의 전투 시스템을 완성하는 데 집중했다. 공격 애니메이션과 실제 데미지 판정을 연동하여 AI가 플레이어에게 실질적인 위협을 가하도록 만들었으며, 피격 및 죽음 시퀀스를 구현하여 전투의 한 사이클을 완성했다. 또한, 기존의 시각(Sight) 감각에만 의존하던 AI의 한계를 극복하기 위해 청각(Hearing)과 촉각(Damage Sense)을 추가하여, 플레이어가 등 뒤에 있어도 인지할 수 있는 훨씬 더 똑똑한 AI의 기반을 다졌다.
AIPerceptionStimuliSource 설정#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "AIMonsterBase.generated.h"
using namespace std;
UCLASS()
class SPARTA_TPROJECT_02_API AAIMonsterBase : public ACharacter
{
GENERATED_BODY()
public:
AAIMonsterBase();
protected:
virtual void BeginPlay() override;
public:
virtual float TakeDamage(float DamageAmount, struct FDamageEvent const& DamageEvent, class AController* EventInstigator, AActor* DamageCauser) override;
virtual void Attack();
void Die();
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AI Stats")
float MaxHealth;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "AI Stats")
float CurrentHealth;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AI Stats")
float AttackDamage;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AI Stats")
float AttackRange;
protected:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "AI State")
bool bIsDead;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "AI Combat")
class UAnimMontage* HitReactMontage;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "AI Combat")
class UAnimMontage* DeathMontage;
};
#include "AIMonsterBase.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Components/CapsuleComponent.h"
#include "Kismet/GameplayStatics.h"
#include "Engine/World.h"
#include "Sparta_TProject_02/Sparta_TProject_02Character.h"
#include "AIController.h"
#include "BrainComponent.h"
using namespace std;
// ... 생성자, BeginPlay 등 ...
float AAIMonsterBase::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
if (bIsDead) return 0.0f;
const float ActualDamage = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
if (ActualDamage > 0.0f)
{
CurrentHealth -= ActualDamage;
if (CurrentHealth <= 0.0f)
{
Die();
}
else
{
if (HitReactMontage) PlayAnimMontage(HitReactMontage); // 피격 리액션
}
}
return ActualDamage;
}
void AAIMonsterBase::Attack()
{
if (bIsDead) return;
// ... SphereTrace를 이용한 데미지 판정 로직 ...
// AnimNotify를 통해 이 함수가 호출됨
UGameplayStatics::ApplyDamage(...);
}
void AAIMonsterBase::Die()
{
if (bIsDead) return;
bIsDead = true;
AAIController* AIController = Cast<AAIController>(GetController());
if (AIController && AIController->GetBrainComponent())
{
AIController->GetBrainComponent()->StopLogic("Died"); // BT 정지
}
GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
if (GetMesh())
{
GetMesh()->SetCollisionProfileName(TEXT("Ragdoll")); // 래그돌 프리셋
GetMesh()->SetSimulatePhysics(true); // 물리 시뮬레이션 활성화
}
SetLifeSpan(10.0f); // 10초 후 소멸
}
기존의 시각(Sight) 설정에 더해, 청각(Hearing)과 촉각(Damage) 센서를 추가로 설정했다. 이제 AI는 더 다양한 방법으로 플레이어를 인지할 수 있다.
// AIC_Monster.cpp
#include "AIC_Monster.h"
#include "Perception/AIPerceptionComponent.h"
#include "Perception/AISenseConfig_Sight.h"
#include "Perception/AISenseConfig_Hearing.h" // 청각 헤더
#include "Perception/AISenseConfig_Damage.h" // 촉각 헤더
#include "BehaviorTree/BlackboardComponent.h"
#include "Sparta_TProject_02/Sparta_TProject_02Character.h"
using namespace std;
AAIC_Monster::AAIC_Monster()
{
AIPerceptionComponent = CreateDefaultSubobject<UAIPerceptionComponent>(TEXT("AIPerceptionComponent"));
// 시각 설정
SightConfig = CreateDefaultSubobject<UAISenseConfig_Sight>(TEXT("SightConfig"));
// ... SightConfig 상세 설정 ...
AIPerceptionComponent->ConfigureSense(*SightConfig);
// 청각 설정 추가
UAISenseConfig_Hearing* HearingConfig = CreateDefaultSubobject<UAISenseConfig_Hearing>(TEXT("HearingConfig"));
HearingConfig->HearingRange = 2000.0f;
HearingConfig->DetectionByAffiliation.bDetectEnemies = true;
AIPerceptionComponent->ConfigureSense(*HearingConfig);
// 촉각 설정 추가
UAISenseConfig_Damage* DamageConfig = CreateDefaultSubobject<UAISenseConfig_Damage>(TEXT("DamageConfig"));
AIPerceptionComponent->ConfigureSense(*DamageConfig);
AIPerceptionComponent->SetDominantSense(SightConfig->GetSenseImplementation());
AIPerceptionComponent->OnTargetPerceptionUpdated.AddDynamic(this, &AAIC_Monster::OnTargetPerceptionUpdated);
}
// ... OnPossess, OnTargetPerceptionUpdated 등 ...
Die() 함수에서 죽음 애니메이션을 재생하고 SetLifeSpan으로 액터를 파괴했는데, 사라지기 직전 아주 잠깐 AI가 기본 자세로 돌아오는 현상이 있었다. 원인은 애니메이션과 캡슐 컴포넌트의 물리 상태가 충돌하는 것이었다. GetMesh()->SetSimulatePhysics(true)를 호출하여 래그돌(Ragdoll) 상태로 전환시켜주니, 훨씬 자연스럽게 쓰러지면서 문제가 해결됐다.BP_FirstPersonProjectile)에 Apply Damage 노드를 추가했는데도 AI가 반응이 없었다. 확인해보니 AI 몬스터의 캡슐 컴포넌트 콜리전 설정에서 총알(Projectile)과의 충돌이 무시(Ignore)로 설정되어 있었다. 콜리전 프리셋을 Pawn으로 제대로 설정하거나, Projectile 채널에 Block으로 응답하도록 수정하여 해결했다. 🔫| 개념 | 설명 | 비고 |
|---|---|---|
Anim Notify | 애니메이션 몽타주의 특정 프레임에 이벤트를 발생시키는 기능. | 공격 애니메이션의 타격 지점에 Anim Notify를 추가하고, Attack() 함수를 호출하여 데미지 판정을 동기화했다. |
Ragdoll | 캐릭터의 물리 시뮬레이션을 활성화하여 현실적인 쓰러짐 등을 연출하는 기술. | Die() 함수에서 SetSimulatePhysics(true)를 호출하여 구현. |
| 다중 감각 (Multi-Sense) | 시각, 청각, 촉각 등 여러 Perception을 함께 사용하여 AI의 인지 능력을 향상시키는 것. | AISenseConfig_Hearing, AISenseConfig_Damage를 추가하여 구현. |
AIPerceptionStimuliSource | 특정 액터가 AI에게 감지될 수 있는 '자극원'임을 등록하는 컴포넌트. | 플레이어 캐릭터에 이 컴포넌트를 추가하고 AISense_Hearing을 등록해야 AI가 플레이어의 소리를 들을 수 있다. |