DirectX11/Eternal Return 모작

[DirectX 11 Eternal Return 모작] 9. 아이템 시스템 & 제작 시스템

Vfly 2025. 9. 3. 01:03

전체 코드 : 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


 

아이템 시스템과 동적 제작(Crafting) 시스템

핵심 구현 목표

  1. 아이템 등급별 분류와 관리
    • 5단계 등급 시스템 ( Common → Legendary )
    • 등급별 제작 시간 차등 적용
    • 타입별 아이템 분류 및 관리
  2. 동적 제작 가능성 검사 알고리즘
    • 실시간 재료 보유량 체크
    • 복합 레시피 체인 분석
    • 최적화된 제작 순서 결정

1. 시스템 아키텍쳐

1.1 핵심 설계 철학

  • 확장 가능한 설계 : 새로운 아이템 추가가 쉽게
  • UI와 로직의 분리
  • 실시간 상태 기반 : 게임플레이와 자연스러운 연동
// 아이템 시스템의 전체 구조
Item (기본 클래스)
├── EquipableItem (장비 아이템)
└── IngredientItem (재료 아이템)

ItemManager (아이템 관리)
├── 아이템 생성 및 관리
├── 리소스 로딩
└── 아이템 검색

Recipe System
├── Recipe (개별 레시피)
├── RecipeManager (레시피 관리)
└── 실시간 제작 가능 여부 검사

 

2. Strategy Pattern 기반 아이템 시스템

2.1 Strategy Pattern 이란?

전략 패턴은 객체들이 할 수 있는 행위 각각에 대해 전략 클래스를 생성하고, 유사한 행위들을 캡슐화하는 인터페이스를 정의.

 

class Item {
public:
    // 순수 가상 함수로 타입별 다른 동작 보장
    virtual bool Use() = 0;
    virtual bool CanUse() const = 0;
    virtual Item* Clone() const = 0;
    
protected:
    wstring m_name;
    ITEMTYPE m_itemType;
    ITEMGRADE m_itemGrade;
    int32 m_itemID;
};

class EquipableItem : public Item {
public:
    bool Use() override;
    const ItemStatus& GetStatus() const { return m_status; }
    void SetEquipType(EquipmentType _type) { m_equipType = _type; }
    
private:
    EquipmentType m_equipType;
    ItemStatus m_status;  // 공격력, 방어력, HP 등
};

class IngredientItem : public Item {
public:
    bool Use() override;  // 재료는 직접 사용 불가
    bool CanUse() const override { return false; }
};

 

특징

  • 각 아이템 타입마다 완전히 다른 동작을 하면서도 일관된 인터페이스를 제공한다.

3. Factory Pattern으로 아이템 생성 관리

아이템 생성 로직을 중앙화하여 확장성과 유지보수성을 향상

class ItemManager {
public:
    void CreatItems() {
        for (int i = 0; i < itemIconID.size(); i++) {
            if (itemType[i] == ITEMTYPE::INGREDIENTS) {
                shared_ptr<IngredientItem> ingredientItem = make_shared<IngredientItem>();
                ingredientItem->SetItemID(itemIconID[i]);
                ingredientItem->SetName(itemIconTag[i]);
                ingredientItem->SetItemType(itemType[i]);
                ingredientItem->SetItemGrade(itemGrade[i]);
                
                m_itemsContainerByName[itemIconTag[i]] = ingredientItem;
                m_itemsContainerByID[itemIconID[i]] = ingredientItem;
            }
            else if (itemType[i] == ITEMTYPE::EQUIPABLE) {
                shared_ptr<EquipableItem> equipableItem = make_shared<EquipableItem>();
                // 장비 전용 설정
                equipableItem->SetEquipType(equipableItemType[i]);
                equipableItem->SetStatus(equipableItemStatus[i]);
                
                m_itemsContainerByName[itemIconTag[i]] = equipableItem;
                m_itemsContainerByID[itemIconID[i]] = equipableItem;
            }
        }
    }
    
    // 이름과 ID 두 방식으로 검색 가능
    shared_ptr<Item> GetItem(const wstring& name);
    shared_ptr<Item> GetItem(int32 ID);
    
private:
    unordered_map<wstring, shared_ptr<Item>> m_itemsContainerByName;
    unordered_map<int32, shared_ptr<Item>> m_itemsContainerByID;
};

 

 

4. 아이템 등급별 분류 시스템

4.1 아이템 등급 정의

// 아이템 등급 시스템
enum class ITEMGRADE
{
    COMMON = 0,     // 일반 (흰색)
    UNCOMMON,       // 고급 (초록색)  
    RARE,          // 희귀 (파란색)
    EPIC,          // 영웅 (보라색)
    LEGENDARY,     // 전설 (주황색)
    UNIQUE         // 유니크 (빨간색)
};

// 아이템 타입 분류
enum class ITEMTYPE
{
    CONSUMABLE = 0, // 소모품
    EQUIPABLE,      // 장착 가능
    MATERIAL,       // 제작 재료
    ETC            // 기타
};

 

아이템 등급에 따라 배경 색이 다름

 

4.2 등급별 제작 시간 차등화

등급이 높을수록 제작 시간이 증가하여 게임 밸런스를 유지

// BiancaCraftState.cpp - 등급별 제작 시간 설정
void BiancaCraftState::SetCraftTimeByGrade(ITEMGRADE grade)
{
    m_craftingItemGrade = grade;
    switch (grade) {
        case ITEMGRADE::COMMON:    m_craftingTime = 1.0f; break;
        case ITEMGRADE::UNCOMMON:  m_craftingTime = 3.0f; break;
        case ITEMGRADE::RARE:      m_craftingTime = 5.0f; break;
        case ITEMGRADE::EPIC:      m_craftingTime = 7.0f; break;
        case ITEMGRADE::LEGENDARY: m_craftingTime = 9.0f; break;
        default:                   m_craftingTime = 11.0f; break;
    }
}

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

 

  • 제작하는 등급에 따라 제작 시간이 달라짐
  • Z키와 아이템을 누르면 제작 가능

 

5. 레시피 관리 시스템

5.1 레시피 구조 설계

// Recipe.h - 레시피 기본 구조
struct RecipeIngredient
{
    wstring itemID;
    int requiredCount;
    
    RecipeIngredient(const wstring& id, int count = 1) 
        : itemID(id), requiredCount(count) {}
};

class Recipe
{
private:
    wstring m_resultItemID;              // 결과 아이템
    vector<RecipeIngredient> m_ingredients; // 필요 재료들
    
public:
    bool CanCraft(const vector<shared_ptr<ItemSlot>>& slots) const;
    bool ExecuteCraftFromSlots(vector<shared_ptr<ItemSlot>>& slots);
};

 

5.2 복합 레시피 체인 구현

실제 게임에서 사용되는 비질란테(헬멧) 제작 체인:

// RecipeManager.cpp - 3단계 제작 체인 예시
void RecipeManager::RegisterBasicRecipes()
{
    auto itemManager = ItemManager::GetInstance();
    
    // 1단계: 안전모 = 자전거 헬멧 + 돌멩이
    RegisterRecipe(
        make_shared<Recipe>(
            itemManager->GetItem(L"안전모")->GetItemID(),
            RecipeIngredient(itemManager->GetItem(L"자전거 헬멧")->GetItemID()), 
            RecipeIngredient(itemManager->GetItem(L"돌멩이")->GetItemID())
        )
    );
    
    // 2단계: 소방 헬멧 = 안전모 + 흑연
    RegisterRecipe(
        make_shared<Recipe>(
            itemManager->GetItem(L"소방 헬멧")->GetItemID(),
            RecipeIngredient(itemManager->GetItem(L"안전모")->GetItemID()), 
            RecipeIngredient(itemManager->GetItem(L"흑연")->GetItemID())
        )
    );
    
    // 3단계: 비질란테 = 소방 헬멧 + 화약 (최종 EPIC 등급)
    RegisterRecipe(
        make_shared<Recipe>(
            itemManager->GetItem(L"비질란테")->GetItemID(),
            RecipeIngredient(itemManager->GetItem(L"소방 헬멧")->GetItemID()), 
            RecipeIngredient(itemManager->GetItem(L"화약")->GetItemID())
        )
    );
}

 

 

6. 동적 제작 가능성 검사 알고리즘

6.1 실시간 제작 가능성 검사

// InventoryManager.cpp - 핵심 알고리즘
vector<shared_ptr<Recipe>> InventoryManager::GetAvailableRecipes() const
{
    vector<shared_ptr<Recipe>> availableRecipes;
    auto allRecipes = RecipeManager::GetInstance()->GetAllRecipes();
    
    // 모든 레시피에 대해 제작 가능성 검사
    for (const auto& recipe : allRecipes)
    {
        if (recipe->CanCraft(m_inventorySlots))  // 동적 검사
        {
            availableRecipes.push_back(recipe);
        }
    }
    
    // 우선순위 정렬 (높은 등급부터)
    sort(availableRecipes.begin(), availableRecipes.end(), 
        [](const shared_ptr<Recipe>& a, const shared_ptr<Recipe>& b) {
            auto itemA = ItemManager::GetInstance()->GetItem(a->GetResultItemID());
            auto itemB = ItemManager::GetInstance()->GetItem(b->GetResultItemID());
            return static_cast<int>(itemA->GetItemGrade()) > 
                   static_cast<int>(itemB->GetItemGrade());
        });
        
    return availableRecipes;
}

 

 

6.2 재료 보유량 검사 로직

// Recipe.cpp - 세밀한 재료 체크
bool Recipe::CanCraft(const vector<shared_ptr<ItemSlot>>& slots) const
{
    // 각 필요 재료별로 검사
    for (const auto& ingredient : m_ingredients)
    {
        int foundCount = 0;
        
        // 인벤토리 전체 스캔
        for (const auto& slot : slots)
        {
            if (slot && !slot->IsEmpty())
            {
                auto item = slot->GetItem();
                if (item && item->GetItemID() == ingredient.itemID)
                {
                    foundCount += slot->GetCount();
                    
                    // 필요량 충족 시 조기 종료 (최적화)
                    if (foundCount >= ingredient.requiredCount)
                        break;
                }
            }
        }
        
        // 하나라도 부족하면 제작 불가
        if (foundCount < ingredient.requiredCount)
            return false;
    }
    
    return true; // 모든 재료 보유량 충족
}

 

 

 

7. 실시간 제작 시스템 통합

7.1 게임 루프와의 연동

// LumiaIsland.cpp - 제작 시스템 통합
void LumiaIsland::Start()
{
    // 제작 시도 이벤트 등록
    m_player->GetPlayerStateMachine()->OnTryCraft.Push([this](bool& success) {
        auto inventoryMgr = InventoryManager::GetInstance();
        auto recipes = inventoryMgr->GetAvailableRecipes();
        
        success = !recipes.empty();  // 제작 가능한 레시피 존재 여부
        
        if (success)
        {
            // 제작 준비 작업
            auto resultItem = ItemManager::GetInstance()->GetItem(recipes[0]->GetResultItemID());
            if (resultItem)
            {
                // 등급별 제작 시간 동적 적용
                ITEMGRADE itemGrade = resultItem->GetItemGrade();
                float craftTime = GetCraftTimeByGrade(itemGrade);
                
                // UI 업데이트
                m_uiManager->GetCraftGageUI()->SetVisible(true);
                m_uiManager->GetCraftGageUI()->SetItem(resultItem);
            }
        }
    });
}

 

7.2 제작 완료 상태 체크

// PlayerStateMachine.cpp - 상태 기반 제작 관리
void PlayerStateMachine::CheckCraftCompletion()
{
    if (IsInState(PlayerStateType::Craft))
    {
        bool craftCompleted = false;
        OnCraftCompleted(craftCompleted);  // Client에게 완료 여부 확인
        
        if (craftCompleted)
        {
            cout << "제작 완료 감지 - Wait 상태로 전환" << endl;
            
            RequestStateChange(PlayerStateType::Wait);
            
            if (m_animationStateMachine)
            {
                m_animationStateMachine->RequestStateChange(AnimationStateType::Wait);
            }
        }
    }
}

 

 

8. 성능 최적화 및 확장성

8.1 메모리 효율성

// ItemManager.cpp - 싱글톤 패턴으로 메모리 절약
class ItemManager
{
private:
    static ItemManager* m_instance;
    unordered_map<wstring, shared_ptr<Item>> m_items; // 아이템 풀링
    
    ItemManager() = default;
    
public:
    static ItemManager* GetInstance()
    {
        if (m_instance == nullptr)
            m_instance = new ItemManager();
        return m_instance;
    }
    
    shared_ptr<Item> GetItem(const wstring& itemID)
    {
        auto it = m_items.find(itemID);
        return (it != m_items.end()) ? it->second : nullptr;
    }
};

 

확장 가능한 레시피 시스템

 

현재 구현된 제작 체인들 

  • 비잘란테 ( 3단계 헬멧 )
  • 운명의 수레바퀴 ( 3단계 무기 )
  • 타키온 브레이스 ( 3단계 신발 )
  • 어사의 ( 3단계 의상 )

 

결론

시스템의 장점

  1. 동적 가능성 검사 : 실시간으로 제작 가능한 아이템만 표시
  2. 등급별 차등화 : 높은 등급일수록 더 오래 걸리는 자연스러운 밸런스
  3. 복합 체인 지원 : 3단계 이상의 복잡한 제작 과정 구현
  4. 확장성 : 새로운 레시피 추가가 간단함

향후 개선 방향

  1. 대량의 레시피 : 레시피가 지금보다 더 많아질 경우 XML 파일이나 JSON파일의 형태로 저장한 뒤 불러오기
  2. 아이템의 스택화 : 현재 소모형 아이템과 재료아이템들의 스택화는 미구현되어 있음
  3. 제작창 : 현재는 단순히 인벤토리 위에 제작 가능한 레시피를 보여주지만 특정 키를 누르면 제작 UI를 출력하는것도 고려해볼 수 있음