DirectX11/Eternal Return 모작

[DirectX 11 Eternal Return 모작] 15. HUD와 상태바

Vfly 2025. 9. 3. 04:37

전체 코드 : https://github.com/HyangRim/DirectX11-Engine-Client

 

GitHub - HyangRim/DirectX11-Engine-Client

Contribute to HyangRim/DirectX11-Engine-Client development by creating an account on GitHub.

github.com

 


실시간 HUD와 셰이더 기반 상태바 구현

1. 전체 UI 시스템 구조

1.1 UIManager - 중앙 집중식 UI 관리

UIManager는 모든 UI 패널을 중앙에서 관리하며, 플레이어와의 연결점 역할을 한다.

// UI 시스템의 최상위 관리자
class UIManager : public Component
{
private:
    shared_ptr<GameHUDPanelUI>        m_gameHUD;
    shared_ptr<SkillLevelUpPanelUI>   m_skillLevelUp;
    shared_ptr<PlayerStatusPanelUI>   m_playerStatus;
    shared_ptr<InventoryPanelUI>      m_inventory;
    shared_ptr<EquipmentPanelUI>      m_equipment;
    // ... 기타 UI 패널들
    
public:
    void InitializeUI();
    void Update();
};

// 모든 UI 패널의 기본 인터페이스
class IBasePanelUI
{
public:
    virtual void Initialize() = 0;
    virtual void Update() = 0;
    virtual void SetVisible(bool visible) = 0;
    virtual void Cleanup() = 0;

protected:
    virtual void CreatePanels() = 0;
    shared_ptr<GameObject> m_panel;
    bool m_isVisible = true;
};

 

1.2 싱글톤 패턴을 활용한 리소스 관리

// UI 리소스 전용 관리자
class UIResourceManager
{
    DECLARE_SINGLE(UIResourceManager);
    
private:
    unordered_map<wstring, shared_ptr<Material>> m_materials;
    unordered_map<wstring, shared_ptr<Texture>> m_textures;
    
public:
    // 리소스 로딩 및 설정
    shared_ptr<Material> LoadUIMaterial(const wstring& name, const wstring& texturePath);
    void SetupUIMaterial(shared_ptr<Material> material);
    
    // 특화된 UI 리소스 로딩
    void LoadSkillIcons();
    void LoadStatusBarResources();
    void LoadEquipmentResources();
};

 

 

2. 주요 UI 시스템

2.1 게임 HUD 시스템

class GameHUDPanelUI : public IBasePanelUI
{
public:
    void CreateSkillIcons();     // 스킬 아이콘 생성
    void CreateStatBars();       // HP/SP/EXP 바 생성
    void UpdateSkillCoolDown();  // 스킬 쿨다운 실시간 업데이트
    void UpdateSkillLevelBar(int skillIndex); // 스킬 레벨업 시 UI 반영
    
private:
    void CreateCharacterImage(); // 캐릭터별 초상화 표시
};

 

스탯바 업데이트

void GameHUDPanelUI::UpdateStatBar()
{
    if (!m_player) return;
    
    const auto& status = m_player->GetStatus();
    
    // HP 바 업데이트 - 쉐이더에 비율 전송
    float hpRatio = static_cast<float>(status.hp) / static_cast<float>(status.max_HP);
    
    if (m_hpBarImage)
    {
        auto material = m_hpBarImage->GetLayers()[0].material;
        if (material && material->GetShader())
        {
            material->GetShader()->PushHealthBarData(hpRatio, 0.0f, 0);
        }
    }
    
    // MP 바 업데이트
    float mpRatio = static_cast<float>(status.stamina) / static_cast<float>(status.max_Stamina);
    
    if (m_mpBarImage)
    {
        auto material = m_mpBarImage->GetLayers()[0].material;
        if (material && material->GetShader())
        {
            material->GetShader()->PushHealthBarData(0.0f, mpRatio, 0);
        }
    }
    
    UpdateStatText(); // 수치 텍스트 업데이트
}

 

 

위의 사진에서 빨간색 사각형 부분이 GameHUDPanelUI 다.

 

특징

  • 실시간 스킬 쿨다운 표시
  • 동적 HP/SP/EXP 바 업데이트
  • 캐릭터별 스킬 아이콘 동적 로딩
  • Character Select Scene에서 선택한 캐릭터의 초상화 출력
  • 캐릭터 레벨 표시

2.2  인벤토리 & 장비 시스템

class InventoryManager
{
    DECLARE_SINGLE(InventoryManager);
    
public:
    // 슬롯 간 아이템 이동
    bool MoveItem(int fromSlot, int toSlot, SLOTTYPE fromType, SLOTTYPE toType);
    
    // 장비 착용/해제
    bool EquipItem(int inventorySlotIndex);
    bool UnequipItem(int equipmentSlotIndex);
    
    // 이벤트 기반 UI 업데이트
    Delegate::Delegate<> OnInventoryChanged;
    
private:
    vector<shared_ptr<ItemSlot>> m_inventorySlots;
    vector<shared_ptr<ItemSlot>> m_equipmentSlots;
};

 

동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.

 

몬스터를 처치 한 후 아이템을 획득하면 인벤토리 창에 표시가 되고 인벤토리 창을 기준으로 조합 가능한 조합식이 상단에 표시된다.

 

특징

  • 델리게이트 패턴으로 UI 자동 갱신
  • 자동 장비 타입 검증 ( 무기는 무기 슬롯에만, 상의는 상의 슬롯에만 )
  • 클릭을 통합 장비 장착 및 해제

 

2.3 스탯 표시 시스템

class PlayerStatusPanelUI : public IBasePanelUI
{
private:
    // 스탯별 색상과 표시 방식 정의
    vector<StatTextConfig> statConfigs = {
        {0, 0, [&]() { return to_wstring((int)playerStatus.hitAttack); }, 
         ColorNormalize(Vec4(218, 187, 102, 255)), L"AttackPower"},
        // ... 각 스탯별 설정
    };
    
public:
    void UpdatePlayerStatus(); // 실시간 스탯 업데이트
};

 

위의 영상과 같이 장비 장착과 해제시 스탯이 반영됨.

 

 

3. 동적 UI 업데이트 시스템

3.1 이벤트 기반 UI 갱신

// 인벤토리 변화 시 자동 UI 갱신
inventoryManager->OnInventoryChanged.Push([this]() {
    if (m_isVisible) {
        UpdateCraftableItems();
    }
});

// 스킬 레벨업 시 UI 반영
skill->SkillLevelUp();
m_uiManager->GetGameHUD()->UpdateSkillLevelBar(skillIndex);

 

3.2 실시간 데이터 바인딩

void GameHUDPanelUI::UpdateStatBar()
{
    PlayerStatus& playerStatus = m_player->GetStatus();
    
    // HP 바 실시간 업데이트
    float ratio = ((float)playerStatus.hp / (float)playerStatus.max_HP);
    Vec2 newSize = Vec2(253.f * ratio, 10.f);
    hpImageUI->SetLayerSize(0, newSize);
}

 

4. 백그라운드 로딩 시스템

// 메인 스레드 블로킹 방지를 위한 백그라운드 UI 로딩
DWORD __stdcall LumiaIsland::BackgroundLoadingThread(LPVOID _param)
{
    // 백그라운드에서 리소스 로딩
    ItemManager::GetInstance()->Initialize();
    RecipeManager::GetInstance()->Initialize();
    scene->m_uiManager->InitializeUI();
    
    // 메인 스레드에서 실행할 작업 큐에 추가
    scene->m_mainThreadTasks.push([scene]() {
        scene->CreateItemBoxPanel();
        scene->m_objectsCreated = true;
    });
}

 

5. 경험치 바와 레벨업 시스템

5.1 Player클래스의 경험치 시스템

void Player::AddExp(int _exp)
{
    m_status.exp += _exp;
    
    // 레벨업 체크
    while (m_status.exp >= GetRequiredExp())
    {
        LevelUp();
    }
    
    // UI 업데이트 요청
    if (auto manager = m_uiManager.lock())
    {
        manager->GetGameHUD()->UpdateExpBar();
    }
}

int Player::GetRequiredExp() const
{
    // 레벨에 따른 필요 경험치 계산 (지수적 증가)
    return static_cast<int>(100 * pow(1.5f, m_status.level - 1));
}

void Player::LevelUp()
{
    m_status.level++;
    m_status.exp -= GetRequiredExp();
    
    // 레벨업 시 스탯 증가
    int hpIncrease = 20 + (m_status.level * 5);
    int staminaIncrease = 10 + (m_status.level * 3);
    
    m_status.max_HP += hpIncrease;
    m_status.max_Stamina += staminaIncrease;
    
    // 현재 HP/MP 회복
    m_status.hp = m_status.max_HP;
    m_status.stamina = m_status.max_Stamina;
    
    // 레벨업 효과 및 UI 업데이트
    PlayLevelUpEffect();
    
    cout << "레벨업! 새로운 레벨: " << m_status.level << endl;
}

 

5.2 경험치 바 시각화

void GameHUDPanelUI::UpdateExpBar()
{
    if (!m_player) return;
    
    const auto& status = m_player->GetStatus();
    
    // 현재 레벨에서의 경험치 진행률 계산
    int requiredExp = m_player->GetRequiredExp();
    int currentLevelExp = status.exp;
    
    float expRatio = static_cast<float>(currentLevelExp) / static_cast<float>(requiredExp);
    expRatio = max(0.0f, min(1.0f, expRatio)); // 0~1 범위로 제한
    
    // 경험치 바에 쉐이더 데이터 전송
    if (m_expBarImage)
    {
        auto material = m_expBarImage->GetLayers()[0].material;
        if (material && material->GetShader())
        {
            material->GetShader()->PushHealthBarData(expRatio, 0.0f, 2); // type=2는 경험치바
        }
    }
    
    // 경험치 텍스트 업데이트
    wstring expText = std::to_wstring(currentLevelExp) + L"/" + std::to_wstring(requiredExp);
    if (m_expText)
        m_expText->SetText(expText);
}

동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.

 

 

 

결론

  1. 모듈화된 UI 구조
    1. 각 UI 패널이 독립적으로 동작
    2. IBasePanelUI 인터페이스로 일관된 생명주기 관리
  2. 리소스 최적화
    1. UIResourceManager로 텍스쳐 / Material 중복 로딩 방지
    2. 백그라운드 로딩으로 초기화 시간 단축
  3. 이벤트 기반 설계
    1. Delegate 패턴으로 느슨한 결합
    2. 데이터 변경 시 관련 UI 자동 갱신 ( 변경 될때 갱신 함수 호출 )
  4. 확장 가능한 구조
    1. 새로운 UI 패널 추가 시 기존 코드 수정 최소화
    2. 캐릭터별 / 아이템별 동적 UI 생성 가능
    3.