선택 상태 기반 제작 패널 동기화 구조

목차

1. 요구 사항

2. 설계 목표

3. 흐름도

4. 구현

        4.1. 선택 상태의 단일 기준화

       4.2. 슬롯 클릭 시 제작 패널 동기화

       4.3. 실시간 재화/재료 UI 동기화

       4.4. 검색 입력 시 선택 상태 초기화

5. 개발 의도

1. 시스템 요구 사항

제작 슬롯을 데이터 기반으로 동적 생성하는 구조를 만든 이후, 다음으로 해결해야 했던 문제는 어떤 아이템이 선택되었는지를 시스템 전반에서 일관되게 인식하는 방식이었다.

제작 UI는 단순히 슬롯을 나열하는 것으로 끝나지 않고, 슬롯 클릭 시 제작 패널에 아이템 이미지, 스탯 정보, 필요 재료, 보유 재화, 제작 버튼 상태까지 동시에 갱신되어야 한다.

이때 각 UI 요소가 서로 다른 기준으로 갱신되면, 특정 패널만 이전 아이템 정보를 보여주거나 재화 수량이 어긋나는 문제가 발생하기 쉽다.

초기 구현에서는 슬롯 클릭 시마다 여러 UI 요소를 개별적으로 갱신하는 방식이었는데, 이 경우 어떤 정보가 어떤 아이템 기준으로 표시되는지를 추적하기 어려웠다.

특히 검색 기능과 결합되었을 때, 선택 상태가 명확히 관리되지 않으면 잘못된 아이템 기준으로 제작이 시도될 수 있었다.

따라서 제작 시스템에서는 선택 상태를 하나의 기준 값으로 관리하고, 그 기준을 중심으로 UI와 제작 로직이 함께 움직이도록 구조를 재정리할 필요가 있었다.

2. 설계  목표

- 선택된 제작 아이템을 단일 상태 값으로 관리할 것

- UI 표시, 제작 로직, 재화 갱신이 동일한 기준을 사용하도록 할 것

- 슬롯 재생성(검색) 시 선택 상태를 명확히 초기화할 것

- 제작 패널이 선택 상태가 유효할 때만 활성화되도록 할 것

3. 흐름도

이 구조에서 중요한 점은, 선택 상태 자체가 UI에 종속되지 않는다는 점이다.

어떤 슬롯이 클릭되었는지는 버튼이나 슬롯 오브젝트가 아니라, selectedIndex라는 단일 상태 값으로만 관리된다.

제작 패널, 재화 표시, 제작 버튼 로직은 모두 이 값을 기준으로 동작하며, 슬롯은 단순히 선택 상태를 바꾸는 트리거 역할만 수행한다.

4. 구현

4.1. 선택 상태의 단일 기준화
private int selectedIndex = -1;

선택 상태는 단순한 정수 인덱스로 관리한다.

이 값은 craftableItems와 materialItems 리스트의 공통 인덱스를 의미하며, 현재 제작 패널이 어떤 아이템을 기준으로 표시되어야 하는지를 나타낸다.

초기값을 -1로 둔 이유는 아무 아이템도 선택되지 않은 상태를 명확히 표현하기 위함이다.

이 값이 유효 범위에 들어가지 않으면 제작 패널은 갱신 대상이 없다고 판단할 수 있다.

이 방식의 장점은 선택 상태가 UI 오브젝트에 묶이지 않는다는 점이다.

선택 상태를 객체 참조가 아닌 정수 인덱스로 관리한 이유는, 슬롯 재생성이나 UI 파괴 시에도 참조 무효 문제가 발생하지 않도록 하기 위함이다.

이 값이 유효 범위에 들어가지 않으면 제작 패널은 갱신 대상이 없다고 판단할 수 있다.

따라서, Update나 검색 이벤트 처리에서 조건문으로 바로 활용할 수 있는 단순한 상태 기준이 된다.

선택 기준은 단순한 값 하나로 유지되며, 필요할 경우 언제든 초기화할 수 있다.

4.2. 슬롯 클릭 시 제작 패널 동기화
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));
}

OnItemClicked 함수는 제작 슬롯이 클릭되었을 때 호출되며, 선택된 아이템을 기준으로 제작 패널 전체를 초기화하고 동기화하는 역할을 한다.

이 함수는 단순히 선택 상태를 기록하는 데서 끝나지 않고, 플레이어가 지금 어떤 아이템을 제작하려고 하는지를 시각적으로 확인할 수 있도록 제작 패널 UI를 즉시 갱신하는 진입점이다.

함수가 호출되면 가장 먼저 selectedIndex를 전달받은 인덱스로 갱신한다.

이 값은 이후 제작 시스템 전반에서 사용되는 단일 선택 기준이 되며, 제작 패널 UI 표시, 재료 수량 표시, 제작 버튼 로직까지 모두 이 값을 기준으로 동작한다.

선택 상태가 갱신된 직후 제작 패널을 활성화한다.

슬롯을 클릭했을 때 즉시 제작 패널을 열어주는 이유는, 플레이어가 클릭한 아이템이 제작 대상임을 명확하게 인지하고 다음 행동(제작 여부 판단)으로 자연스럽게 이어질 수 있도록 하기 위함이다.

이후 제작 패널 내부의 아이템 이미지와 재료 이미지를 각각 설정한다.

아이템 이미지는 슬롯 클릭 시 전달받은 item의 이미지로 설정하고, 재료 이미지는 동일한 인덱스를 사용하는 materialItems[index]에서 가져온다.

이 구조를 통해 제작 패널에 표시되는 아이템과 재료가 슬롯에서 선택한 대상과 정확히 일치함을 보장한다.

이는 플레이어가 현재 보고 있는 제작 대상이 자신이 의도한 아이템이 맞는지를 즉시 시각적으로 확인할 수 있게 하기 위한 설계다.

제작 시스템에서는 잘못된 아이템으로 제작이 진행되는 경험 자체가 치명적인 UX 오류가 되기 때문에, 이미지 동기화는 선택 직후 가장 먼저 이루어져야 한다.

스탯 정보는 string.Join("\n", item.stats)를 사용해 문자열 배열을 하나의 줄바꿈 문자열로 결합했다.

string.Join은 C#에서 문자열 컬렉션을 특정 구분자를 기준으로 하나의 문자열로 합치는 표준 API다.

이 함수를 사용한 이유는 단순한 문자열 결합이 아니라, 여러 개의 스탯 항목을 UI에 나열해서 보여준다는 의도를 코드 레벨에서 명확히 드러내기 위함이다.

만약 반복문을 돌며 += 연산으로 문자열을 이어 붙였다면, 문자열 결합 로직과 UI 표현 목적이 코드 안에서 섞이게 되고 가독성도 떨어진다.

string.Join은 내부적으로 문자열 결합을 효율적으로 처리하며, 무엇보다 배열을 줄 단위 텍스트로 변환한다는 의도가 함수 호출만으로 명확하게 드러난다.

이 방식 덕분에 스탯 개수가 가변적인 상황에서도 UI 코드를 수정할 필요 없이, 데이터 구조(item.stats)만 변경하면 자동으로 표시 결과가 갱신된다.

즉, 이 선택은 단순한 문법 사용이 아니라 UI는 데이터를 어떻게 가공하는지가 아니라, 데이터를 어떻게 보여주는지에만 집중하도록 하기 위한 설계 선택이다.

스탯 정보 표시 이후에는, 제작 가능 여부를 판단하기 위한 현재 보유 자원 정보를 UI에 반영한다.

haveItemTxt에는 선택된 아이템에 대응되는 재료 아이템의 현재 보유 수량을 표시하고, haveCoinTxt에는 ItemManager가 관리하는 현재 보유 재화 수량을 표시한다.

이 두 값은 단순한 UI 표시용 텍스트가 아니라, 플레이어가 제작 버튼을 누르기 전에 지금 이 아이템을 만들 수 있는가를 판단하는 핵심 정보다.

스탯 정보가 만들었을 때의 결과를 보여주는 정보라면, 보유 재료와 재화 정보는 지금 행동 가능한지를 판단하게 만드는 정보다.

제작에 필요한 재료 수량과 재화 비용은 선택된 아이템의 종류에 따라 다르게 표시된다.

현재 구현에서는 itemCode를 기준으로 간단한 분기 처리를 사용했다.

bool isHpPotion = item.itemCode == "200_01_01"; 

이 조건은 선택된 아이템이 체력 포션인지 여부를 판단하기 위한 것으로, 포션 아이템만 제작 비용이 더 높은 예외 케이스로 설정되어 있다.

이후 삼항 연산자를 사용해 필요 재료 수량과 재화 비용을 UI에 반영한다.

이 방식은 제작 레시피 구조를 설명하기보다는, 선택된 아이템에 따라 제작 조건이 동적으로 바뀐다는 UI 동작을 명확히 보여주기 위한 단순화된 구현이다.

실제 서비스 환경에서는 제작 비용 정보가 아이템 데이터나 레시피 테이블로 분리되는 것이 바람직하다.

다만 이 제작 시스템에서는 선택 상태 기반 UI 동기화 구조를 설명하는 것이 목적이었기 때문에, 비용 로직은 최소한의 분기만으로 표현했다.

이로 인해 코드 구조는 단순하지만, “선택된 아이템 → 제작 조건 → UI 반영”이라는 흐름은 명확하게 드러난다.

이후 레시피 구조가 확장되더라도, 이 구간만 데이터 참조 방식으로 교체하면 전체 제작 패널 동기화 구조는 그대로 유지할 수 있다.

이후 보유 재료 수량과 보유 재화 수량을 현재 선택된 인덱스를 기준으로 표시한다.

이 정보는 플레이어가 제작 가능 여부를 즉시 판단하는 데 사용되며, 제작 버튼을 누르기 전에 필요한 판단 정보를 모두 제공하는 역할을 한다.

마지막으로 제작 버튼의 클릭 이벤트를 재설정한다.

버튼은 제작 패널에서 재사용되는 UI 요소이기 때문에, 이전에 선택된 아이템의 제작 로직이 남아 있지 않도록 RemoveAllListeners()로 기존 이벤트를 제거한 뒤 현재 선택된 아이템과 인덱스를 기준으로 TryCraft()를 다시 연결한다.

제작 버튼에서도 동일한 index를 전달함으로써, 이후 제작 조건 검증 및 실행 단계까지 선택 상태 기준이 유지되도록 했다.

이 구조를 통해 OnItemClicked()는 '슬롯 선택 → 제작 패널 표시 → UI 정보 동기화 → 제작 로직 연결' 이라는 전체적인 역할을 수행한다.

슬롯은 상태를 저장하지 않고, 이 함수 하나를 통해 제작 시스템의 모든 후속 동작이 동일한 선택 기준으로 이어지도록 설계되었다.

4.3. 실시간 재화/재료 UI 동기화
private void Update()
{
    if (selectedIndex >= 0 && selectedIndex < materialItems.Count)
    {
        haveItemTxt.text = materialItems[selectedIndex].count.ToString();
        haveCoinTxt.text = itemManager.coin.ToString();
    }
}

이 구간은 제작 패널이 열려 있는 동안, 현재 선택된 아이템 기준으로 재료 수량과 재화 수량을 항상 최신 상태로 유지하는 역할을 담당한다.

OnItemClicked()가 선택 순간의 UI 초기화라면, 이 Update 로직은 선택 이후에도 상태가 계속 변할 수 있다는 전제를 반영한 지속 동기화 단계다.

제작 패널이 열려 있는 동안에도 인벤토리나 재화 수량은 외부 시스템에 의해 변경될 수 있다.

예를 들어 다른 제작 시스템, 상점 구매·판매, 보상 획득, 퀘스트 완료 등은 모두 제작 UI 외부에서 발생하지만, 결과적으로는 제작 가능 여부에 직접적인 영향을 미친다.

만약 제작 패널이 최초 선택 시점의 값만 표시하고 이후 갱신되지 않는다면, 플레이어는 이미 재료를 소모했음에도 여전히 제작이 가능한 것처럼 보이거나, 반대로 이미 충분한 재화를 확보했음에도 제작이 불가능한 상태로 인식하는 UI 불일치를 겪게 된다.

이를 방지하기 위해, 제작 시스템에서는 선택 상태가 유효한 동안에는 매 프레임 현재 데이터를 다시 읽어와 UI를 갱신하는 방식을 사용했다.

Update 함수 내부에서 먼저 selectedIndex가 유효 범위에 들어가는지를 검사하는 이유는, 제작 패널이 열려 있지 않거나 아무 아이템도 선택되지 않은 상태에서는 불필요한 UI 갱신을 수행하지 않도록 하기 위함이다.

selectedIndex >= 0 조건은 현재 선택된 아이템이 존재하는가를 의미하고, selectedIndex < materialItems.Count 조건은 선택 인덱스가 데이터 리스트 범위를 벗어나지 않았는가를 보장한다.

이 두 조건을 만족하는 경우에만, 현재 선택된 아이템 기준으로 재료와 재화 정보를 UI에 반영한다.

haveItemTxt는 현재 선택된 아이템 제작에 필요한 재료 아이템의 보유 수량을 표시한다.

여기서 중요한 점은, 이 값이 UI가 따로 저장하고 있는 값이 아니라 항상 materialItems 리스트에 들어 있는 실제 데이터 값을 그대로 읽어온다는 점이다.

즉, 제작 UI는 상태를 기억하는 주체가 아니라, 항상 현재 데이터 상태를 조회해서 보여주는 뷰(View) 역할만 수행한다.

마찬가지로 재화 수량도 UI 내부에서 관리하지 않고, ItemManager가 관리하는 단일 재화 데이터(source of truth)를 그대로 참조한다.

이 구조를 통해 재화 소모, 획득, 변동이 어디에서 발생하든지 간에, 제작 패널에 표시되는 값은 항상 실제 게임 상태와 일치하게 된다.

이 방식의 핵심 설계 의도는 UI와 데이터의 책임을 명확히 분리하는 것이다.

UI는 상태를 저장하거나 판단하지 않고, 오직 현재 상태를 보여주기만 한다.

제작 가능 여부 판단은 이후 TryCraft 함수단계에서 데이터 기준으로 다시 검증되며, UI는 그 판단을 대신하지 않는다.

이로 인해 UI에는 가능해 보였는데 실제로는 실패하는 상황이나, UI가 오래된 정보를 보여주는 문제가 구조적으로 차단된다.

물론 Update 기반 갱신은 매 프레임 실행되기 때문에, 이벤트 기반 갱신에 비해 불필요한 연산이 발생할 수 있다.

그러나 이 제작 시스템에서 Update가 수행하는 작업은 텍스트 몇 개를 갱신하는 수준이며, 제작 패널이 열려 있는 동안에만 의미를 갖는다.

또한 재화 변경 이벤트가 여러 시스템에서 발생하는 구조에서는, 모든 변경 지점마다 이벤트를 연결하는 방식보다 현재 상태를 주기적으로 읽는 방식이 오히려 안정적이라고 판단했다.

정리하면, 이 Update 로직은 단순한 UI 갱신 코드가 아니라 선택 이후에도 상태는 변할 수 있다는 게임 시스템의 현실을 반영하여, 제작 UI가 항상 신뢰 가능한 정보만을 표시하도록 보장하는 장치다.

이를 통해 제작 패널은 선택 시점에만 정확한 UI가 아니라, 열려 있는 동안 내내 정확한 UI로 동작하게 된다.

4.4. 검색 입력 시 선택 상태 초기화
private void OnSearchValueChanged(string searchText)
{
    ...
    selectedIndex = -1;
    craftPanel.SetActive(false);
}

검색어가 변경될 때는 슬롯이 재생성되며, 이 시점에서 선택 상태는 반드시 초기화된다.

검색은 제작 가능한 아이템 목록을 보여주는 방식만 바꾸는 기능이지, 이전에 선택했던 제작 대상을 유지하거나 제작 상태를 이어가는 기능이 아니기 때문이다.

만약 검색 결과가 바뀌었음에도 불구하고 이전 선택 상태가 그대로 유지된다면, 화면에 더 이상 표시되지 않는 아이템을 기준으로 제작 패널이 표시되거나 제작 시도가 이루어지는 문제가 발생할 수 있다.

이를 방지하기 위해 검색 입력이 변경되는 시점에서 selectedIndex를 명시적으로 -1로 초기화한다.

이 값은 아무 아이템도 선택되지 않은 상태를 의미하며, 제작 패널을 갱신할 대상이 없다는 것을 시스템 전반에 명확히 전달하는 역할을 한다.

선택 상태를 단순한 정수 값으로 관리하고 있기 때문에, 이 초기화는 Update나 이후 제작 로직에서 조건문 하나로 즉시 처리할 수 있는 명확한 기준이 된다.

선택 상태 초기화와 함께 제작 패널도 비활성화한다

제작 패널은 선택된 아이템이 있을 때만 의미를 가지는 UI이기 때문에, 검색으로 인해 슬롯 목록이 바뀌는 순간 기존 제작 패널을 그대로 유지하는 것은 UX 측면에서 혼란을 유발할 수 있다.

플레이어는 검색 결과를 통해 새로운 아이템 목록을 보고 있는데, 제작 패널에는 이전 아이템 정보가 남아 있다면 현재 무엇을 기준으로 제작이 이루어지는지 직관적으로 이해하기 어렵다.

따라서 검색 입력이 발생하면 제작 패널을 닫고, 시스템을 다시 아이템을 선택하지 않은 대기 상태로 되돌린다.

이로써 제작 패널은 항상 현재 화면에 표시된 슬롯 중 하나가 선택되었을 때만 열리게 되며, 제작 시스템의 상태와 화면 상태가 어긋나는 상황을 구조적으로 차단할 수 있다.

이 설계에서 중요한 점은, 검색 기능을 제작 시스템의 핵심 상태 관리에서 분리했다는 것이다.

검색은 어디까지나 슬롯 표시를 돕는 보조 기능이며, 제작 대상 선택이나 제작 로직의 기준을 직접 변경하지 않는다.

선택 상태는 오직 슬롯 클릭이라는 명시적인 사용자 행동을 통해서만 설정되며, 검색은 그 선택을 무효화할 수는 있어도 대체하지는 않는다.

이 구조를 통해 제작 시스템은 항상 현재 화면에 보이는 아이템 중 하나가 명확하게 선택되었는가라는 질문에만 의존하게 된다.

그 결과 검색, 슬롯 생성, 제작 패널 표시, 제작 실행까지의 흐름이 서로 얽히지 않고, 예측 가능한 상태 전이를 유지할 수 있다.

5. 개발 의도

이 게시글에서 다룬 구조의 핵심은 선택 상태를 하나의 값으로 수렴시키는 것이다.

UI 요소마다 기준을 따로 두는 대신, 제작 시스템 전체가 selectedIndex라는 단일 기준을 바라보도록 설계했다.

이 덕분에 슬롯 UI, 제작 패널, 재화 표시, 제작 버튼 로직이 서로 얽히지 않고도 자연스럽게 동기화된다.

이 구조를 기반으로 이후 제작 조건 검증과 실행 로직을 추가하더라도, 시스템의 중심인 선택 상태 기준은 흔들리지 않는다.