Enemy Class 생성하기
HeathPoints, Strength, Armer, Attackrange등의 변수가 있고 이걸 처리할 메소드가 선언되어있다.
PawnSensingComponent를 추가했다. 감지하는 영억안으로 플레이어 캐릭터가 들어왔는지 체크했다. 캐릭터가 감지되면 _chasedTarget에 저장된다. 영역밖으로 벗어나면 nullptr로 설정된다.
Chase()는 플레이어캐릭터를 추격하는 함수이다.
key 입력을 받지 않으므로 Input관련 설정은 안해줘도 된다.
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "Enemy.generated.h"
UCLASS()
class PANGAEA_API AEnemy : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
AEnemy();
UPROPERTY(EditAnywhere, Category = "Enemy Params")
int HealthPoints = 100; //the character's max health points
UPROPERTY(EditAnywhere, Category = "Enemy Params")
float Strength = 5; //the character's attack strength
UPROPERTY(EditAnywhere, Category = "Enemy Params")
float Armer = 1; //the character's defense armer
UPROPERTY(EditAnywhere, Category = "Enemy Params")
float AttackRange = 200.0f; //the character's attack range
UPROPERTY(EditAnywhere, Category = "Enemy Params")
float AttackInterval = 3.0f; //the character's attack invertal
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
int _HealthPoints;
float _AttackCountingDown;
APawn* _chasedTarget = nullptr;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
UFUNCTION(BlueprintCallable, Category = "Pangaea|Enemy", meta = (DisplayName = "Get HP"))
int GetHealthPoints(); //get current health points
UFUNCTION(BlueprintCallable, Category = "Pangaea|Enemy")
bool IsKilled(); //check if the character has been killed
UFUNCTION(BlueprintCallable, Category = "Pangaea|Enemy")
bool CanAttack(); //check if the character can attack
UFUNCTION(BlueprintCallable, Category = "Pangaea|Enemy")
void Chase(APawn* targetPawn);
void Attack();
void Hit(int damage);
void DieProcess(); //process when the character is killed
private:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
class UPawnSensingComponent* PawnSensingComponent;
};
cpp에서 생성자에서
AEnemy::AEnemy()
{
PrimaryActorTick.bCanEverTick = true;
PawnSensingComponent = CreateDefaultSubobject<UPawnSensingComponent>(TEXT("PawnSensor"));
}
// Fill out your copyright notice in the Description page of Project Settings.
#include "Enemy.h"
#include "Perception/PawnSensingComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "EnemyController.h"
#include "EnemyAnimInstance.h"
// Sets default values
AEnemy::AEnemy()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
PawnSensingComponent = CreateDefaultSubobject<UPawnSensingComponent>(TEXT("PawnSensor"));
}
// Called when the game starts or when spawned
void AEnemy::BeginPlay()
{
Super::BeginPlay();
_HealthPoints = HealthPoints;
}
// Called every frame
void AEnemy::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
auto animInst = Cast<UEnemyAnimInstance>(GetMesh()->GetAnimInstance());
animInst->Speed = GetCharacterMovement()->Velocity.Size2D();
if (_AttackCountingDown == AttackInterval)
{
animInst->State = EEnemyState::Attack;
}
if (_AttackCountingDown > 0.0f)
{
_AttackCountingDown -= DeltaTime;
}
if (_chasedTarget != nullptr && animInst->State == EEnemyState::Locomotion)
{
auto enemyController = Cast<AEnemyController>(GetController());
enemyController->MakeAttackDecision(_chasedTarget);
}
}
int AEnemy::GetHealthPoints()
{
return _HealthPoints;
}
bool AEnemy::IsKilled()
{
return (_HealthPoints <= 0.0f);
}
bool AEnemy::CanAttack()
{
auto animInst = GetMesh()->GetAnimInstance();
auto enemyAnimInst = Cast<UEnemyAnimInstance>(animInst);
return (_AttackCountingDown <= 0.0f && enemyAnimInst->State == EEnemyState::Locomotion);
}
void AEnemy::Chase(APawn* targetPawn)
{
auto animInst = GetMesh()->GetAnimInstance();
auto enemyAnimInst = Cast<UEnemyAnimInstance>(animInst);
if (targetPawn != nullptr && enemyAnimInst->State == EEnemyState::Locomotion)
{
auto enemyController = Cast<AEnemyController>(GetController());
enemyController->MoveToActor(targetPawn, 90.0f);
}
_chasedTarget = targetPawn;
}
void AEnemy::Attack()
{
GetController()->StopMovement();
_AttackCountingDown = AttackInterval;
}
void AEnemy::Hit(int damage)
{
_HealthPoints -= damage;
auto animInst = GetMesh()->GetAnimInstance();
auto enemyAnimInst = Cast<UEnemyAnimInstance>(animInst);
enemyAnimInst->State = EEnemyState::Hit;
if (IsKilled())
{
PrimaryActorTick.bCanEverTick = false;
}
}
void AEnemy::DieProcess()
{
PrimaryActorTick.bCanEverTick = false;
K2_DestroyActor();
GEngine->ForceGarbageCollection(true);
}
Player 클래스와 비교해 Enemy 클래스가 추가적으로 수행하는 건 PawnSensingComponent를 생성하고 Chase 함수를 구현하는 것이다.
Chase 함수는 1개의 매개변수를 가지며, 이를 통해 쫒아야하는 타깃 폰을 전달한다. 함수는 우선 AIController의 서브클래스인 적 캐릭터의 컨트롤러를 획득하고 이어서 MoveToActor 함수를 호출해 타깃인 플레이어 캐릭터를 쫒게 만든다.
EnemyController Class 생성하기
언리얼은 NPC를 제어할수 있는 비헤이버트리나 블랙보드를 사용해 NPC의 행동을 설계할수 있다. 하지만 여기서는 AIController의 서브클래스를 만들고 스크립트를 작성해 적 캐릭터를 제어해볼 것이다.
AIController클래스를 상속받는 새로은 C++클래스인 EnemyController를 만들어 보자 앞서 생성한 C++클래스와 마찬가지로 EnemyController.h와 enemyController.cpp파일이 Pangaea폴더에 위치해야 한다.
EnemyController.h파일의 코드
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "AIController.h"
#include "EnemyController.generated.h"
/**
*
*/
UCLASS()
class PANGAEA_API AEnemyController : public AAIController
{
GENERATED_BODY()
public:
void MakeAttackDecision(APawn *targetPawn); //AI decision making. Called by PawnTick
};
EnemyController.cpp파일
MakeAttackDecision은 적이 공격범위에 있고 공격 가능하다면 Attack()함수를 호출한다.
// Fill out your copyright notice in the Description page of Project Settings.
#include "EnemyController.h"
#include "Enemy.h"
void AEnemyController::MakeAttackDecision(APawn* targetPawn)
{
auto controlledCharacter = Cast<AEnemy>(GetPawn());
auto dist = FVector::Dist2D(targetPawn->GetActorLocation(), GetPawn()->GetTargetLocation());
UE_LOG(LogTemp, Warning, TEXT("Distance=%d"), dist);
if (dist <= controlledCharacter->AttackRange && controlledCharacter->CanAttack())
{
controlledCharacter->Attack();
}
}
공격뿐만이 아니라 적이 이동하는것도 핵심적인 기능이다. AIConroller MoveToActor MoveToLocation MoveTo(FAIMoveRequest구조체) StopMovement 를 활용해 수행할수 있다.
AIController가 잘 수행되기 위해서는 NavMesh가 적절하게 설정돼야 한다. NavMesh를 생성하려면 게임 레벨에 NavMeshBoundsVolume을 드래그 앤 드롭한 다음. 지정된 영역안에 레벨이 포함되도록 해야 한다.
키보드의 P를 눌러 NaveMesh를 보이거나 숨길 수 있다. 녹생으로 표시되는 영역들이 NavMesh이다.
앞서 만든어 놓은 EnemyController를 활용해 적이 플레이어를 감지 추적 공격하게 만들자. 애님인스턴스와 애님블루프린터를 만들어 비헤이비어 애니메이션과 싱크를 맞춘다.
AnimInstance를 상속받는 EnemyAnimInstance클래스를 만들자 헤더 파일에는 다음과 같다
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Animation/AnimInstance.h"
#include "EnemyAnimInstance.generated.h"
UENUM(BlueprintType)
enum class EEnemyState : uint8
{
Locomotion,
Attack,
Hit,
Die
};
/**
*
*/
UCLASS()
class PANGAEA_API UEnemyAnimInstance : public UAnimInstance
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Enemy Params")
float Speed;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Enemy Params")
EEnemyState State;
UFUNCTION(BlueprintCallable)
void OnStateAnimationEnds();
};
EnemyAnimInstance.cpp는 다음과 같다.
// Fill out your copyright notice in the Description page of Project Settings.
#include "EnemyAnimInstance.h"
#include "Enemy.h"
void UEnemyAnimInstance::OnStateAnimationEnds()
{
if (State == EEnemyState::Attack)
{
State = EEnemyState::Locomotion;
}
else
{
auto enemy = Cast<AEnemy>(GetOwningActor());
if (State == EEnemyState::Hit)
{
if (enemy->GetHealthPoints() > 0.0f)
{
State = EEnemyState::Locomotion;
}
else
{
State = EEnemyState::Die;
}
}
else if (State == EEnemyState::Die)
{
enemy->DieProcess();
}
}
}
ABP_Enemy 애니메이션 블루프린트 생성하기
저자가 이걸 보고 만들라고 하네
https://www.youtube.com/watch?v=xm-7m5Fw1HU
ABP_Enemy
'언리얼러닝 > C++스크립트게임개발' 카테고리의 다른 글
복제를 통한 클라이언트와 액터 변수 동기화 하기 (0) | 2025.06.07 |
---|---|
네트워크 멀티플레이어 게임 만들기 - RPC (0) | 2025.06.07 |
EnhancedInput vs LegacyInput, Input Error (0) | 2025.05.27 |
7장 CanAttack() Attack() 함수 구현하기 (0) | 2025.05.27 |
07 PlayerController 캐릭터 제어하기 SimpleMoveToLocation (1) | 2025.05.24 |