ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [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

     

    반응형

    댓글

Designed by Tistory.