-
[Unreal] 언리얼 인터페이스 이야기 - Unreal C++ Interface쾌락없는 책임 (공부)/Unreal 2022. 11. 28. 21:28반응형
개요
일단 기존의 C++에서는 '인터페이스'와 관련한 이야기가 없습니다. 이 글을 쓰는 시점에서는 __interface라는 키워드가 있지만 이는 vs의. Net 플랫폼에서만 되는 것으로 알고 있어 현재 표준은 없는 걸로 판단이 됩니다.
하지만 다양한 디자인 패턴들을 적용해 유지보수를 하기 위해서는 이런 '인터페이스'와 관련한 개념이 꼭 필요하게 되며 이로 인해서 언리얼에서는 이를 위한 UInterface를 제공해주게 됩니다. 이번에는 언리얼을 공부하면서 간단하게 알아낸 이 인터페이스와 사용 방법에 대해서 알아보겠습니다.
Unreal Interface 생성법
일단 Blueprint와 C++ 클래스를 만드는 2가지 방법이 있지만 이번 글은 C++을 기준으로 설명하겠습니다. C++에서 생성하면 일단 C++, 블루프린트 전부 사용이 가능하며 블루프린트 생성 인터페이스는 C++에서 볼 수 없다는 사실을 알면 될 것 같습니다.
일단 기존의 Actor 관련 클래스들 생성하듯 C++ 클래스 생성 메뉴를 보면 맨 아래 Unreal Interface 항목을 볼 수 있습니다. 이를 통해서 간단하게 생성을 할 수 있게 됩니다.
생성 시 주의점
- 언리얼의 Actor는 A 접두사가 붙는 것처럼 인터페이스의 경우에는 I가 붙게 됩니다. 때문에 클래스 이름을 지을 때 흔히 '인터페이스니깐 클래스 이름 앞에 I를 붙여야지!'같은 건 하지 않는 게 좋습니다. 그러면 IIClassname 같은 괴랄한 이름이 나오게 되거든요.C++에서 Interface class
// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "UObject/Interface.h" #include "PoolableObject.generated.h" UINTERFACE(MinimalAPI) class UPoolableObject : public UInterface { GENERATED_BODY() }; /** * */ class PROJECTFA_API IPoolableObject { GENERATED_BODY() public: virtual void SetObjectActive(const FVector Poistion) = 0; virtual void SetObjectDeactive() = 0; };
일단 저의 경우에는 풀링을 위한 오브젝트의 인터페이스를 만들었습니다. 보시면 알겠지만 위 UInterface를 상속받는 곳은 GENERATED_BODY()만이 있고 다른 추가적인 코드는 넣지 않습니다.
실제 인터페이스를 위한 함수들은 아래에 선언할 수 있으며 이곳에서 virtual ~~~ = 0;으로 추상 메서드를 선언해주면 인터페이스로 사용할 수 있게 됩니다. 그리고 추상 메서드로 선언한 함수의 경우에는 cpp에서 구현을 하지 않아도 괜찮으므로 cpp를 건들 일이 없다는 장점이 있습니다.
추가적으로 순수 가상 함수가 아닌 함수도 선언이 가능하며 이 경우에는 cpp에서 구현부에 대한 정의를 남겨줘야 합니다.
C++에서 인터페이스 상속
C++에서는 다중 상속을 기본적으로 지원하기에(타 언어들은 대부분 인터페이스만 다중 허용) 문제없이 상송을 할 수 있습니다. 아래 코드처럼 ACharacter 파생 클래스에서 IPoolableObject 인터페이스를 상속받을 수 있는 것이죠.
// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "GameFramework/Character.h" #include "ProjectFA/GameSystem/ObjectPooling/PoolableObject.h" #include "ZombieBase.generated.h" UCLASS() class PROJECTFA_API AZombieBase : public ACharacter, public IPoolableObject { GENERATED_BODY() //...인터페이스의 함수만 봅시다 public: /** From IPoolableObject */ virtual void SetObjectActive(const FVector Poistion) override; virtual void SetObjectDeactive() override; //... };
상속은 기존 ACharacter를 받는 것처럼 IPoolableObject를 상속받으면 됩니다. 또한 상속받은 클래스들은 순수 가상 함수를 둘 수 없으니(사실상 대부분 Actor가 받으므로) cpp에서 구현을 추가적으로 해줘야 합니다.
void AZombieBase::SetObjectActive(const FVector Poistion) { AttackRange->SetActive(true); AttackCollision->SetActive(true); SetActorLocation(Poistion); SetZombieState(MovingState); } void AZombieBase::SetObjectDeactive() { AttackRange->SetActive(false); AttackCollision->SetActive(false); }
C++에서 인터페이스 변수 가져보기 - interface as variable
이제 이런 인터페이스를 구현한 객체들을 다른 클래스에 관리하기 위해서 변수로 저장할 필요가 있습니다. 일단 이 글을 쓰는 시점에서 제가 사용하는 방법을 말씀드리겠습니다.
TScriptInterface<class IWeapon> CurWeapon; TScriptInterface<IWeapon> NullWeapon; TScriptInterface<IWeapon> SwordWeapon; TScriptInterface<IWeapon> BowWeapon;
현재 플레이어의 무기를 위해 스트래터지 패턴을 만들 예정에 있었고 이를 위해 변수들을 저장한 모습입니다. TObjectPtr처럼 키워드가 하나 제공되는데 바로 TScriptInterface 입니다. 언리얼에서 공식으로 제공하는 키워드로 이곳에 객체 포인터를 넣어 생성할 수 있습니다.
// 생성자에서의 모습 NullWeapon(CreateDefaultSubobject<UNullWeapon>(TEXT("NullWeapon"))), SwordWeapon(CreateDefaultSubobject<USwordWeapon>(TEXT("SwordWeapon"))), BowWeapon(CreateDefaultSubobject<UBowWeapon>(TEXT("BowWeapon")))
변수로 가졌으면 기존 포인터 사용하듯 ->, ==, = 등의 연산자를 사용할 수 있습니다.
void AProjectFACharacter::SetWeapon(TScriptInterface<IWeapon> Weapon) { if(Weapon == nullptr || Weapon == CurWeapon) return; if(CurWeapon != nullptr) { CurWeapon->ClearPlayerAttackProperty(this); } Weapon->SetPlayerAttackProperty(this); CurWeapon = Weapon; }
엑터가 인터페이스를 구현하고 있는지 보기 위해서는?
일단 포인터들이 nullptr이 아님을 보는 것처럼 엑터를 얻었을 때 '이 엑터가 인터페이스를 구현하고 있는가?'를 알아봐야 합니다.
if(SomeActor->GetClass()->ImplementsInterface(UInterfaceClassName::StaticClass())) { // for C++ implementation auto InterfaceVariable = Cast<IInterfaceClassname>(SomeActor); InterfaceVariable->Function();... // for Blueprint implementation IInterfaceClassName::Execute_Function(SomeActor); }
UClass::ImplementsInterface
This will return whether or not this class implements the passed in class / interface
docs.unrealengine.com
이런 식의 코드를 작성하면 되게 됩니다.
그런데 직접 겪은 이야기는 아니지만 오브젝트의 인터페이스 구현을 블루프린트에서만 한다면 Cast로 한 경우 무조건적으로 nullptr을 반환하게 된다고 합니다. 이 경우에는 여러 다른 방법을 사용해야 한다고 하는데 직접 한건 아닌데 아래 한번 이야기를 해 보겠습니다.
블루프린트로 구현한 오브젝트를 Cast?
Cast<> will always return nullptr if the instance in question is only implementing the interface at the Blueprint level, and none of its superclasses implement it in C++.
- stevestreeting.com인터페이스를 공부하기 위해서 가장 잘 참조한 블로그의 이야기입니다. 위 이야기를 번역하면 블루프린트로만 구현한 오브젝트를 인터페이스 변수로 Cast 하면 항상 nullptr이 나온다는 오류입니다.
현재 제 프로젝트에서는 블루프린트를 통한 인터페이스 활용을 하고 있지 않기에 이러한 오류를 체감할 수 없었지만 위 블로그에서 문제와 함께 간단한 해결책을 제시해 줬기에 함께 적어보겠습니다.
UPROPERTY(BlueprintReadWrite) UObject* SomeObject; if (SomeObject) { int Num = IDoSomeThings::Execute_FunctionName(SomeObject); }
해결책으로는 일반적인 UObject 포인터를 가지고 Execute를 호출하는 것을 제시했습니다. 위 필자는 이런 문제들 때문에 TScriptInterface를 사용하지 않고 일반 UObject포인터를 사용한다고 합니다.
하지만 저 같은 경우에는 현재로는 C++로만 구성을 하고 있기 때문에 TObjectPtr<UObject> 같은 식으로 사용하지 않고 TScriptInterface를 사용할 예정입니다. 이와 관련한 문제들이 나오면 또 이야기해봐야겠네요.
참고자료
- 번역기로도 번역이 잘 되므로 아주 강추하는 곳입니다.
UE4 C++ Interfaces - Hints n Tips · SteveStreeting.com
Tutorial on how to use interfaces defined in C++ in UE4, with some useful practical tips
www.stevestreeting.com
인터페이스
인터페이스 생성 및 구현 관련 레퍼런스입니다.
docs.unrealengine.com
TScriptInterface
Templated version of [FScriptInterface](API\Runtime\CoreUObject\UObject\FScriptInterface), which provides accessors and operators for referencing the interface portion of a [UObject](API\Runtime\CoreUObject\UObject\UObject) that implements a native interfa
docs.unrealengine.com
반응형'쾌락없는 책임 (공부) > Unreal' 카테고리의 다른 글
[Unreal] Additive Animation - 애디티브 애니메이션 (1) 2023.03.13 [Unreal] 언리얼 블루프린트 vs C++, 왜 프로그래머는 C++을 써야 하는가? (0) 2023.03.10 [Unreal] 언리얼의 GC에 대해서 알아보자. 아주 간략히 (0) 2022.11.07 [Unreal] 언리얼 Anim Montage가 재생되지 않을 때 - Unreal Anim Montage not playing (0) 2022.11.05 [Unreal] GarbageCollection.cpp 에러, Invalid object in GC 에러 (0) 2022.11.05