스파르타코딩클럽 게임개발

오늘은 스파르타 코딩 클럽 unity 게임 개발 과정 42일차땜빵(아이템 제작 스크립트 리팩토링 .. 실패)

코드천자문 2024. 1. 2. 21:11
반응형

마인크래프트 형식의 아이템 제작을 구현하는 코드를 검색하며 블로그를 뒤져가며 찾았다.

하지만 코드가 워낙 한 클래스에 너무 기능이 집약되어있어 리팩토링을 진행할까 싶었다.

아래는 원본 코드이다. 

```
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

[System.Serializable]
public class SlotContainer
{
    public Sprite itemSprite; // 아이템의 스프라이트 (아이템 배열에 있는 것과 동일해야 함), 또는 null로 두면 아이템 없음
    public int itemCount; // 이 슬롯에 있는 아이템 수, 1 이하면 1개의 아이템으로 해석됨
    [HideInInspector]
    public int tableID;
    [HideInInspector]
    public SC_SlotTemplate slot;
}

[System.Serializable]
public class Item
{
    public Sprite itemSprite;
    public bool stackable = false; // 이 아이템을 결합(쌓을 수) 있나요?
    public string craftRecipe; // 이 아이템을 제작하기 위해 필요한 아이템 키, 쉼표로 구분 (팁: 플레이 모드에서 제작 버튼을 사용하고 콘솔에서 인쇄된 레시피를 확인하세요)
}

public class SC_ItemCrafting : MonoBehaviour
{
    public RectTransform playerSlotsContainer;
    public RectTransform craftingSlotsContainer;
    public RectTransform resultSlotContainer;
    public Button craftButton;
    public SC_SlotTemplate slotTemplate;
    public SC_SlotTemplate resultSlotTemplate;



    public SlotContainer[] playerSlots;
    SlotContainer[] craftSlots = new SlotContainer[9];
    SlotContainer resultSlot = new SlotContainer();
    // 사용 가능한 모든 아이템 목록
    public Item[] items;

    SlotContainer selectedItemSlot = null;

    int craftTableID = -1; // 아이템이 하나씩 배치될 테이블의 ID (예: 제작 테이블)
    int resultTableID = -1; // 아이템을 가져갈 수 있지만 놓을 수 없는 테이블의 ID

    ColorBlock defaultButtonColors;

    // 첫 번째 프레임 업데이트 전에 호출됩니다
    void Start()
    {
        // 슬롯 요소 템플릿 설정
        slotTemplate.container.rectTransform.pivot = new Vector2(0, 1);
        slotTemplate.container.rectTransform.anchorMax = slotTemplate.container.rectTransform.anchorMin = new Vector2(0, 1);
        slotTemplate.craftingController = this;
        slotTemplate.gameObject.SetActive(false);
        // 결과 슬롯 요소 템플릿 설정
        resultSlotTemplate.container.rectTransform.pivot = new Vector2(0, 1);
        resultSlotTemplate.container.rectTransform.anchorMax = resultSlotTemplate.container.rectTransform.anchorMin = new Vector2(0, 1);
        resultSlotTemplate.craftingController = this;
        resultSlotTemplate.gameObject.SetActive(false);

        // 제작 버튼에 클릭 이벤트 연결
        craftButton.onClick.AddListener(PerformCrafting);
        // 제작 버튼 기본 색상 저장
        defaultButtonColors = craftButton.colors;

        // 제작 슬롯 초기화
        InitializeSlotTable(craftingSlotsContainer, slotTemplate, craftSlots, 5, 0);
        UpdateItems(craftSlots);
        craftTableID = 0;

        // 플레이어 슬롯 초기화
        InitializeSlotTable(playerSlotsContainer, slotTemplate, playerSlots, 5, 1);
        UpdateItems(playerSlots);

        // 결과 슬롯 초기화
        InitializeSlotTable(resultSlotContainer, resultSlotTemplate, new SlotContainer[] { resultSlot }, 0, 2);
        UpdateItems(new SlotContainer[] { resultSlot });
        resultTableID = 2;

        // 후버링 요소로 사용될 슬롯 요소 템플릿 리셋
        slotTemplate.container.rectTransform.pivot = new Vector2(0.5f, 0.5f);
        slotTemplate.container.raycastTarget = slotTemplate.item.raycastTarget = slotTemplate.count.raycastTarget = false;
    }

    void InitializeSlotTable(RectTransform container, SC_SlotTemplate slotTemplateTmp, SlotContainer[] slots, int margin, int tableIDTmp)
    {
        int resetIndex = 0;
        int rowTmp = 0;
        for (int i = 0; i < slots.Length; i++)
        {
            if (slots[i] == null)
            {
                slots[i] = new SlotContainer();
            }
            GameObject newSlot = Instantiate(slotTemplateTmp.gameObject, container.transform);
            slots[i].slot = newSlot.GetComponent<SC_SlotTemplate>();
            slots[i].slot.gameObject.SetActive(true);
            slots[i].tableID = tableIDTmp;

            float xTmp = (int)((margin + slots[i].slot.container.rectTransform.sizeDelta.x) * (i - resetIndex));
            if (xTmp + slots[i].slot.container.rectTransform.sizeDelta.x + margin > container.rect.width)
            {
                resetIndex = i;
                rowTmp++;
                xTmp = 0;
            }
            slots[i].slot.container.rectTransform.anchoredPosition = new Vector2(margin + xTmp, -margin - ((margin + slots[i].slot.container.rectTransform.sizeDelta.y) * rowTmp));
        }
    }

    // 테이블 UI 업데이트
    void UpdateItems(SlotContainer[] slots)
    {
        for (int i = 0; i < slots.Length; i++)
        {
            Item slotItem = FindItem(slots[i].itemSprite);
            if (slotItem != null)
            {
                if (!slotItem.stackable)
                {
                    slots[i].itemCount = 1;
                }
                // 총 아이템 수 적용
                if (slots[i].itemCount > 1)
                {
                    slots[i].slot.count.enabled = true;
                    slots[i].slot.count.text = slots[i].itemCount.ToString();
                }
                else
                {
                    slots[i].slot.count.enabled = false;
                }
                // 아이템 아이콘 적용
                slots[i].slot.item.enabled = true;
                slots[i].slot.item.sprite = slotItem.itemSprite;
            }
            else
            {
                slots[i].slot.count.enabled = false;
                slots[i].slot.item.enabled = false;
            }
        }
    }

    // 아이템 목록에서 스프라이트를 참조하여 아이템 찾기
    Item FindItem(Sprite sprite)
    {
        if (!sprite)
            return null;

        for (int i = 0; i < items.Length; i++)
        {
            if (items[i].itemSprite == sprite)
            {
                return items[i];
            }
        }

        return null;
    }

    // 아이템 목록에서 레시피를 참조하여 아이템 찾기
    Item FindItem(string recipe)
    {
        if (recipe == "")
            return null;

        for (int i = 0; i < items.Length; i++)
        {
            if (items[i].craftRecipe == recipe)
            {
                return items[i];
            }
        }

        return null;
    }

    public void ClickEventRecheck()
    {
        if (selectedItemSlot == null)
        {
            HandleNullSelectedItemSlot();
        }
        else
        {
            HandleSelectedItemSlot();
        }
    }

    private void HandleNullSelectedItemSlot()
    {
        selectedItemSlot = GetClickedSlot();
        if (selectedItemSlot != null && selectedItemSlot.itemSprite != null)
        {
            selectedItemSlot.slot.count.color = selectedItemSlot.slot.item.color = new Color(1, 1, 1, 0.5f);
        }
        else
        {
            selectedItemSlot = null;
        }
    }

    private void HandleSelectedItemSlot()
    {
        SlotContainer newClickedSlot = GetClickedSlot();
        if (newClickedSlot != null)
        {
            bool swapPositions = false;
            bool releaseClick = true;

            if (newClickedSlot != selectedItemSlot)
            {
                swapPositions = HandleDifferentSlots(newClickedSlot, swapPositions, ref releaseClick);
            }

            if (swapPositions)
            {
                SwapPositions(newClickedSlot);
            }

            if (releaseClick)
            {
                ReleaseClick();
            }

            UpdateItems(playerSlots);
            UpdateItems(craftSlots);
            UpdateItems(new SlotContainer[] { resultSlot });
        }
    }

    private bool HandleDifferentSlots(SlotContainer newClickedSlot, bool swapPositions, ref bool releaseClick)
    {
        // 같은 테이블의 다른 슬롯을 클릭한 경우
        if (newClickedSlot.tableID == selectedItemSlot.tableID)
        {
            swapPositions = HandleSameTableClicked(newClickedSlot, swapPositions);
        }
        else
        {
            // 다른 테이블로 이동하는 경우
            swapPositions = HandleDifferentTableClicked(newClickedSlot, swapPositions, ref releaseClick);
        }
        return swapPositions;
    }
    private bool HandleSameTableClicked(SlotContainer newClickedSlot, bool swapPositions)
    {
        // 새로 클릭한 아이템이 같은 경우, 쌓기; 아니면 교환 (제작 테이블이 아니라면 아무것도 하지 않음)
        if (newClickedSlot.itemSprite == selectedItemSlot.itemSprite)
        {
            Item slotItem = FindItem(selectedItemSlot.itemSprite);
            if (slotItem.stackable)
            {
                // 아이템이 같고 쌓을 수 있는 경우, 이전 위치에서 제거하고 새 위치에 수량 추가
                selectedItemSlot.itemSprite = null;
                newClickedSlot.itemCount += selectedItemSlot.itemCount;
                selectedItemSlot.itemCount = 0;
            }
            else
            {
                swapPositions = true;
            }
        }
        else
        {
            swapPositions = true;
        }
        return swapPositions;
    }

    private bool HandleDifferentTableClicked(SlotContainer newClickedSlot, bool swapPositions, ref bool releaseClick)
    {
        // 다른 테이블로 이동하는 경우
        if (resultTableID != newClickedSlot.tableID)
        {
            if (craftTableID != newClickedSlot.tableID)
            {
                if (newClickedSlot.itemSprite == selectedItemSlot.itemSprite)
                {
                    Item slotItem = FindItem(selectedItemSlot.itemSprite);
                    if (slotItem.stackable)
                    {
                        // 아이템이 같고 쌓을 수 있는 경우, 이전 위치에서 제거하고 새 위치에 수량 추가
                        selectedItemSlot.itemSprite = null;
                        newClickedSlot.itemCount += selectedItemSlot.itemCount;
                        selectedItemSlot.itemCount = 0;
                    }
                    else
                    {
                        swapPositions = true;
                    }
                }
                else
                {
                    swapPositions = true;
                }
            }
            else
            {
                if (newClickedSlot.itemSprite == null || newClickedSlot.itemSprite == selectedItemSlot.itemSprite)
                {
                    // selectedItemSlot에서 1개의 아이템 추가
                    newClickedSlot.itemSprite = selectedItemSlot.itemSprite;
                    newClickedSlot.itemCount++;
                    selectedItemSlot.itemCount--;
                    if (selectedItemSlot.itemCount <= 0)
                    {
                        // 마지막 아이템을 배치한 경우
                        selectedItemSlot.itemSprite = null;
                    }
                    else
                    {
                        releaseClick = false;
                    }
                }
                else
                {
                    swapPositions = true;
                }
            }
        }
        return swapPositions;
    }

    // 같은 테이블의 다른 슬롯을 클릭한 경우와 다른 테이블로 이동하는 경우에 대한 메소드를 구현하시면 되겠습니다.
    // 위의 코드를 참조하여 구현하시면 됩니다.

    private void SwapPositions(SlotContainer newClickedSlot)
    {
        Sprite previousItemSprite = selectedItemSlot.itemSprite;
        int previousItemConunt = selectedItemSlot.itemCount;

        selectedItemSlot.itemSprite = newClickedSlot.itemSprite;
        selectedItemSlot.itemCount = newClickedSlot.itemCount;

        newClickedSlot.itemSprite = previousItemSprite;
        newClickedSlot.itemCount = previousItemConunt;
    }

    private void ReleaseClick()
    {
        selectedItemSlot.slot.count.color = selectedItemSlot.slot.item.color = Color.white;
        selectedItemSlot = null;
    }

    private SlotContainer GetClickedSlot()
    {
        for (int i = 0; i < playerSlots.Length; i++)
        {
            if (playerSlots[i].slot.hasClicked)
            {
                playerSlots[i].slot.hasClicked = false;
                return playerSlots[i];
            }
        }

        for (int i = 0; i < craftSlots.Length; i++)
        {
            if (craftSlots[i].slot.hasClicked)
            {
                craftSlots[i].slot.hasClicked = false;
                return craftSlots[i];
            }
        }

        if (resultSlot.slot.hasClicked)
        {
            resultSlot.slot.hasClicked = false;
            return resultSlot;
        }

        return null;
    }

    private void PerformCrafting()
    {
        string[] combinedItemRecipe = new string[craftSlots.Length];

        craftButton.colors = defaultButtonColors;

        for (int i = 0; i < craftSlots.Length; i++)
        {
            Item slotItem = FindItem(craftSlots[i].itemSprite);
            if (slotItem != null)
            {
                combinedItemRecipe[i] = slotItem.itemSprite.name + (craftSlots[i].itemCount > 1 ? "(" + craftSlots[i].itemCount + ")" : "");
            }
            else
            {
                combinedItemRecipe[i] = "";
            }
        }

        string combinedRecipe = string.Join(",", combinedItemRecipe);
        print(combinedRecipe);

        // 레시피가 아이템 레시피와 일치하는지 확인
        Item craftedItem = FindItem(combinedRecipe);
        if (craftedItem != null)
        {
            // 제작 슬롯 초기화
            for (int i = 0; i < craftSlots.Length; i++)
            {
                craftSlots[i].itemSprite = null;
                craftSlots[i].itemCount = 0;
            }

            resultSlot.itemSprite = craftedItem.itemSprite;
            resultSlot.itemCount = 1;

            UpdateItems(craftSlots);
            UpdateItems(new SlotContainer[] { resultSlot });
        }
        else
        {
            ColorBlock colors = craftButton.colors;
            colors.selectedColor = colors.pressedColor = new Color(0.8f, 0.55f, 0.55f, 1);
            craftButton.colors = colors;
        }
    }

    // 매 프레임마다 호출됩니다
    void Update()
    {
        // 슬롯 UI가 마우스 위치를 따라감
        if (selectedItemSlot != null)
        {
            if (!slotTemplate.gameObject.activeSelf)
            {
                slotTemplate.gameObject.SetActive(true);
                slotTemplate.container.enabled = false;

                // 선택된 아이템 값을 슬롯 템플릿에 복사
                slotTemplate.count.color = selectedItemSlot.slot.count.color;
                slotTemplate.item.sprite = selectedItemSlot.slot.item.sprite;
                slotTemplate.item.color = selectedItemSlot.slot.item.color;
            }

            // 템플릿 슬롯이 마우스 위치를 따라가게 함
            slotTemplate.container.rectTransform.position = Input.mousePosition;
            // 아이템 수량 업데이트
            slotTemplate.count.text = selectedItemSlot.slot.count.text;
            slotTemplate.count.enabled = selectedItemSlot.slot.count.enabled;
        }
        else
        {
            if (slotTemplate.gameObject.activeSelf)
            {
                slotTemplate.gameObject.SetActive(false);
            }
        }
    }
}
```

```
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using TMPro;

public class SC_SlotTemplate : MonoBehaviour, IPointerClickHandler
{
    public Image container;
    public Image item;
    public TextMeshProUGUI count;

    [HideInInspector]
    public bool hasClicked = false;
    [HideInInspector]
    public SC_ItemCrafting craftingController;

    //Do this when the mouse is clicked over the selectable object this script is attached to.
    public void OnPointerClick(PointerEventData eventData)
    {
        hasClicked = true;
        craftingController.ClickEventRecheck();
    }
}
```

 

 

코드가 진짜 너무 길다..

 

그래서 이런식으로 바꾸어 보려고 했다.

클래스 구조

  • Item: 아이템에 대한 데이터를 포함합니다.
  • Slot: 슬롯에 대한 데이터를 포함합니다.
  • ItemDatabase: 사용 가능한 모든 아이템의 데이터를 저장합니다.
  • Inventory: 플레이어의 인벤토리를 관리합니다.
  • CraftingSystem: 크래프팅 로직을 처리합니다.
  • UIManager: UI 요소들을 관리합니다.
  • SlotUI: 각 슬롯의 UI 표현을 관리합니다.

하지만 완성하지 못했다.

 

클래스를 나누어 실행시켜보니 slotUI가 제 위치를 찾아가지 못하고 딴길로 새버리는 것이다.

 

한참동안 이 문제를 해결하려고 했으나 역부족이였다. 

 

시간은 많이 남아있으니 해결해보도록 노력해 보겠다. 

반응형