액터 컴포넌트가 캐릭터에게 무언가를 시키도록 하고 싶다면? 또는 데이터를 전달하고 싶다면?
인터페이스를 사용해보자!
인터페이스를 사용하면 느슨한 결합(loose coupling)이 가능해진다.
컴포넌트가 구체적인 클래스 대신 인터페이스를 통해 상호작용함으로써, 어떤 클래스가 해당 인터페이스를 구현하고 있는지 알 필요 없이, 일관된 방식으로 기능을 호출할 수 있다.
또한 다형성을 지원하고, 코드의 재사용성과 유지보수성도 향상시킨다.
요구사항: SkillCastComponent (Actor Component) 가 캐릭터의 스킬을 재생시키도록 하고 싶다.
먼저 인터페이스를 만들어준다.
#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "Skill/GOSkillCastComponent.h"
#include "GOPlaySkillAnimInterface.generated.h"
...
class GUARDIANSORDERS_API IGOPlaySkillAnimInterface
{
GENERATED_BODY()
// Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:
virtual UGOSkillCastComponent* GetSkillCastComponent() = 0;
virtual void PlaySkillAnim() = 0;
};
캐릭터 코드에서 인터페이스를 구현하도록 한다.
#include "Interface/GOPlaySkillAnimInterface.h"
...
UCLASS()
class GUARDIANSORDERS_API AGOCharacterBase : public ACharacter, public IGOAnimationAttackInterface, public IGOCharacterWidgetInterface, public IGOPlaySkillAnimInterface
{
GENERATED_BODY()
...
// ======== IPlaySkillAnimInterface ========
virtual UGOSkillCastComponent* GetSkillCastComponent()
{
return SkillCastComponent;
}
virtual void PlaySkillAnim()
{
UE_LOG(LogTemp, Warning, TEXT("[ACharacterBase::PlaySkillAnim] called. This function is inherited from GOPlaySkillAnimInterface. "));
}
...
}
이제 컴포넌트의 코드이다.
void UGOSkillCastComponent::OnUpdateCast(float DeltaTime)
{
if (CurrentSkill == nullptr || !bIsOnCasting)
{
return;
}
// 스킬 캐스팅 중 업데이트 로직
CurrentSkill->UpdateCast(DeltaTime);
if (AActor* Owner = GetOwner())
{
if (IGOPlaySkillAnimInterface* GOPlaySkillAnimInterface = Cast<IGOPlaySkillAnimInterface>(Owner))
{
GOPlaySkillAnimInterface->PlaySkillAnim();
}
}
if (DeltaTime > CurrentSkill->GetCastingTime())
{
OnFinishCast();
}
...
}
출력 결과는 잘 나온다.
[ACharacterBase::PlaySkillAnim] called. This function is inherited from GOPlaySkillAnimInterface.
그러니까 UGOSkillCastComponent는 IGOPlaySkillAnimInterface의 헤더를 UGOSkillCastComponent.h 또는 UGOSkillCastComponent.cpp 어디에서도 포함하고 있지 않아도 정상 작동한다.
왜일까?
Unreal Engine에서는 자체 빌드 시스템인 Unreal Build Tool (UBT)을 사용하고, UBT는 헤더 파일의 종속성을 관리하는 과정에서 약간의 마법을 부린다 (?)
액터 컴포넌트는 액터한테만 붙어야 하는 녀석이고
캐릭터 컴포넌트는 캐릭터한테만 붙어야 하는 녀석이고
탱커 컴포넌트는 탱커한테만 붙어야 하는 녀석이다
이렇게 인터페이스로 구현하는 구조가 마치 ASC랑 비슷한 구조와 비슷하다는 생각이 들었다. 헤더 의존성을 줄여주고, 태스크마다 만들어주는 면에서 든 생각이다.
그렇다면 보통 이렇게 뭔가 할 것이 있을 때마다
인터페이스를 사용하고 델리게이트를 사용하나? -> 맞다!