[Day 4] AI Monster with C++

베리투슀·2025λ…„ 8μ›” 7일

TIL: Today I Learned

λͺ©λ‘ 보기
12/87

πŸ“Œ λͺ©ν‘œ

  • ν”Œλ ˆμ΄μ–΄λ₯Ό μΈμ‹ν•˜κ³  μΆ”μ ν•˜λŠ” λͺ¬μŠ€ν„° AIλ₯Ό λ§Œλ“ λ‹€
  • κ·Όμ ‘ν•˜λ©΄ κ³΅κ²©ν•œλ‹€
  • 일정 μ‹œκ°„ 이상 κ°€κΉŒμ΄ 있으면 'λΆ„λ…Έ μƒνƒœ'둜 μ „ν™˜ν•˜μ—¬ 더 빨라진닀

βœ… ν”„λ‘œμ νŠΈ 생성

  • Unreal Engine 5 μ‹€ν–‰
  • μƒˆ ν”„λ‘œμ νŠΈ > Games > First Person ν…œν”Œλ¦Ώ
  • With Starter Content 선택 κ°€λŠ₯
  • ν”„λ‘œμ νŠΈ 이름: MonShooting
  • With C++ ν™œμ„±ν™”!

🧱 Step 1: MonsterCharacter 클래슀

이 ν΄λž˜μŠ€λŠ” μ‹€μ œ λͺ¬μŠ€ν„° μΊλ¦­ν„°μ˜ μ›€μ§μž„, 곡격, λΆ„λ…Έ μƒνƒœ 등을 λ‹΄λ‹Ήν•©λ‹ˆλ‹€.

MonsterCharacter.h

UCLASS()
class MONSHOOTING_API AMonsterCharacter : public ACharacter
{
	GENERATED_BODY()

public:
	AMonsterCharacter();

protected:
	virtual void BeginPlay() override;

public:	
	virtual void Tick(float DeltaTime) override;

	UPROPERTY(EditAnywhere, Category = "AI")
	float DetectionRange = 1000.0f;  // ν”Œλ ˆμ΄μ–΄ 감지 거리

	UPROPERTY(EditAnywhere, Category = "AI")
	float AttackRange = 150.0f;      // 곡격 거리

	UPROPERTY(EditAnywhere, Category = "AI")
	float EnrageTime = 5.0f;         // λͺ‡ 초 이상 κ°€κΉŒμ΄ 있으면 λΆ„λ…Έ

	UPROPERTY(EditAnywhere, Category = "AI")
	float NormalSpeed = 300.0f;

	UPROPERTY(EditAnywhere, Category = "AI")
	float EnragedSpeed = 600.0f;

private:
	APawn* TargetPlayer;   // 좔적 λŒ€μƒ
	float TimeInRange;     // κ·Όμ ‘ μ‹œκ°„ λˆ„μ 
	bool bIsEnraged;       // λΆ„λ…Έ μƒνƒœ μ—¬λΆ€

	void CheckPlayerProximity(float DeltaTime); // 거리 체크
	void AttackPlayer();                        // 곡격 μ‹€ν–‰
	void BecomeEnraged();                       // λΆ„λ…Έ μ „ν™˜
};

MonsterCharacter.cpp

AMonsterCharacter::AMonsterCharacter()
{
	PrimaryActorTick.bCanEverTick = true;

	// κΈ°λ³Έ 이동 속도 μ„€μ •
	GetCharacterMovement()->MaxWalkSpeed = NormalSpeed;
}
void AMonsterCharacter::BeginPlay()
{
	Super::BeginPlay();

	// νƒ€κ²Ÿμ„ ν˜„μž¬ ν”Œλ ˆμ΄μ–΄λ‘œ μ„€μ •
	TargetPlayer = UGameplayStatics::GetPlayerPawn(GetWorld(), 0);

	// μ΄ˆκΈ°ν™”
	TimeInRange = 0.0f;
	bIsEnraged = false;
}
// λ§€ ν”„λ ˆμž„λ§ˆλ‹€ 거리 체크 μˆ˜ν–‰
void AMonsterCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	if (TargetPlayer)
	{
		CheckPlayerProximity(DeltaTime);
	}
}
void AMonsterCharacter::CheckPlayerProximity(float DeltaTime)
{
	// ν”Œλ ˆμ΄μ–΄μ™€μ˜ 거리 계산
	float Distance = FVector::Dist(GetActorLocation(), TargetPlayer->GetActorLocation());

	if (Distance <= DetectionRange)
	{
		// AIControllerλ₯Ό 톡해 좔적 μ‹œμž‘
		AAIController* AIController = Cast<AAIController>(GetController());
		if (AIController)
		{
			AIController->MoveToActor(TargetPlayer);
		}

		// 일정 μ‹œκ°„ κ°€κΉŒμ΄ 있으면 λΆ„λ…Έ μƒνƒœλ‘œ
		TimeInRange += DeltaTime;
		if (!bIsEnraged && TimeInRange >= EnrageTime)
		{
			BecomeEnraged();
		}

		// 곡격 거리 μ•ˆμ΄λ©΄ 곡격
		if (Distance <= AttackRange)
		{
			AttackPlayer();
		}
	}
	else
	{
		// λ©€μ–΄μ§€λ©΄ 타이머 μ΄ˆκΈ°ν™”
		TimeInRange = 0.0f;
	}
}
void AMonsterCharacter::AttackPlayer()
{
	// μ§€κΈˆμ€ 둜그만 좜λ ₯ (λ‚˜μ€‘μ— 데미지 μΆ”κ°€ κ°€λŠ₯)
	UE_LOG(LogTemp, Warning, TEXT("λͺ¬μŠ€ν„°κ°€ ν”Œλ ˆμ΄μ–΄λ₯Ό κ³΅κ²©ν•©λ‹ˆλ‹€!"));
}

void AMonsterCharacter::BecomeEnraged()
{
	bIsEnraged = true;
	GetCharacterMovement()->MaxWalkSpeed = EnragedSpeed;
	UE_LOG(LogTemp, Warning, TEXT("λͺ¬μŠ€ν„°κ°€ λΆ„λ…Έ μƒνƒœκ°€ λ˜μ—ˆμŠ΅λ‹ˆλ‹€!"));
}

🧠 Step 2: MonsterAIController 클래슀

이 ν΄λž˜μŠ€λŠ” AI의 'λ‘λ‡Œ'μž…λ‹ˆλ‹€. Behavior Treeλ₯Ό μ‹€ν–‰ν•˜κ³ , 좔적 λŒ€μƒμ„ μ„€μ •ν•˜λŠ” 역할을 ν•©λ‹ˆλ‹€.

MonsterAIController.h

UCLASS()
class MONSHOOTING_API AMonsterAIController : public AAIController
{
	GENERATED_BODY()

public:
	AMonsterAIController();
	virtual void BeginPlay() override;

	UPROPERTY(EditDefaultsOnly, Category = "AI")
	UBehaviorTree* BehaviorTreeAsset;

	void SetTargetActor(AActor* NewTarget);
};

MonsterAIController.cpp

AMonsterAIController::AMonsterAIController()
{
	// 에셋 λ‘œλ“œ: Behavior TreeλŠ” λ¦¬μ†ŒμŠ€ κ²½λ‘œκ°€ μ •ν™•ν•΄μ•Ό ν•©λ‹ˆλ‹€!
	static ConstructorHelpers::FObjectFinder<UBehaviorTree> BTAsset(TEXT("/Game/AI/BT_MonsterBehavior"));
	if (BTAsset.Succeeded())
	{
		BehaviorTreeAsset = BTAsset.Object;
	}
}
void AMonsterAIController::BeginPlay()
{
	Super::BeginPlay();

	if (BehaviorTreeAsset)
	{
		RunBehaviorTree(BehaviorTreeAsset);

		// νƒ€κ²Ÿμ„ ν”Œλ ˆμ΄μ–΄λ‘œ μ„€μ •
		APawn* PlayerPawn = UGameplayStatics::GetPlayerPawn(GetWorld(), 0);
		if (PlayerPawn)
		{
			SetTargetActor(PlayerPawn);
		}
	}
}
void AMonsterAIController::SetTargetActor(AActor* NewTarget)
{
	if (GetBlackboardComponent())
	{
		GetBlackboardComponent()->SetValueAsObject(TEXT("TargetActor"), NewTarget);
	}
}

🧩 Step 3: Behavior Tree & Blackboard

AI의 행동을 μ‹œκ°μ μœΌλ‘œ μ‘°μ ˆν•  수 μžˆλŠ” μ‹œμŠ€ν…œμž…λ‹ˆλ‹€.
BlackboardλŠ” κΈ°μ–΅ μ €μž₯μ†Œ, Behavior TreeλŠ” 행동 μ ˆμ°¨μž…λ‹ˆλ‹€.

Blackboard Key λͺ©λ‘

Key μ΄λ¦„νƒ€μž…μš©λ„
TargetActorObject좔적 λŒ€μƒ
IsEnragedBoolλΆ„λ…Έ μƒνƒœ

Behavior Tree ꡬ쑰

[Root]
 └── Selector
      β”œβ”€β”€ Sequence
      β”‚    β”œβ”€β”€ Check: TargetActor != null
      β”‚    └── MoveTo(TargetActor)
      └── Task: AttackPlayer

βš™οΈ Step 4: μ»€μŠ€ν…€ Task λ…Έλ“œ

AttackPlayerλ₯Ό Behavior Tree μ•ˆμ—μ„œ μ‹€ν–‰ν•  수 있게 ν•΄μ£ΌλŠ” C++ ν΄λž˜μŠ€μž…λ‹ˆλ‹€.

BTTask_AttackPlayer.h

UCLASS()
class MONSHOOTING_API UBTTask_AttackPlayer : public UBTTaskNode
{
	GENERATED_BODY()

public:
	UBTTask_AttackPlayer();
	virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
};

BTTask_AttackPlayer.cpp

EBTNodeResult::Type UBTTask_AttackPlayer::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
	AAIController* AIController = OwnerComp.GetAIOwner();
	if (!AIController) return EBTNodeResult::Failed;

	AMonsterCharacter* Monster = Cast<AMonsterCharacter>(AIController->GetPawn());
	if (!Monster) return EBTNodeResult::Failed;

	Monster->AttackPlayer(); // μ‹€μ œ 곡격 μ‹€ν–‰

	return EBTNodeResult::Succeeded;
}

βœ… 핡심 μš”μ•½

ꡬ성 μš”μ†Œμ—­ν• 
MonsterCharacterν”Œλ ˆμ΄μ–΄μ™€μ˜ 거리 체크 β†’ 좔적 β†’ 곡격 β†’ λΆ„λ…Έ μƒνƒœ μ „ν™˜
MonsterAIControllerBehavior Tree μ‹€ν–‰ 및 νƒ€κ²Ÿ μ§€μ •
BlackboardTargetActor, IsEnraged μƒνƒœκ°’ μ €μž₯ 및 관리
Behavior Tree좔적·곡격 ν”Œλ‘œμš° μ œμ–΄
μ»€μŠ€ν…€ TaskBTμ—μ„œ C++ 곡격 ν•¨μˆ˜(AttackPlayer()) μ‹€ν–‰

profile
Shin Ji Yong // Unreal Engine 5 κ³΅λΆ€μ€‘μž…λ‹ˆλ‹€~

0개의 λŒ“κΈ€