[패스트캠퍼스 수강 후기] 올인원 패키지 : 유니티 포트폴리오 완성 100% 환급 챌린지 18회차 미션 시작합니다.


02 디아블로 게임 - 31. 인벤토리와 아이템 구현하기 - 02 인벤토리 시스템 구현하기
02 디아블로 게임 - 32. 인벤토리와 아이템 구현하기 - 03 플레이어와 상점을 위한 인벤토리 UI 구현하기




ItemDatabaseobject는 모든 아이템을 가지는 형태로 고려되어 있기때문에 InventoryObject에 할당될 때 문제가 없지만, 칼/투구/장갑 등 종류별로 ItemDatabaseObject를 구성하고자 하는 경우에는 id의 충돌이 발생하게 됩니다.
이를 간단히 피하기 위해서 baseId를 추가하여 처리하는 방식을 고려할 수 있습니다. 칼은 100번대, 투구는 200번대, 장갑은 300번대 baseId를 고정시켜두고 내부 id가 baseId에서 증가되는 형태로 제작할 수 있는 것입니다.





InventoryObject_New 스크립트를 생성합니다. UI와 관련이 높은 클래스입니다.

public enum InterfaceType_New { // 인벤토리 종류
    Inventory,
    Equipment,
    QuickSlot,
    ChestBox,
}

[CreateAssetMenu(fileName = "New Inventory", menuName = "Inventory System/Inventory_New")]
public class InventoryObject_New : ScriptableObject
{
    public ItemObjectDatabase_New database;
    public InterfaceType_New type;
    
    [SerializeField]
    private Inventory_New container = new Inventory_New();
    
    public InventorySlot[] Slots => container.slots;
    
    public int EmptySlotCount {
        get {
            int counter = 0;
            foreach (InventorySlot_New slot in Slots)
                if (slot.item.id < 0)
                    counter++;
            return counter;
        }
    }
    
    public bool AddItem(Item_New item, int amount) {
        if (EmptySlotCount <= 0) return false;
        InventorySlot_New slot = FindItemInInventory(item);
        if (!database.itemObjects[item.id].stackable || slot)
            GetEmptySlot().AddItem(item, amount);
        else
            slot.AddAmount(amount);
        return true;
    }
    
    public InventorySlot_New FindItemInInventory(Item_New item) {
        return Slots.FindOrDefault(i => i.item.id == item.id);
    }
    
    public InventorySlot_New GetEmptySlot() {
        return Slots.FirstOrDefault(i => i.item.id < 0);
    }
    
    public bool IsContainItem(ItemObject_New itemObject) {
        return Slots.FirstOrDefault(i => i.item.id == itemObject.data.id) != null;
    }
    
    public void SwapItems(InventorySlot_New itemSlotA, InventorySlot_New itemSlotB) {
        if (itemSlotA == itemSlotB) return;
        if (itemSlotB.CanPlaceInSlot(itemSlotA.ItemObject) && itemSlotA.CanPlaceInSlot(itemSlotB.ItemObject)) {
            InventorySlot tempSlot = new InventorySlot_New(itemSlotB.item, itemSlotB.amount);
            itemSlotB.UpdateSlot(itemSlotA.item, itemSlotA.amount);
            itemSlotA.UpdateSlot(tempSlot.item, tempSlot.amount);
        }
    }
}




이제 InventoryUI를 구현해볼 차례입니다.



UI 시스템은 다른 입력시스템이나 이벤트 트리거 등과 연관이 많이 되는 부분이라 복잡해지는 경향이 있습니다.

여기에서 InventoryUI는 실제 InventorySlot UI와 EventManager를 중재해주는 컨트롤러의 역할입니다.

즉, InventoryObject라는 Model, InventorySlot & EventManager라는 View, InventoryUI라는 Controller로 구분하여 MVC 모델을 구현한 것입니다.

StaticInventoryUI는 형태가 고정된 인벤토리 UI이며, DynamicInventoryUI는 형태가 고정되지 않은 인벤토리 UI입니다.
StaticInventoryUI는 장비창, DynamicInventoryUI는 인벤토리창으로 구현합니다.


 


public static class MouseData_New {
    public static InventoryUI interfaceMouseIsOver; // 어느 UI에 있는가?
    public static GameObject slotHoveredOver; // 어느 Slot 위에 있는가?
    public static GameObject tempItemBeingDragged;
}

[RequireComponent(typeof(EventTrigger))]
public abstract class InventoryUI_New : MonoBehaviour
{
    public InventoryObject_New inventoryObject;
    private InventoryObject_New previousInventoryObject;
    
    public Dictionary<GameObject, InventorySlot_New> slotUIs = new Dictionary<GameObject, InventoryObject_New>();
    
    private void Awake() {
        CreateSlotUIs();
        for (int i = 0; i < inventoryObject.Slots.Length; i++) {
            inventoryObject.Slots[i].parent = inventoryObject; // 초기화
            inventoryObject.Slots[i].OnPostUpdate += OnPostUpdate;
        }
        
        AddEvent(gameObject, EventTriggerType.PointerEnter, delegate { OnEnterInterface(gameObject); });
        AddEvent(gameObject, EventTriggerType.PointerExit, delegate { OnExitInterface(gameObject); });
    }
   
    protected virtual void Start() {
        for (int i = 0; i < inventoryObject.Slots.Length; i++) {
            inventoryObject.Slots[i].UpdateSlot(inventoryObject.Slots[i].item, inventoryObject.Slots[i].amount);
        }
    }
    
    public abstract CreateSlotUIs();
    
    protected void AddEvent(GameObject go, EventTriggerType type, UnityAction<BaseEventData> action) {
        EventTrigger trigger = go.GetComponent<EventTrigger>();
        if (!trigger) {
            Debug.LogWarning("No EventTrigger component found!");
            return;
        }
        
        EventTrigger.Entry eventTrigger = new EventTrigger.Entry { eventID = type };
        EventTrigger.callback.AddListener(action);
        trigger.triggers.Add(eventTrigger);
    }
    
    public void OnPostUpdate(InventorySlot_New slot) {
        slot.slotUI.transform.GetChild(0).GetComponent<Image>().sprite = slot.item.id < 0 ? null : slot.ItemObject.icon;
        slot.slotUI.transform.GetChild(0).GetComponent<Image>().color = slot.item.id < 0 ? new Color(1, 1, 1, 0) : new Color(1, 1, 1, 1);
        slot.slotUI.GetComponentInChildren<TextMeshProUGUI>().text = slot.item.id < 0 ? string.Empty : (slot.amount == 1);
    }
    
    public void OnEnterInterface(GameObject go) {
        MouseData_New.interfaceMouseIsOver = go.GetComponent<InventoryUI>();
    }
    
    public void OnExitInterface(GameObject go) {
        MouseData_New.interfaceMouseIsOver = null;
    }
    
    public void OnEnterSlot(GameObject go) {
        MouseData_New.slotHoveredOver = go;
    }
    
    public void OnExitSlot(GameObject go) {
        MouseData_New.slotHoveredOver = null;
    }
    
    public void OnStartDrag(GameObject go) {
        MouseData_New.tempItemBeingDragged = CreateDragImage(go);
    }
   
    public void CreateDragImage(GameObject go) {
        if (slotUIs[go].item.id < 0) return null;
        GameObject dragImageGo = new GameObject();
        RectTransform rectTransform = dragImageGo.AddComponent<RectTransform>();
        rectTransform.sizeDelta = new Vector2(50, 50);
        dragImageGo.transform.SetParent(transform.parent);
        Image image = dragImageGo.AddComponent<Image>();
        image.sprite = slotUIs[go].ItemObject.icon;
        image.raycastTarget = false;
        dragImageGo.name = "Drag Image";
        return dragImageGo;
    }
   
    public void OnDrag(GameObject go) {
        if (MouseData_New.tempItemBeingDragged == null) return;
        MouseData_New.tempItemBeingDragged.GetComponent<RectTransform>().position = Input.mousePosition;
    }
    
    public void OnEndDrag(GameObject go) {
        Destroy(MouseData_New.tempItemBeingDragged);
        if (MouseData_New.interfaceMouseIsOver == null)
            slotUIs[go].RemoveItem();
        else if (MouseData_New.slotHoveredOver) {
            InventorySlot_New mouseHoverSlotData = MouseData_New.interfaceMouseIsOver.slotUIs[MouseData_New.slotHoveredOver];
            inventoryObject.SwapItems(slotUIs[go], mouseHoverSlotData);
        }
    }
}


마우스가 아이콘을 클릭하여 드래그를 시작하면 임시 이미지를 만들어서 드래그중에는 마우스포인터 위치에 임시 이미지가 따라다니도록 하는 루틴입니다.
마우스를 특정 위치에서 놓았을 때에는 특정 UI에 있지 않은 경우라면 아이템을 버리는 것으로 보고 삭제합니다.
인터페이스위 슬롯 위에 있다면 아이템을 서로 교체하는 로직이 구동되게 됩니다.

 

 

오늘도 역시 눈은 즐겁지만 머리는 뱅글뱅글 도는 시간이 되었네요..



<위의 코드들은 제가 보면서 주요한 함수, 코드를 확인하기 위해 타이핑한 용도로, 전체 소스코드가 아님에 주의해 주세요. 전체 코드는 교육 수강을 하면 완벽하게 받으실 수가 있답니다 ^^>

패스트캠퍼스 - 올인원 패키지 : 유니티 포트폴리오 완성 bit.ly/2R561g0

 

유니티 게임 포트폴리오 완성 올인원 패키지 Online. | 패스트캠퍼스

게임 콘텐츠 프로그래머로 취업하고 싶다면, 포트폴리오 완성은 필수! '디아블로'와 '배틀그라운드' 게임을 따라 만들어 보며, 프로그래머 면접에 나오는 핵심 개념까지 모두 잡아 보세요!

www.fastcampus.co.kr

 

+ Recent posts