[인벤토리 시스템] 아이템 슬롯 컨텍스트

Jongmin Kim·2025년 9월 15일

Lapi

목록 보기
14/14

시작

이번 글에서는 아이템 슬롯을 범용적으로 사용할 수 있도록 각 상황에 알맞는 슬롯을 돌려주거나 설정해주는 컨텍스트를 구현해볼 것이다.

글의 마지막에는 이 슬롯 컨텍스트가 라피에서는 어떻게 완성되었는지 전체 코드를 보이도록 하겠다. 시작해보록 하겠다.



아이디어

처음부터 아이템 슬롯에 컨텍스트를 도입한 것은 아니었다. 라피에서 기능을 확장하면 확장할수록 아이템 슬롯의 Presenter 코드가 너무 비대해지는 것을 느꼈다.

여기서 구조적인 문제가 있다고 생각한 나는 Presenter의 책임을 줄이기 위해서 컨텍스트를 도입하기로 결정했다.

컨텍스트는 SlotType을 받아서 단순히 해당하는 컨텍스트의 슬롯을 반환하거나 해당 컨텍스트에서의 연산을 지원한다.



ItemSlotContext

우선 아이템 슬롯 컨텍스트를 구현하기 전에 확장성을 위해서 인터페이스를 작성했다.

using System;
using InventoryService;

public interface IItemSlotContext
{
	// slot_type과 offset을 이용하여 특정 슬롯에 update_action 이벤트를 등록한다.
    void Register(SlotType slot_type, 
    			  Action<int, ItemData> update_action, 
                  int offset = 0, 
                  int count = 0);
    
    // slot_type을 이용하여 update_action 이벤트를 해제한다.
    void Discard(SlotType slot_type, 
    			 Action<int, ItemData> update_action);
                 
    // slot_type과 offset을 이용하여 슬롯의 ItemData를 반환한다.
    ItemData Get(SlotType slot_type, 
    			 int offset, 
                 int count = 1);
                 
    // slot_type과 offset을 이용하여 슬롯의 ItemData를 설정한다.
    void Set(SlotType slot_type, 
    		 int offset, 
             ItemCode code, 
             int count);
             
    // slot_type과 offset을 이용하여 슬롯의 아이템 개수를 변경한다.
    void Update(SlotType slot_type, 
    			int offset, 
                int count);
                
    // slot_type과 offset을 이용하여 슬롯을 비운다.
    void Clear(SlotType slot_type, 
    		   int offset);
}

그리고 이 인터페이스에 맞춰서 구체화를 시킨다.

using System;
using EquipmentService;
using InventoryService;
using ShortcutService;
using SkillService;

public class ItemSlotContext : IItemSlotContext
{
    private readonly IItemDataBase m_item_db;

    private readonly IInventoryService m_inventory_service;

    public ItemSlotContext(IItemDataBase item_db)
    {
        m_item_db = item_db;
        m_inventory_service = inventory_service;
    }

    public void Register(SlotType slot_type, Action<int, ItemData> update_action, int offset = 0, int count = 0)
    {
        switch (slot_type)
        {
            case SlotType.Inventory:
                m_inventory_service.OnUpdatedSlot += update_action;
                break;
        }       
    }             
    
    public void Discard(SlotType slot_type, Action<int, ItemData> update_action)
    {
        switch (slot_type)
        {
            case SlotType.Inventory:
                m_inventory_service.OnUpdatedSlot -= update_action;
                break;
        }        
    }    

    public ItemData Get(SlotType slot_type, int offset, int count = 1)
    {
        return slot_type switch
        {
            SlotType.Inventory              => m_inventory_service.GetItem(offset),
            _                               => null,
        };
    }

    public void Set(SlotType slot_type, int offset, ItemCode code, int count = 1)
    {
        var action = slot_type switch
        {
            SlotType.Inventory              => () => m_inventory_service.SetItem(offset, code, count),
            _                               => (Action)(() => {})
        };

        action();
    }

    public void Update(SlotType slot_type, int offset, int count)
    {
        var action = slot_type switch
        {
            SlotType.Inventory              => () => m_inventory_service.UpdateItem(offset, count),
            _                               => (Action)(() => {})
        };

        action();
    }

    public void Clear(SlotType slot_type, int offset)
    {
        var action = slot_type switch
        {
            SlotType.Inventory              => () => m_inventory_service.Clear(offset),
            _                               => (Action)(() => {})
        };

        action();     
    }    
}



ItemSlotContextInstaller

위에서 작성한 ItemSlotContext가 정상적으로 동작하기 위해서는 인스톨러가 필요하다.

using EquipmentService;
using InventoryService;
using ShortcutService;
using SkillService;
using UnityEngine;

public class ItemSlotContextInstaller : MonoBehaviour, IInstaller
{
    [Header("아이템 데이터베이스")]
    [SerializeField] private ItemDataBase m_item_db;

    public void Install()
    {
        var item_slot_context = new ItemSlotContext(m_item_db,
                                                    ServiceLocator.Get<IInventoryService>());
        DIContainer.Register<IItemSlotContext>(item_slot_context);
    }
}



ItemSlotContext에 의한 확장

ItemSlotPresenter 확장

public class ItemSlotPresenter : IDisposable
{
	// 이전과 동일
	private readonly IItemSlotContext m_slot_context;
    
    public ItemSlotPresenter(IItemSlotView view,
                             IItemDataBase item_db,
                             IItemSlotContext slot_context,		// 확장된 부분
                             SlotType slot_type = SlotType.Inventory,
                             int item_count = 1)
    {
        m_view = view;
        m_item_db = item_db;
        m_slot_context = slot_context;							// 확장된 부분
        m_offset = offset;
        m_slot_type = slot_type;
        m_item_count = item_count;

        m_slot_context.Register(m_slot_type, UpdateSlot, m_offset, m_item_count);

        m_view.Inject(this);
    }    
    
    // 이전과 동일
    
    public void Dispose()
    {
        m_slot_context.Discard(m_slot_type, UpdateSlot);		// 확장된 부분
    }    
}

ItemSlotFactory 확장

public class ItemSlotFactory
{
    private readonly IItemSlotContext m_slot_context;				// 확장된 부분
    
    public ItemSlotFactory(IInventoryService inventory_service,
                           IItemSlotContext slot_context,			// 확장된 부분
                           IItemDataBase item_db,
                           ICursorDataBase cursor_db,
                           IItemCooler item_cooler)
    {
        m_inventory_service = inventory_service;

        m_slot_context = slot_context;								// 확장된 부분

        m_item_db = item_db;
        m_cursor_db = cursor_db;
    }    
    
    public ItemSlotPresenter Instantiate(IItemSlotView view, 
    									 int offset, 
                                         SlotType slot_type, 
                                         int count = 1)
    {
        return new ItemSlotPresenter(view,
                                     m_item_db,
                                     m_slot_context,				// 확장된 부분
                                     offset,
                                     slot_type,
                                     count);
    }
}

ItemSlotFactoryInstaller 확장

using InventoryService;
using SkillService;
using UnityEngine;

public class ItemSlotFactoryInstaller : MonoBehaviour, IInstaller
{
    [Header("아이템 데이터베이스")]
    [SerializeField] private ItemDataBase m_item_db;

    [Header("커서 데이터베이스")]
    [SerializeField] private CursorDataBase m_cursor_db;

    public void Install()
    {
        var item_slot_factory = new ItemSlotFactory(ServiceLocator.Get<IInventoryService>(),
                                                    DIContainer.Resolve<IItemSlotContext>(),	// 확장된 부분
                                                    m_item_db,
                                                    m_cursor_db);
        DIContainer.Register<ItemSlotFactory>(item_slot_factory);
    }
}



마무리

이번 글을 통해서 구현한 컨텍스트 덕분에 아이템 슬롯을 더욱 범용적으로 사용할 수 있게 되었다.

현재로서는 인벤토리 서비스만 존재하기 때문에 컨텍스트가 왜 필요한지 궁금할 수도 있을까봐 다음과 같이 전체 코드를 남기려고 한다.

using System;
using EquipmentService;
using InventoryService;
using ShortcutService;
using SkillService;

public class ItemSlotContext : IItemSlotContext
{
    private readonly IItemDataBase m_item_db;

    private readonly IInventoryService m_inventory_service;
    private readonly IEquipmentService m_equipment_service;
    private readonly ISkillService m_skill_service;
    private readonly IShortcutService m_shortcut_service;

    public ItemSlotContext(IItemDataBase item_db,
                           IInventoryService inventory_service,
                           IEquipmentService equipment_service,
                           ISkillService skill_service,
                           IShortcutService shortcut_service)
    {
        m_item_db = item_db;
        m_inventory_service = inventory_service;
        m_equipment_service = equipment_service;
        m_skill_service = skill_service;
        m_shortcut_service = shortcut_service;
    }

    public void Register(SlotType slot_type, Action<int, ItemData> update_action, int offset = 0, int count = 0)
    {
        switch (slot_type)
        {
            case SlotType.Inventory:
                m_inventory_service.OnUpdatedSlot += update_action;
                break;

            case SlotType.Equipment:
                m_equipment_service.OnUpdatedSlot += update_action;
                break;

            case SlotType.Skill:
                m_skill_service.OnUpdatedSlot += update_action;
                break;

            case SlotType.Shortcut:
                m_shortcut_service.OnUpdatedSlot += update_action;
                break;

            case SlotType.Shop:
            case SlotType.Craft:
                update_action?.Invoke(offset, Get(slot_type, offset, count));
                break;
        }       
    }             
    
    public void Discard(SlotType slot_type, Action<int, ItemData> update_action)
    {
        switch (slot_type)
        {
            case SlotType.Inventory:
                m_inventory_service.OnUpdatedSlot -= update_action;
                break;

            case SlotType.Equipment:
                m_equipment_service.OnUpdatedSlot -= update_action;
                break;

            case SlotType.Skill:
                m_skill_service.OnUpdatedSlot -= update_action;
                break;

            case SlotType.Shortcut:
                m_shortcut_service.OnUpdatedSlot -= update_action;
                break;
        }        
    }    

    public ItemData Get(SlotType slot_type, int offset, int count = 1)
    {
        return slot_type switch
        {
            SlotType.Inventory              => m_inventory_service.GetItem(offset),
            SlotType.Equipment              => m_equipment_service.GetItem(offset),
            SlotType.Skill                  => m_skill_service.GetSkill(offset),
            SlotType.Shortcut               => m_shortcut_service.GetItem(offset),
            SlotType.Shop or SlotType.Craft => new ItemData(m_item_db.GetItem((ItemCode)offset).Code, count),
            _                               => null,
        };
    }

    public void Set(SlotType slot_type, int offset, ItemCode code, int count = 1)
    {
        var action = slot_type switch
        {
            SlotType.Inventory              => () => m_inventory_service.SetItem(offset, code, count),
            SlotType.Equipment              => () => m_equipment_service.SetItem(offset, code),
            SlotType.Shortcut               => () => m_shortcut_service.SetItem(offset, code),
            _                               => (Action)(() => {})
        };

        action();
    }

    public void Update(SlotType slot_type, int offset, int count)
    {
        var action = slot_type switch
        {
            SlotType.Inventory              => () => m_inventory_service.UpdateItem(offset, count),
            _                               => (Action)(() => {})
        };

        action();
    }

    public void Clear(SlotType slot_type, int offset)
    {
        var action = slot_type switch
        {
            SlotType.Inventory              => () => m_inventory_service.Clear(offset),
            SlotType.Equipment              => () => m_equipment_service.Clear(offset),
            SlotType.Shortcut               => () => m_shortcut_service.Clear(offset),
            _                               => (Action)(() => {})
        };

        action();     
    }    
}

다음 글에서는 본격적으로 아이템 슬롯 이벤트와 연산에 대해서 알아볼 것이다.

profile
Game Client Programmer

0개의 댓글