제작 시스템

제작 시스템은 재료와 재화를 소모하여 아이템을 생성하는 구조이다.

검색 기능을 통해 제작 가능한 아이템을 빠르게 탐색할 수 있다.

전체 플레이 영상

전체 코드

using System.Text;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class CraftSystem : MonoBehaviour
{
    [Header("프리팹 및 패널")]
    public GameObject itemSlotPrefab;     // 제작 아이템 슬롯 프리팹
    public Transform contentPanel;      // 아이템 슬롯이 배치될 부모 패널
    public GameObject craftPanel;       // 아이템 제작 패널

    [Header("아이템 데이터")]
    public List<Item> craftableItems;   // 제작 가능한 아이템 목록
    public List<Item> materialItems;    // 각 아이템 제작에 필요한 재료 목록

    [Header("UI 표시 요소")]
    public Image itemImage;             // 제작 아이템 이미지 표시
    public Image materialImage;         // 제작 재료 아이템 이미지 표시
    public TextMeshProUGUI statTxt;     // 제작 아이템 스탯 정보
    public TextMeshProUGUI haveItemTxt;    // 현재 소지 재료 아이템 수량
    public TextMeshProUGUI haveCoinTxt;    // 현재 보유 재화 수량
    public TextMeshProUGUI needItemTxt;    // 필요한 재료 수량
    public TextMeshProUGUI needCoinTxt;    // 필요한 재화 수량
    public GameObject cantCraftMsg;        // 재료 부족 시 표시할 메시지

    [Header("아이템 검색")]
    public TMP_InputField serch_InputField;

    ItemManager itemManager;    
    private int selectedIndex = -1; // 현재 선택된 아이템 인덱스

    private void Start()
    {
        itemManager = GameManager_LDW.instance.itemManager;

        if (serch_InputField != null) serch_InputField.onValueChanged.AddListener(OnSearchValueChanged);

        CreateItemSlots(); 
    }

    private void Update()
    {
        // 선택된 아이템이 있을 때, 재료/재화 실시간 갱신
        if (selectedIndex >= 0 && selectedIndex < materialItems.Count) 
        {
            haveItemTxt.text = materialItems[selectedIndex].count.ToString();
            haveCoinTxt.text = itemManager.coin.ToString();  
        }
    }

    /// --- 아이템 검색 --- ///
    // 한글 완성형 초성 테이블
    private static readonly char[] InitialConsonants =
    {
    'ㄱ','ㄲ','ㄴ','ㄷ','ㄸ','ㄹ','ㅁ','ㅂ','ㅃ',
    'ㅅ','ㅆ','ㅇ','ㅈ','ㅉ','ㅊ','ㅋ','ㅌ','ㅍ','ㅎ'
    };

    // 아이템 이름 → 초성 문자열 (예: "생명의 반지" → "ㅅㅁㅇㅢㅂㅈ")
    private string GetInitialsFromName(string source)
    {
        if (string.IsNullOrEmpty(source))
            return string.Empty;

        StringBuilder sb = new StringBuilder();

        foreach (char ch in source)
        {
            // 한글 완성형: AC00 ~ D7A3
            if (ch >= 0xAC00 && ch <= 0xD7A3)
            {
                int unicode = ch - 0xAC00;
                int initialIndex = unicode / (21 * 28);
                sb.Append(InitialConsonants[initialIndex]);
            }
            // 나머지(공백, 영어 등)는 그냥 무시
        }

        return sb.ToString();
    }

    // 검색어 → 초성 패턴 (예: "생반" → "ㅅㅂ", "ㅅ" → "ㅅ")
    private string GetInitialPatternFromFilter(string filter)
    {
        if (string.IsNullOrEmpty(filter))
            return string.Empty;

        StringBuilder sb = new StringBuilder();

        foreach (char ch in filter)
        {
            // 완성형 한글이면 초성 뽑기
            if (ch >= 0xAC00 && ch <= 0xD7A3)
            {
                int unicode = ch - 0xAC00;
                int initialIndex = unicode / (21 * 28);
                sb.Append(InitialConsonants[initialIndex]);
            }
            // 자모(ㄱ~ㅎ)면 그대로 사용
            else if (ch >= 0x3131 && ch <= 0x314E)
            {
                sb.Append(ch);
            }
            // 그 외(숫자, 영어 등)는 무시
        }

        return sb.ToString();
    }


    /// --- 제작 슬롯 생성 --- ///
    public void CreateItemSlots()
    {
        CreateItemSlots(string.Empty);
    }

    private void CreateItemSlots(string filter)
    {
        // 기존 슬롯 전부 삭제
        foreach (Transform child in contentPanel)
            Destroy(child.gameObject);

        string lowerFilter = string.IsNullOrEmpty(filter) ? string.Empty : filter.ToLower();
        string initialFilter = GetInitialPatternFromFilter(filter);   // 검색어의 초성 패턴

        for (int i = 0; i < craftableItems.Count; i++)
        {
            Item item = craftableItems[i];
            Item material = materialItems[i];

            // 검색어가 있을 때만 필터링
            if (!string.IsNullOrEmpty(lowerFilter))
            {
                string nameLower = item.itemName.ToLower();
                string nameInitials = GetInitialsFromName(item.itemName);

                bool nameMatch = nameLower.Contains(lowerFilter);    // "반지", "체력" 등 일반 검색
                bool initialMatch = false;

                // 초성 패턴이 있을 때만 초성 비교
                if (!string.IsNullOrEmpty(initialFilter))
                {
                    initialMatch = nameInitials.Contains(initialFilter);
                }

                if (!nameMatch && !initialMatch)
                    continue; // 둘 다 아니면 스킵
            }

            GameObject slot = Instantiate(itemSlotPrefab, contentPanel);
            SetSlotInfo(slot, item, material, i);
        }
    }

    private void SetSlotInfo(GameObject slot, Item item, Item material, int index)
    {
        slot.transform.Find("ItemNameTxt").GetComponent<TextMeshProUGUI>().text = item.itemName;
        slot.transform.Find("ItemIcon").GetComponent<Image>().sprite = item.itemImage;              
        slot.transform.Find("MaterialIcon").GetComponent<Image>().sprite = material.itemImage; 

        TextMeshProUGUI matAmountTxt = slot.transform.Find("MaterialAmountTxt").GetComponent<TextMeshProUGUI>();    
        TextMeshProUGUI coinAmountTxt = slot.transform.Find("CoinAmountTxt").GetComponent<TextMeshProUGUI>();   

        // 아이템별 제작 필요 수량 설정
        if (item.itemCode == "200_01_01")   
        {
            matAmountTxt.text = "x 3";
            coinAmountTxt.text = "x 100"; 
        }
        else  
        {
            matAmountTxt.text = "x 1";  
            coinAmountTxt.text = "x 50";   
        }
        
        slot.GetComponent<Button>().onClick.AddListener(() => OnItemClicked(item, index)); 
    }

    // 검색 결과에 따라 제작 슬롯 생성
    private void OnSearchValueChanged(string searchText)
    {
        // 양쪽 공백 제거
        searchText = searchText.Trim();

        // 아무 것도 없으면 전체 다시 생성
        if (string.IsNullOrEmpty(searchText))
        {
            CreateItemSlots();
        }
        else
        {
            CreateItemSlots(searchText);
        }

        // 검색할 때는 선택 초기화 / 제작 패널 닫기
        selectedIndex = -1;
        craftPanel.SetActive(false);
    }


    /// --- 아이템 클릭 시 제작창 표시 --- ///
    private void OnItemClicked(Item item, int index)
    {
        selectedIndex = index; 
        craftPanel.SetActive(true);

        itemImage.sprite = item.itemImage;
        materialImage.sprite = materialItems[index].itemImage;

        // 스탯 표시
        statTxt.text = string.Join("\n", item.stats);

        // 현재 재료 및 재화 표시
        haveItemTxt.text = materialItems[index].count.ToString();  
        haveCoinTxt.text = itemManager.coin.ToString();

        // 필요 수량 계산
        bool isHpPotion = item.itemCode == "200_01_01";
        needItemTxt.text = isHpPotion ? "3" : "1";
        needCoinTxt.text = isHpPotion ? "100" : "50";

        // 버튼 이벤트 설정
        Button craftBtn = craftPanel.transform.Find("CraftBtn").GetComponent<Button>();
        craftBtn.onClick.RemoveAllListeners();
        craftBtn.onClick.AddListener(() => TryCraft(item, index));
    }


    /// --- 제작 시도 및 검증 --- ///
    private void TryCraft(Item item, int index)
    {
        int haveItems = int.Parse(haveItemTxt.text);
        int needItems = int.Parse(needItemTxt.text);
        int haveCoins = int.Parse(haveCoinTxt.text);
        int needCoins = int.Parse(needCoinTxt.text);

        if (haveItems >= needItems && haveCoins >= needCoins)
            CraftItem(item, index, needItems, needCoins);
        else
            StartCoroutine(ShowTemporaryMsg(cantCraftMsg, 0.5f));
    }


    /// --- 제작 처리 --- ///
    private void CraftItem(Item item, int index, int requiredItem, int requiredCoin)
    {
        materialItems[index].count -= requiredItem;
        itemManager.coin -= requiredCoin;

        itemManager.AddItem(item.itemCode, 1);
        UpdateUIAfterCraft(index);
    }

    private void UpdateUIAfterCraft(int index)
    {
        haveItemTxt.text = materialItems[index].count.ToString();
        haveCoinTxt.text = itemManager.coin.ToString();
    }

    private IEnumerator ShowTemporaryMsg(GameObject obj, float duration)
    {
        obj.SetActive(true);
        yield return new WaitForSeconds(duration);
        obj.SetActive(false);
    }
}

전체 흐름도