[66. Enemy Combat #1]
폰은 기능이 많지 않은 차량이나 기본적인 입력만을 받고 돌아다니는 것들에 적합하다.
하지만 캐릭터에 기능들이 좀 더 다양하므로 해당 실습에서는 폰이 아닌 캐릭터 클래스를 기반으로 적을 구현하도록 한다.
캐릭터를 상속받는 Enemy 클래스를 만든다.
해당 C++ 클래스를 블루프린트 클래스로 만들고 프로토타입을 작성해보도록 한다.
Sphere Collision을 하나 추가해서 반경을 600으로 크게 설정한다.
![](https://blog.kakaocdn.net/dn/dgWdrZ/btrLTDcPyov/BDq32vJe5jvwIGhhpQZMMK/img.png)
OtherActor를 Main캐릭터로 형변환 후 TargetActor로 둔다.
Acceptance Radius만큼의 Target 반경에 접근하면 이동이 완료된 것으로 간주한다.
다만 이상태로는 실행을 해도 추적하지 않는다. 추적하는 경로를 모르기 때문이다.
그래서 내비게이션 메시를 깔아주면 경로 계산이 가능해져서 추적을 하게 된다.
![](https://blog.kakaocdn.net/dn/6lClq/btrLLydo11E/HXG8DwmH022az7q2gSktek/img.png)
![](https://blog.kakaocdn.net/dn/3PrjM/btrLNqy1UG4/fZhZC7DpmCj1R6k0CiZCs1/img.png)
스케일을 50, 50, 50 정도로 설정하면 매우 넓게 설정이 된다. 캐릭터와 적이 내비게이션 메시 위에 동시에 존재하면서 겹침 이벤트가 발생 시 추적하게 된다.
이제 C++에서 구현할 것이기 때문에 프로토타입으로 작성한 블루프린트 이벤트는 지워주도록 한다.
// Enemy.h
UENUM(BlueprintType)
enum class EEnemyMovementStatus : uint8
{
EMS_Idle UMETA(DisplayName="Idle"),
EMS_MoveToTarget UMETA(DisplayName="MoveToTarget"),
EMS_Attacking UMETA(DisplayName="Attacking"),
EMS_MAX UMETA(DisplayName="DefaultMAX")
};
UCLASS()
class FIRSTPROJECT_API AEnemy : public ACharacter
{
GENERATED_BODY()
public:
AEnemy();
protected:
virtual void BeginPlay() override;
public:
virtual void Tick(float DeltaTime) override;
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
public:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Movement")
EEnemyMovementStatus EnemyMovementStatus;
FORCEINLINE void SetEnemyMovementStatus(EEnemyMovementStatus Status) { EnemyMovementStatus = Status; }
};
// EnemyAnimInstance.h
public:
virtual void NativeInitializeAnimation() override;
public:
UFUNCTION(BlueprintCallable, Category = AnimationProperties)
void UpdateAnimationProperties();
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Movement)
float MovementSpeed;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Movement)
class APawn* Pawn;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Movement)
class AEnemy* Enemy;
};
// EnemyAnimInstance.cpp
#include "Enemy.h"
void UEnemyAnimInstance::NativeInitializeAnimation()
{
if (Pawn == nullptr)
{
Pawn = TryGetPawnOwner();
if (Pawn)
{
Enemy = Cast<AEnemy>(Pawn);
}
}
}
void UEnemyAnimInstance::UpdateAnimationProperties()
{
if (Pawn == nullptr)
{
Pawn = TryGetPawnOwner();
if (Pawn)
{
Enemy = Cast<AEnemy>(Pawn);
}
}
if (Pawn)
{
FVector Speed = Pawn->GetVelocity();
FVector LateralSpeed = FVector(Speed.X, Speed.Y, 0.f);
MovementSpeed = LateralSpeed.Size();
}
}
애니메이션도 추가해 주도록 한다.
![](https://blog.kakaocdn.net/dn/d74JwQ/btrLLhQxLOm/gZTPyHU81156tvhMkn2cPk/img.png)
![](https://blog.kakaocdn.net/dn/y8bHt/btrLOfqwYPL/X4d9RgegIECF9W3lNwuGh1/img.png)
블렌드 스페이스에서 최대값은 150으로 설정한다.
[67. Ememy Combat #2]
블루프린트에서 프로토타입으로 확인했던 내용을 C++로 구현해볼 차례이다.
일정 범위 안에 들어오면 캐릭터를 추적하고 공격하게 할 것이다.
// Enemy.h
UENUM(BlueprintType)
enum class EEnemyMovementStatus : uint8
{
EMS_Idle UMETA(DisplayName="Idle"),
EMS_MoveToTarget UMETA(DisplayName="MoveToTarget"),
EMS_Attacking UMETA(DisplayName="Attacking"),
EMS_MAX UMETA(DisplayName="DefaultMAX")
};
UCLASS()
class FIRSTPROJECT_API AEnemy : public ACharacter
{
GENERATED_BODY()
public:
AEnemy();
protected:
virtual void BeginPlay() override;
public:
virtual void Tick(float DeltaTime) override;
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
public:
// 어그로 범위, 공격 범위를 따로 구분한다.
UFUNCTION()
virtual void AgroSphereOnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
UFUNCTION()
virtual void AgroSphereOnOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
UFUNCTION()
virtual void CombatSphereOnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
UFUNCTION()
virtual void CombatSphereOnOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
// 타겟으로 이동
void MoveToTarget(class AMain* Target);
// getter, setter
FORCEINLINE void SetEnemyMovementStatus(EEnemyMovementStatus Status) { EnemyMovementStatus = Status; }
FORCEINLINE EEnemyMovementStatus GetEnemyMovementStatus() { return EnemyMovementStatus; }
public:
// 행동 상태
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Movement")
EEnemyMovementStatus EnemyMovementStatus;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="AI")
class USphereComponent* AgroSphere;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "AI")
USphereComponent* CombatSphere;
// AI를 조종하기 위한 컨트롤러
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "AI")
class AAIController* AIController;
};
#include "Components/SphereComponent.h"
#include "AIController.h"
#include "Main.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;
AgroSphere = CreateDefaultSubobject<USphereComponent>(TEXT("AgroSphere"));
AgroSphere->SetupAttachment(GetRootComponent());
AgroSphere->InitSphereRadius(600.f);
CombatSphere = CreateDefaultSubobject<USphereComponent>(TEXT("CombatSphere"));
CombatSphere->SetupAttachment(GetRootComponent());
CombatSphere->InitSphereRadius(75.f);
}
void AEnemy::BeginPlay()
{
Super::BeginPlay();
AIController = Cast<AAIController>(GetController());
AgroSphere->OnComponentBeginOverlap.AddDynamic(this, &AEnemy::AgroSphereOnOverlapBegin);
AgroSphere->OnComponentEndOverlap.AddDynamic(this, &AEnemy::AgroSphereOnOverlapEnd);
CombatSphere->OnComponentBeginOverlap.AddDynamic(this, &AEnemy::CombatSphereOnOverlapBegin);
CombatSphere->OnComponentEndOverlap.AddDynamic(this, &AEnemy::CombatSphereOnOverlapEnd);
}
void AEnemy::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
}
void AEnemy::AgroSphereOnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
if (OtherActor)
{
AMain* Main = Cast<AMain>(OtherActor);
if (Main)
{
MoveToTarget(Main);
}
}
}
void AEnemy::AgroSphereOnOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
}
void AEnemy::CombatSphereOnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
}
void AEnemy::CombatSphereOnOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
}
void AEnemy::MoveToTarget(AMain* Target)
{
SetEnemyMovementStatus(EEnemyMovementStatus::EMS_MoveToTarget);
if (AIController)
{
UE_LOG(LogTemp, Warning, TEXT("MoveToTarget"));
}
}
AI와 관련된 모듈을 이용하려면 프로젝트의 cs파일에서 공개 의존성 모듈에 "AIModule" 을 추가해 주어야 컴파일 오류가 발생하지 않는다.
우선 어그로 범위 감지용 구체와 전투 범위 감지용 구체 2개를 만들어준다.
각각의 겹침 판정에 대해 함수도 바인딩 해준다.
우선 테스트를 위해 어그로 구체와 겹칠 시 로그를 띄우도록 했고 실행 시 잘 작동하게 된다.
이제 프로토타입에서 사용했던 MoveTo를 직접 코드로 작성해 본다.
virtual FPathFollowingRequestResult MoveTo
(
const FAIMoveRequest & MoveRequest,
FNavPathSharedPtr * OutPath
)
두개의 인자를 받는데, 첫번째는 목표에 대한 구조체이다. 접근 반경이나 길찾기 여부 등의 정보가 담겨있다. 두번째는 경로와 관련이 있는 구조체이다. OutPath라는것을 보면 알수 있듯이 경로가 OutPath에 담겨진다.
void AEnemy::MoveToTarget(AMain* Target)
{
SetEnemyMovementStatus(EEnemyMovementStatus::EMS_MoveToTarget);
if (AIController)
{
FAIMoveRequest MoveRequest;
MoveRequest.SetGoalActor(Target); // 목표 액터 설정
MoveRequest.SetAcceptanceRadius(5.f); // 수용(접근) 범위
FNavPathSharedPtr NavPath;
AIController->MoveTo(MoveRequest, &NavPath);
}
}
애님 블루프린트에서 UpdateAnimationProperties까지 연결시켜주면 거미들이 쫓아올 때 애니메이션이 재생 된다.
[정리]
- AIController를 사용하려면 cs파일의 공개 의존성 모듈에 "AIModule"을 추가하여야 한다.
- AI가 이동하는데 사용하는 함수들은 여러종류가 있다.
https://blog.naver.com/raveneer/220796853517
'언리얼 엔진 > UE4 C++' 카테고리의 다른 글
[UE C++] Combat #5 (0) | 2022.09.12 |
---|---|
[UE C++] Combat #4 (0) | 2022.09.11 |
[UE C++] Combat #2 (0) | 2022.09.11 |
[UE C++] Combat #1 (0) | 2022.09.10 |
[UE C++] Gameplay Mechanics #7 (0) | 2022.09.09 |