
Games > First Person ν
νλ¦ΏWith Starter Content μ ν κ°λ₯MonShootingWith C++ νμ±ν!μ΄ ν΄λμ€λ μ€μ λͺ¬μ€ν° μΊλ¦ν°μ μμ§μ, 곡격, λΆλ Έ μν λ±μ λ΄λΉν©λλ€.
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(); // λΆλ
Έ μ ν
};
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("λͺ¬μ€ν°κ° λΆλ
Έ μνκ° λμμ΅λλ€!"));
}
μ΄ ν΄λμ€λ AIμ 'λλ'μ λλ€. Behavior Treeλ₯Ό μ€ννκ³ , μΆμ λμμ μ€μ νλ μν μ ν©λλ€.
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);
};
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);
}
}
AIμ νλμ μκ°μ μΌλ‘ μ‘°μ ν μ μλ μμ€ν μ λλ€.
Blackboardλ κΈ°μ΅ μ μ₯μ,Behavior Treeλ νλ μ μ°¨μ λλ€.
| Key μ΄λ¦ | νμ | μ©λ |
|---|---|---|
| TargetActor | Object | μΆμ λμ |
| IsEnraged | Bool | λΆλ Έ μν |
[Root]
βββ Selector
βββ Sequence
β βββ Check: TargetActor != null
β βββ MoveTo(TargetActor)
βββ Task: AttackPlayer
AttackPlayerλ₯Ό Behavior Tree μμμ μ€νν μ μκ² ν΄μ£Όλ C++ ν΄λμ€μ λλ€.
UCLASS()
class MONSHOOTING_API UBTTask_AttackPlayer : public UBTTaskNode
{
GENERATED_BODY()
public:
UBTTask_AttackPlayer();
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
};
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 | νλ μ΄μ΄μμ 거리 μ²΄ν¬ β μΆμ β 곡격 β λΆλ Έ μν μ ν |
| MonsterAIController | Behavior Tree μ€ν λ° νκ² μ§μ |
| Blackboard | TargetActor, IsEnraged μνκ° μ μ₯ λ° κ΄λ¦¬ |
| Behavior Tree | μΆμ ·곡격 νλ‘μ° μ μ΄ |
| 컀μ€ν Task | BTμμ C++ 곡격 ν¨μ(AttackPlayer()) μ€ν |