이제 순찰 중에 플레이어를 만나면 쫓아가도록 구현해보자
이때 Pawn Sensing Component를 통해 플레이어를 감지할 수 있다.
블루프린트 상에서 추가해보면 다양한 속성을 볼 수 있는데 이때 듣는것과 보는것 모두 감지거리를 조정해줄 수 있다.
LOS는 최대로 들을 수 있는 거리이다.
이를 코드로 구현해보자. 우선 UPawnSensingComponent 를 전방선언해주고 이를 DefaultSubObject로 생성해주고 시야각과 시야범위를 설정해주자. 그리고 플레이어를 감지했을 때의 콜백 함수를 지정해주자.
Enemy.h
private:
UPROPERTY(VisibleAnywhere)
class UPawnSensingComponent* PawnSensing;
protected:
UFUNCTION()
void PawnSeen(APawn* SeenPawn);
Enemy.cpp
AEnemy::AEnemy()
{
PawnSensing = CreateDefaultSubobject<UPawnSensingComponent>(TEXT("PawnSensing"));
PawnSensing->SightRadius = 4000.f;
PawnSensing->SetPeripheralVisionAngle(45.f);
}
void AEnemy::BeginPlay()
{
Super::BeginPlay();
if (HealthBarWidget)
{
HealthBarWidget->SetVisibility(false);
}
EnemyController = Cast<AAIController>(GetController());
MoveToTarget(PatrolTarget);
if (PawnSensing)
{
PawnSensing->OnSeePawn.AddDynamic(this, &AEnemy::PawnSeen);
}
}
void AEnemy::PawnSeen(APawn* SeenPawn)
{
}
이렇게 해준 다음 이제 적의 Patrol Chase Attack 상태를 나누면서 쫓아가는 로직 구현해보자
우선 enum class를 통해 각 state를 나눠주자.
UENUM(BlueprintType)
enum class EEnemyState : uint8
{
EES_Patrolling UMETA(DisplayName = "Patrolling"),
EES_Chasing UMETA(DisplayName = "Chasing"),
EES_Attacking UMETA(DisplayName = "Attacking"),
};
이렇게 해준 다음 Enemy클래스에서 이 enum class 변수를 가지고 있게 하자.
Enemy.h
private:
EEnemyState EnemyState = EEnemyState::EES_Patrolling;
그리고 Pawn을 발견했을 때 쫓아가도록 해야한다. 이때 Cast로 검사를 해주게 되면 이 함수의 연산 간격이 짧기 때문에 연산부하가 많이 지게 된다. 이때 특정 태그값을 가지고 있는지 검사해주면 된다.
SlashCharacter.cpp
void ASlashCharacter::BeginPlay()
{
Tags.Add(FName("SlashCharacter"));
}
Enemy.cpp
void AEnemy::PawnSeen(APawn* SeenPawn)
{
if (EnemyState == EEnemyState::EES_Chasing) return;
//Chase
if (SeenPawn->ActorHasTag(FName("SlashCharacter")))
{
EnemyState = EEnemyState::EES_Chasing;
GetWorldTimerManager().ClearTimer(PatrolTimer);
GetCharacterMovement()->MaxWalkSpeed=300.f;
CombatTarget=SeenPawn;
MoveToTarget(CombatTarget);
}
}
이렇게 해주면 잘 쫓아오는 것을 알 수 있다.
이렇게 해주고 반경 안에서 사리지면 다시 Patrol하도록 해주자.
void AEnemy::CheckCombatTarget()
{
if (!InTargetRange(CombatTarget, CombatRadius))
{
//바깥이면 Patrol로
CombatTarget = nullptr;
if (HealthBarWidget)
{
HealthBarWidget->SetVisibility(false);
Attributes->SetHealth(100.f);
}
EnemyState = EEnemyState::EES_Patrolling;
GetCharacterMovement()->MaxWalkSpeed = 125.f;
MoveToTarget(PatrolTarget);
}
}
이제 여기서 Attack State로 바꾸고 Attack을 할 수 있게 Attack Radius를 지정해주어야 한다. 그리고 이제 CheckCombatTarget함수에서 플레이어와의 거리를 측정해서 Patrol Chase Attack상태가 나눠지도록 조건문을 구성해주자. 이때 PawnSeen함수에서 공격상태가 아닐때만 Chase하도록 해주자.
Enemy.h
private:
UPROPERTY(EditAnywhere)
double AttackRadius = 150.f;
Enemy.cpp
void AEnemy::PawnSeen(APawn* SeenPawn)
{
if (EnemyState == EEnemyState::EES_Chasing) return;
//Chase
if (SeenPawn->ActorHasTag(FName("SlashCharacter")))
{
GetWorldTimerManager().ClearTimer(PatrolTimer);
GetCharacterMovement()->MaxWalkSpeed=300.f;
CombatTarget=SeenPawn;
if (EnemyState != EEnemyState::EES_Attacking)
{
EnemyState = EEnemyState::EES_Chasing;
MoveToTarget(CombatTarget);
}
}
}
void AEnemy::CheckCombatTarget()
{
if (!InTargetRange(CombatTarget, CombatRadius))
{
//바깥이면 Patrol로
CombatTarget = nullptr;
if (HealthBarWidget)
{
HealthBarWidget->SetVisibility(false);
Attributes->SetHealth(100.f);
}
EnemyState = EEnemyState::EES_Patrolling;
GetCharacterMovement()->MaxWalkSpeed = 125.f;
MoveToTarget(PatrolTarget);
}
else if (!InTargetRange(CombatTarget, AttackRadius) && EnemyState != EEnemyState::EES_Chasing)
{
//공격반경 바깥
EnemyState = EEnemyState::EES_Chasing;
GetCharacterMovement()->MaxWalkSpeed = 300.f;
MoveToTarget(CombatTarget);
}
else if (InTargetRange(CombatTarget, AttackRadius) && EnemyState != EEnemyState::EES_Attacking)
{
//공격 반경 내부
EnemyState = EEnemyState::EES_Attacking;
//공격
}
}
하지만 지금만약 적의 뒤에서 공격을 하면 아무 반응이 없다. 이러한 점을 고쳐보도록 하자. 이는 TakeDamage함수에서 지정해줄 수 있다.
float AEnemy::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
if (Attributes)
{
Attributes->ReceiveDamage(DamageAmount);
HealthBarWidget->SetHealthPercent(Attributes->GetHealthPercent());
}
CombatTarget = EventInstigator->GetPawn();
EnemyState = EEnemyState::EES_Chasing;
GetCharacterMovement()->MaxWalkSpeed = 300.f;
MoveToTarget(CombatTarget);
return DamageAmount;
}
마지막으로 적에게 무기를 장착하기 위해 다양한 무기들을 추가해보자. 이때 무기 에셋마다 각 Origin(Pivot)이 다르기 때문에 조심해야한다.
우선 무료 무기 에셋 중에 Free Fantasy Weapon Sample Pack이라는 에셋을 추가하여 사용해보자. 무기 중에 Axe를 보면 원점이 손잡이에 있는 것을 볼 수 있다.
https://www.fab.com/ko/listings/d5be0dc9-1a41-4be2-a63a-5ed436f3445d
이 Axe로 매쉬를 변경하고 실행해서 잡아보면 문제가 생기는 것을 알 수 있다. 손에서 떨어져 있으며 도끼의 방향도 잘 못된것을 알 수 있다.
이렇게 된 무기들의 원점을 동일하게 바꿔주어야하는데 이를 위해 Blender를 사용하자. Blendor에서 사용하려면 해당 에셋을 Export해주어야한다.
Blender에서는 fbx 파일을 가져와서 에디트 모드로 a키를 눌러 모두 선택한 다음들어간다음 R키를 누르고 Y키를 눌러 y축이 고정된 상태로 180도 회전시켜주면된다. 그리고 G키와 Z키를 통해 원점을 Z축이 고정된 상태로 손잡이로 바꾸주자.
마지막으로 크기는 S키를 누르면 변경할 수있다. 완성된 fbx파일은 이름을 기존 것과 다르게 해준 다음 다시 언리얼에서 임폴트 해주자.
임폴트한 에셋에 머티리얼은 이전것과 동일한 것으로 지정해주자. 이렇게 해주면 이전과 다르게 잘 잡고 있는 것을 볼 수 있다.
'게임공부 > Unreal Engine' 카테고리의 다른 글
[Unreal Engine][C++]25. Patrol (0) | 2025.02.07 |
---|---|
[Unreal Engine][C++]24. Enemey Behavior2 (0) | 2025.02.05 |
[Unreal Engine][C++]23. Enemey Behavior (0) | 2025.01.27 |
[Unreal Engine][C++]22. Death (1) | 2025.01.26 |
[Unreal Engine][C++]21. Damage (1) | 2025.01.21 |