ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [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)를 구체적으로 참조하지 않고, 인터페이스만 참조.
    • 모듈화:
      • 캐릭터의 복잡한 동작을 인터페이스로 분리하여 역할과 책임을 명확히 구분.
    • 유연성:
      • 다양한 타입의 캐릭터를 인터페이스를 통해 동작하게 할 수 있음(다형성).
    • 확장성:
      • 새로운 캐릭터나 시스템을 추가할 때, 기존 코드를 수정하지 않고 새로운 인터페이스를 구현하면 됨.

     

     

Designed by Tistory.