본문 바로가기

언리얼엔진

[C++] 인터페이스 구현, 상속, 호출

변경 사항

* 저번에 만든 ControlledPawnInterface 클래스 명을 PosableOneInterface로 바꿔주었다.

//헤더파일 명 변경
#include "PosableOneInterface.generated.h"
//첫번째 클래스 명 변경(U~)
class UPosableOneInterface : public UInterface
//두번째 클래스 명 변경(I~)
class GPC_CPP_12_API IPosableOneInterface

 

액션 매핑에 추가했던 Crouch 삭제 후, Pose A / Pose B 두 액션 매핑을 새로 추가했다.

프로젝트 세팅 - 입력 - 액션 패밍


C++ 인터페이스 구현하기

지금까지 블루프린트로 인터페이스 함수를 만들때 기본 구현을 미리 만들어둘 수 없었다.

하지만 C++ 인터페이스에서는 가능하다. 그래서 인터페이스 클래스 생성 시, cpp파일도 생성되는 것이다.

만약 블루프린트와 마찬가지로 기본 구현을 미리 만들지 않을 예정이라면 cpp파일을 지워버려도 된다.

* 순수 가상이 아닌 함수를 선언 시, 정의가 필요하다.

 

class GPC_CPP_12_API IPosableOneInterface
{
	GENERATED_BODY()

	// Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:
	virtual void BeginPoseA() PURE_VIRTUAL(IPosableOneInterface::BeginPoseA)
	virtual void BeginPoseB() PURE_VIRTUAL(IPosableOneInterface::BeginPoseB)
	virtual void EndPoseA() PURE_VIRTUAL(IPosableOneInterface::EndPoseA)
	virtual void EndPoseB() PURE_VIRTUAL(IPosableOneInterface::EndPoseB)
};

순수 가상 함수는 뒤에 = 0; 을 붙이거나, abstract를 사용한다.

인터페이스에서는 변수를 만들지 않고, 엄격하게 보면 순수 가상 함수만 가지도록 해야한다.

 

하지만 언리얼에서 순수가상함수는 PURE_VIRTUAL(클래스명::함수이름)을 함수 뒤에 바로 붙여주면 만들어줄 수 있다.

PURE_VIRTUAL()을 사용하면 인스턴스를 생성하게 된다. 하지만 정의 부분이 구현된 상태가 아니므로 순수가상함수를 호출 시 오류가 발생한다.


다른 클래스에 인터페이스 적용하기

이제 GPCCharacter 클래스에 PosableOneInterface 인터페이스를 적용시켜 사용하기 위해 GPCCharacter의 헤더로 온다.

//GPCCharacter 클래스에 인터페이스 포함
#include "PosableOneInterface.h"

UCLASS(Abstract)
class GPC_CPP_12_API AGPCCharacter:
	public ACharacter,
	public IPosableOneInterface
    //PosableOneInterface 인터페이스 상속
{
	
};

이제 GPCCharacter 클래스에서 PosableOneInterface 인터페이스를 상속하게 된다.

그런데 이로 인해 다중 상속의 문제가 발생하게 된다.

 

* 다중 상속의 문제(Diamond Problem)을 해결하기

1. 간단하게 다중 상속을 하지 않는 것이다.

2. 가상 상속을 활용한다.

 

하지만 이 부분의 다중 상속은 큰 문제 없이 허용된다.(특별히 같은 이름의 함수가 두 개나 있는 것이 아니라면 괜찮다.)

그리고 우리가 GPCCharacter 클래스로 인스턴스를 생성할 것이 아니기 때문에 AGPCCharacter 클래스에

UCLASS(Abstract)를 걸어두었다.


인터페이스 함수 정의

AGPCCharacter 클래스에 인터페이스 함수들을 오버라이드 해준다.

* 다중 상속중인 클래스이므로 각각의 함수가 어느것인지 표시해주기 위해 주석을 사용하거나

   #pragma region ~ #pragma endregion으로 표시해줬다.

#pragma region IPosableOneInterface
private:
	void BeginPoseA() override;
	void BeginPoseB() override;
	void EndPoseA()	override;
	void EndPoseB()	override;
#pragma endregion

이제 함수들을 정의해주기 전에, 호출하는 부분 먼저 만들어준다.

때문에 AGPCPlayerController 클래스에서 PosableOneInterface 클래스 헤더를 포함해주고, 상속도 추가해준다.

class GPC_CPP_12_API AGPCPlayerController:
	public APlayerController,
	public IPosableOneInterface
{
	GENERATED_BODY()
	
protected:
	void SetupInputComponent() override;
.
.
.

#pragma region IPosableOneInterface
private:
	void BeginPoseA() override;
	void BeginPoseB() override;
	void EndPoseA()	override;
	void EndPoseB()	override;
#pragma endregion
};

마찬가지로 이곳에도 인터페이스 함수들을 오버라이드하여 추가해준다.

이제 GPCPlayerController 클래스의 cpp로 가서 정의해주었다.

 

void AGPCPlayerController::BeginPoseA()
{
	if (auto const PosableOne = Cast<IPosableOneInterface>(GetPawn()))
		PosableOne->BeginPoseA();
}

void AGPCPlayerController::BeginPoseB()
{
	if (auto const PosableOne = Cast<IPosableOneInterface>(GetPawn()))
		PosableOne->BeginPoseB();
}

void AGPCPlayerController::EndPoseA()
{
	if (auto const PosableOne = Cast<IPosableOneInterface>(GetPawn()))
		PosableOne->EndPoseA();
}

void AGPCPlayerController::EndPoseB()
{
	if (auto const PosableOne = Cast<IPosableOneInterface>(GetPawn()))
		PosableOne->EndPoseB();
}

이어서 GPCPlayerController 클래스의 SetupInputComponent 함수에서 해당 액션입력 시, 해당하는 함수를 호출한다.

void AGPCPlayerController::SetupInputComponent()
{
    Super::SetupInputComponent();

	InputComponent->BindAxis("MoveFB", this, &ThisClass::MoveFB);
	InputComponent->BindAxis("MoveLR", this, &ThisClass::MoveLR);

	InputComponent->BindAxis("LookLR", this, &ThisClass::LookLR);
	InputComponent->BindAxis("LookUD", this, &ThisClass::LookUD);

//여기서 입력 시 해당하는 함수를 부르게 된다.
	InputComponent->BindAction("PoseA", EInputEvent::IE_Pressed, this, &ThisClass::BeginPoseA);
	InputComponent->BindAction("PoseB", EInputEvent::IE_Pressed, this, &ThisClass::BeginPoseB);
	InputComponent->BindAction("PoseA", EInputEvent::IE_Released, this, &ThisClass::EndPoseA);
	InputComponent->BindAction("PoseB", EInputEvent::IE_Released, this, &ThisClass::EndPoseB);
}

이렇게 호출 부분을 작성했다면, GPCCharacter 클래스에서 정의했던 인터페이스 함수에 Logger::Print("함수이름");을

추가하여 빌드하고, 제대로 함수가 호출되는지 확인해볼 수 있다.