
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()) ์คํ |