ActableOneInterface 클래스 추가
무기를 만들기전에 먼저 새 C++클래스 - Unreal Interface 부모 클래스 - ActableOneInterface 클래스를 하나 생성해준다.
인터페이스로 만들었으므로, 마찬가지로 cpp은 사용하지 않을 예정이므로 지워준다.
언리얼 프로젝트에서 입력 - 액션 매핑에 공격과 관련된 키 입력들을
추가해주었다.
* 액션 매핑
- ActA : 왼쪽 마우스 버튼
- ActB : 오른쪽 마우스 버튼
- ActC : R
IActableOneInterface 클래스에 키 입력 시 호출될 BeginAction()함수들과 종료 시 호출될 EndAction()함수들을 선언한다.
class GPC_CPP_12_API IActableOneInterface
{
GENERATED_BODY()
public:
virtual void BeginActionA() PURE_VIRTUAL(IActableOneInterface::BeginActionA)
virtual void BeginActionB() PURE_VIRTUAL(IActableOneInterface::BeginActionB)
virtual void BeginActionC() PURE_VIRTUAL(IActableOneInterface::BeginActionC)
virtual void EndActionA() PURE_VIRTUAL(IActableOneInterface::EndActionA)
virtual void EndActionB() PURE_VIRTUAL(IActableOneInterface::EndActionB)
virtual void EndActionC() PURE_VIRTUAL(IActableOneInterface::EndActionC)
};
GPCCharacter 클래스에 ActableOneInterface 클래스 상속
GPCCharacter 클래스에서는 앞서 만든 ActableOneInterface 클래스를 상속하게 된다.
.
.
.
#include "ActableOneInterface.h"
UCLASS(Abstract)
class GPC_CPP_12_API AGPCCharacter:
public ACharacter,
public IPosableOneInterface,
public IActableOneInterface
{
GENERATED_BODY()
.
.
.
#pragma region IActableOneInterface
private:
void BeginActionA() override;
void BeginActionB() override;
void BeginActionC() override;
void EndActionA() override;
void EndActionB() override;
void EndActionC() override;
#pragma endregion
};
ActableOneInterface 헤더를 포함해주고, : public IActableOneInterface로 해당 클래스에 상속시켜준다.
여기서 #pragma를 통해 ActableOneInterface에서 상속된 함수라는 것을 표시해주었다.
* 접근 지정자를 private : 로 지정하는 이유는, GPCCharacter의 타입으로 cast하여 사용을 막기 위함이다.
이어서 상속한 BeginAction()과 EndAction()함수들을 cpp에 정의를 만들어둔다.
GPCPlayerController 클래스에서 액션 매핑 키 바인드하기
GPCPlayerController 클래스에서는 키 입력 감지 시, 해당하는 함수를 호출할 수 있도록 해주기 위해, ActableOneInterface 헤더파일을 먼저 포함해주고, 클래스에 : public IActableOneInterface로 상속시켜준다.
.
.
.
#include "ActableOneInterface.h"
UCLASS()
class GPC_CPP_12_API AGPCPlayerController:
public APlayerController,
public IPosableOneInterface,
public IActableOneInterface
{
GENERATED_BODY()
protected:
void SetupInputComponent() override;
.
.
.
#pragma region IActableOneInterface
private:
void BeginActionA() override;
void BeginActionB() override;
void BeginActionC() override;
void EndActionA() override;
void EndActionB() override;
void EndActionC() override;
#pragma endregion
};
상속한 함수들을 오버라이드하여 선언해주는 부분은 앞서 상속한 클래스에서 구현한 방식과 같다.
이제 오버라이드한 함수들의 정의를 cpp에 만들어주고, 먼저 SetupInputComponent() 함수에 키 입력 감지 시, 해당 함수를
호출해줄 수 있도록 만들어주었다.
void AGPCPlayerController::SetupInputComponent()
{
Super::SetupInputComponent();
InputComponent->BindAction("ActA", EInputEvent::IE_Pressed, this, &ThisClass::BeginActionA);
InputComponent->BindAction("ActB", EInputEvent::IE_Pressed, this, &ThisClass::BeginActionB);
InputComponent->BindAction("ActC", EInputEvent::IE_Pressed, this, &ThisClass::BeginActionC);
InputComponent->BindAction("ActA", EInputEvent::IE_Released, this, &ThisClass::EndActionA);
InputComponent->BindAction("ActB", EInputEvent::IE_Released, this, &ThisClass::EndActionB);
InputComponent->BindAction("ActC", EInputEvent::IE_Released, this, &ThisClass::EndActionC);
}
ActA의 입력 감지 시, BeginActionA를 부르는 것과 마찬가지로 다른 함수들도 모두 입력이 감지되면 호출된다.
이어서 BeginAction()과 EndAction() 함수들의 정의부분을 작성해주었다.
저번에 정의했던 BeginPose() / EndPose() 함수의 정의와 비슷하게 정의할 수 있다.
void AGPCPlayerController::BeginActionA()
{
if (auto const ActableOne = Cast<IActableOneInterface>(GetPawn()))
ActableOne->BeginActionA();
}
void AGPCPlayerController::BeginActionB()
{
if (auto const ActableOne = Cast<IActableOneInterface>(GetPawn()))
ActableOne->BeginActionB();
}
void AGPCPlayerController::BeginActionC()
{
if (auto const ActableOne = Cast<IActableOneInterface>(GetPawn()))
ActableOne->BeginActionC();
}
void AGPCPlayerController::EndActionA()
{
if (auto const ActableOne = Cast<IActableOneInterface>(GetPawn()))
ActableOne->EndActionA();
}
void AGPCPlayerController::EndActionB()
{
if (auto const ActableOne = Cast<IActableOneInterface>(GetPawn()))
ActableOne->EndActionB();
}
void AGPCPlayerController::EndActionC()
{
if (auto const ActableOne = Cast<IActableOneInterface>(GetPawn()))
ActableOne->EndActionC();
}
받아온Pawn의 IActableOneInterface로 캐스트하여 ActableOne 변수에 담아준 뒤, 해당 함수에 맞는 ActableOne->함수를 호출해주도록 했다.
WeaponInterface 클래스 생성하기
언리얼 에디터로 돌아와, 새 C++클래스 추가 - Unreal Interface 부모 선택 - WeaponInterface 클래스를 생성해준다.
이는 ActableOneInterface와 다른 역할을 해줄 인터페이스이다.
* 마찬가지로 WeaponInterface 클래스의 cpp파일은 지워주었다.
UENUM()
enum class EWeaponType : uint8
{
Unknown,
Gun,
Max
};
UINTERFACE(MinimalAPI)
class UWeaponInterface : public UInterface
{
GENERATED_BODY()
};
class GPC_CPP_12_API IWeaponInterface
{
GENERATED_BODY()
public:
virtual EWeaponType GetWeaponType() const abstract;
//PURE_VIRTUAL(IWeaponInterface::GetWeaponType, return EWeaponType::Unknown;)
};
먼저 Weapon의 Type들을 만들어 WeaponInterface를 상속한 오브젝트들에게 맞는 WeaponType들을 넣어줄 것이다.
그래서 enum class EWeaponType으로 타입들을 만들어두었다.
* enum에 리플렉션 태그(UENUM())을 사용하기 위해선 : uint8이 있어야 한다.
이제 UWeaponInterface 클래스에 순수 가상 함수 virtual EWeaponType GetWeaponType()를 선언해준다.
그런데 여기서 그동안 쓰던 PURE_VIRTUAL()을 사용하지 않고 abstract가 사용되었다.
기본적으로 abstract를 사용하면 인스턴스 생성이 막히는 문제가 있다. 하지만 인터페이스로 인스턴스를 생성하진 않으므로 문제가 없다. 상속받은 대상에서 해당 함수에 대한 구현만 한다면 문제가 발생하지 않는다.
* PURE_VIRTUAL(함수, 가변인자구문)을 사용하고 싶다면 사용할 수 있다.
PURE_VIRTUAL(IWeaponInterface::GetWeaponType, return EWeaponType::Unknown;)
가변 인자 구문(__VA_ARGS_) : PURE_VIRTUAL로 지정되는 함수가 어떤 반환형을 가지고 있을지 모르기 때문에 있다.
따라서 모든 내용이 __VA_ARGS_와 대체된다.
GPCWeapon 클래스에서 WeaponInterface를 상속
#include "ActableOneInterface.h"
#include "WeaponInterface.h"
UCLASS(Abstract)
class GPC_CPP_12_API AGPCWeapon:
public AActor,
public IActableOneInterface,
public IWeaponInterface
{
GENERATED_BODY()
public:
AGPCWeapon(FObjectInitializer const& ObjectInitializer = FObjectInitializer::Get());
protected:
void BeginPlay() override;
private:
EWeaponType GetWeaponType() const override { return WeaponType; }
private:
UPROPERTY(VisibleAnywhere)
UMeshComponent* Mesh;
UPROPERTY(VisibleAnywhere)
EWeaponType WeaponType;
UPROPERTY(EditDefaultsOnly, Meta = (AllowPrivateAccess))
float Damage;
};
먼저 EWeaponType WeaponType; 변수를 하나 만들어준다.
그리고 WeaponInterface에서 선언한 함수 EWeaponType GetWeaponType()함수를 오버라이드해주었다.
또한 각 무기가 가지게될 Mesh와 Damage 변수를 선언해주었다.
Mesh에 Static이나 Skeleton을 정하여 변수를 선언하지 않은 이유는, 어떤 Mesh가 들어올지 확정되지 않았기 때문이다.
GPCWeapon.cpp에서 먼저 Tick()함수의 정의는 사용할 예정이 없으므로 지워버렸다.(크게 상관 없음)
AGPCWeapon::AGPCWeapon(FObjectInitializer const& ObjectInitializer):
Super(ObjectInitializer)
{
Mesh = CreateOptionalDefaultSubobject<USkeletalMeshComponent>("Mesh");
}
void AGPCWeapon::BeginPlay()
{
Super::BeginPlay();
}
보통 총기 Mesh 같은 경우 Skeleton Mesh인 경우가 많다. 총을 사격할 때 애니메이션이 필요하기 때문이다.
그래서 CreateOptionalDefaultSubobject<USkeletalMeshComponent>("Mesh")로 Mesh의 인스턴스를 생성해준다.
여기에 기존에 없던 Optional이 추가됬다. (선택적이란 의미)
ACharacter 정의 피킹 중에 오브젝트이니셜라이저(ObjectInitiablizer)가 있다.
ACharacter(const FObjectInitializer const& ObjectInitiablizer = FObjectInitializer ::Get());
이 기본 인수를 복사하여 AGPCWeapon() 생성자의 기본인수로 넣어주었다.
그리고 AGPCWeapon() 생성자 정의부분에도 추가해준다.
AGPCWeapon::AGPCWeapon(FObjectInitializer const& ObjectInitializer) : Super(ObjectInitializer)
일단 GPCWeapon 생성자가 호출되기 이전에 부모 생성자가 먼저 호출될 것이다.
그런데 여기서 원하는 부모의 생성자를 호출하고 싶다면?
멤버초기화목록을 통해서 원하는 부모의 생성자를 호출해줄 수 있다.( : Super(ObjectInitializer))
* 자세한 내용은 다음에...
'언리얼엔진' 카테고리의 다른 글
[C++] MuzzleFlash, Sound 활성화 / GPCBullet (0) | 2023.09.22 |
---|---|
[C++] GPCWeapon수정 / 무기 Mesh 추가 / Mesh 변환 / GPCGun 생성 (0) | 2023.09.21 |
[C++] 스테이트 머신(Standing - Crouching) / 에임 오프셋 / Camera Lag Enable (0) | 2023.09.19 |
[C++] 인터페이스 구현, 상속, 호출 (0) | 2023.09.18 |
[C++] GameModeBase 설정 / Stand, Crouch 애니메이션 / 인터페이스 생성 (0) | 2023.09.14 |