[DAY54] Shooter Project(4) : Completion of AI combat & multiple senses

베리투스·2025년 10월 27일
0

Shooter Project

목록 보기
5/10

오늘은 AI의 전투 시스템을 완성하는 데 집중했다. 공격 애니메이션과 실제 데미지 판정을 연동하여 AI가 플레이어에게 실질적인 위협을 가하도록 만들었으며, 피격 및 죽음 시퀀스를 구현하여 전투의 한 사이클을 완성했다. 또한, 기존의 시각(Sight) 감각에만 의존하던 AI의 한계를 극복하기 위해 청각(Hearing)과 촉각(Damage Sense)을 추가하여, 플레이어가 등 뒤에 있어도 인지할 수 있는 훨씬 더 똑똑한 AI의 기반을 다졌다.


📌 목표

  • AI의 공격 애니메이션에 맞춰 정확한 타이밍에 데미지가 들어가도록 구현
  • AI의 피격 리액션 및 죽음 애니메이션, 래그돌(Ragdoll) 전환 로직 완성
  • 시각(Sight) 외에 청각(Hearing), 촉각(Damage Sense) Perception 추가
  • 플레이어가 내는 소리를 AI가 감지할 수 있도록 AIPerceptionStimuliSource 설정

💻 코드

AIMonsterBase.h

#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;
};

AIMonsterBase.cpp

#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초 후 소멸
}

AIC_Monster.cpp

기존의 시각(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 등 ...

⚠️ 실수

  • 죽은 AI가 벌떡 일어나는 문제: Die() 함수에서 죽음 애니메이션을 재생하고 SetLifeSpan으로 액터를 파괴했는데, 사라지기 직전 아주 잠깐 AI가 기본 자세로 돌아오는 현상이 있었다. 원인은 애니메이션과 캡슐 컴포넌트의 물리 상태가 충돌하는 것이었다. GetMesh()->SetSimulatePhysics(true)를 호출하여 래그돌(Ragdoll) 상태로 전환시켜주니, 훨씬 자연스럽게 쓰러지면서 문제가 해결됐다.
  • 총알을 쏴도 AI가 데미지를 안 입는 문제: 플레이어의 총알 블루프린트(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가 플레이어의 소리를 들을 수 있다.
profile
Shin Ji Yong // Unreal Engine 5 공부중입니다~

0개의 댓글