UE5

[UE5] 인터페이스의 활용

검정색필통 2024. 12. 5. 06:02

이득우의 언리얼 프로그래밍 pt2에서는 세 개의 레이어를 기준으로 게임의 다양한 기능을 구현한다.

게임 구성 요소의 분류

 

하위 레이어에서 상위 레이어를 참조할 때는 인터페이스를 활용하여 구현한다.

 

인터페이스를 활용할 때의 장점

 

1. 의존성 문제 해결

  • 하위 레이어가 상위 레이어에 강하게 결합하면, 의존성 문제가 발생할 수 있음
class UABCharacterHUD : public UUserWidget
{
public:
    void UpdateHPBar(AABCharacterBase* Character)
    {
        float CurrentHP = Character->GetCurrentHP();
        float MaxHP = Character->GetMaxHP();
        HPBar->SetPercent(CurrentHP / MaxHP);
    }
private:
    UProgressBar* HPBar; // UI의 HP 바 위젯
};
  • 만약 캐릭터 클래스가 AABCharacterBase가 아닌 ANewCharacterBase로 변경되면
    • UI레이어(UABCharacterHUD) 코드도 수정해야함
    • 유지보수성이 떨어지고, 강한 결합이 발생
  • 다른 캐릭터 타입이 추가되면 UI 레이어가 이를 모두 인식하고 처리해야함
    • 새로운 캐릭터마다 UpdateHPBar를 수정하거나 if/else 체인을 만들어야함

 

해결방법

class IABCharacterWidgetInterface
{
public:
    virtual float GetCurrentHP() const = 0;
    virtual float GetMaxHP() const = 0;
};
  • AABCharacterBase와 ANewCharacterBase 같은 캐릭터 클래스에서 IABCharacterWidget 인터페이스를 상속받아 구현
class AABCharacterBase : public ACharacter, public IABCharacterWidgetInterface
{
public:
    virtual float GetCurrentHP() const override { return CurrentHP; }
    virtual float GetMaxHP() const override { return MaxHP; }
private:
    float CurrentHP;
    float MaxHP;
};

class ANewCharacterBase : public ACharacter, public IABCharacterWidgetInterface
{
public:
    virtual float GetCurrentHP() const override { return NewCurrentHP; }
    virtual float GetMaxHP() const override { return NewMaxHP; }
private:
    float NewCurrentHP;
    float NewMaxHP;
};

 

  • UI 레이어에서는 캐릭터의 구체적인 구현을 알 필요없이 인터페이스만 참조하면 됨
class UABCharacterHUD : public UUserWidget
{
public:
    void UpdateHPBar(IABCharacterWidgetInterface* Character)
    {
        float CurrentHP = Character->GetCurrentHP();
        float MaxHP = Character->GetMaxHP();
        HPBar->SetPercent(CurrentHP / MaxHP);
    }
 private:
     UProgressBar* HPBar; // UI의 HP 바 위젯
 };
  • 실제 사용
IABCharacterWidgetInterface* Character1 = new AABCharacterBase();
IABCharacterWidgetInterface* Character2 = new ANewCharacterBase();

HpBarWidget->UpdateHpBar(Character1);
HpBarWidget->UpdateHpBar(Character2);

 

2. 캐릭터의 역할 분리와 책임 모듈화

  • 캐릭터가 여러 인터페이스를 상속받음으로써, 캐릭터의 동작을 역할 단위로 분리할 수 있음
class IABAnimationAttackInterface
{
    virtual void PerformAttackAnimation() = 0;
};

class IABCharacterWidgetInterface
{
    virtual void UpdateCharacterHPBar(float CurrentHP, float MaxHP) = 0;
};

class IABCharacterItemInterface
{
    virtual void EquipItem(UItem* Item) = 0;
};

 

이런 설계를 통해 책임의 분리모듈화가 가능해짐

 

3. 다형성을 활용한 유연성

void TriggerAttackAnimation(IABAnimationAttackInterface* Character)
{
    Character->PerformAttackAnimation();
}
  • 상위 레이어인 AABCharacterBase는 구체적으로 어떤 캐릭터인지 알 필요없음
  • IABAnimationAttackInterface를 구현한 캐릭터(상속받은 캐릭터)라면 누구나 호출가능
  • 이를 통해 새로운 캐릭터 클래스 추가 시 기존 로직 수정 없이 인터페이스만 구현하면 됨
  • 예시) ABossCharacter는 방어막과 HP를 합산한 값을 반환하도록 구현
class ABossCharacter : public ACharacter, public IABCharacterWidgetInterface
{
public:
    virtual float GetCurrentHP() const override
    {
        return Shield > 0.0f ? Shield + CurrentHP : CurrentHP;
    }

    virtual float GetMaxHP() const override
    {
        return MaxHP + MaxShield;
    }

private:
    float CurrentHP = 200.0f;    // 기본 HP
    float MaxHP = 200.0f;        // 최대 HP
    float Shield = 50.0f;        // 현재 방어막
    float MaxShield = 50.0f;     // 최대 방어막
};
  • 이와 같이 UI 로직 수정없이 새로운 캐릭터 클래스가 동작

4. 다중 상속의 강점

 

  • C++에서 인터페이스는 다중 상속이 가능하며, 캐릭터가 다양한 인터페이스를 상속받아 각각의 책임을 구현
class AABCharacterBase : public ACharacter, public IABAnimationAttackInterface, public IABCharacterWidgetInterface, public IABCharacterItemInterface
{
public:
    virtual void PerformAttackAnimation() override { /* 공격 애니메이션 로직 */ }
    virtual void UpdateCharacterHPBar(float CurrentHP, float MaxHP) override { /* UI 업데이트 로직 */ }
    virtual void EquipItem(UItem* Item) override { /* 아이템 장착 로직 */ }
};

 

 

결론

  • 의존성 역전 원칙(DIP):
    • 하위 레이어(UI, Item 등)는 상위 레이어(Character)를 구체적으로 참조하지 않고, 인터페이스만 참조.
  • 모듈화:
    • 캐릭터의 복잡한 동작을 인터페이스로 분리하여 역할과 책임을 명확히 구분.
  • 유연성:
    • 다양한 타입의 캐릭터를 인터페이스를 통해 동작하게 할 수 있음(다형성).
  • 확장성:
    • 새로운 캐릭터나 시스템을 추가할 때, 기존 코드를 수정하지 않고 새로운 인터페이스를 구현하면 됨.