이번 시간에는 저번에 만들었던 상점 시스템 코드를 리펙토링 하는 시간을 가지겠다.
1. 상품의 원본 데이터 훼손 문제

이전 코드를 보면 Item(Scriptable) 데이터를 그대로 사용해서 상품 정보를 바꾸면 Item 정보도 그대로 바꿔서 2번째 게임 실행 시 초기화가 안되는 문제가 발생하였다.
// 구매하기
public void Purchase(Item item, int purchaseCount)
{
// ItemSlot에서 가져온 리스트 구매
item.count -= purchaseCount;
userMoney -= item.price * purchaseCount;
userMoneyTxt.text = userMoney.ToString();
// 유저의 인벤토리에 itme 추가
UpdateProductList();
}
이처럼 item.count -= purchaseCount에서 원본 데이터를 훼손하는 문제가 발생하였다.
따라서 이를 해결하기 위해 원본 데이터 정보를 훼손하지 않는 복사본 데이터를 만들게 되었다.
// 런타임 샵 항목(Asset(Item) 수정하지 않음)
private List<ShopItem> shopItems = new List<ShopItem>();
[System.Serializable]
public class ShopItem
{
public int id;
public Item itemDef;
public int stock;
public int price;
}
// 상품 리스트 초기화
public void InitShopItems()
{
// Resource/Items 폴더 안에 있는 아이템 정보를 불러와 리스트화
Item[] loaded = Resources.LoadAll<Item>("Items");
shopItems = new List<ShopItem>();
int id = 0;
foreach (var it in loaded)
{
shopItems.Add(new ShopItem
{
id = id++,
itemDef = it,
stock = it.stock, // 런타임 재고 복사
price = it.price
});
}
}
다음과 같이 Item 정보를 복사할 수 있는 ShopItem 래퍼 클래스를 추가하였다.
| 래퍼 클래스 (Wrapper Class) | 기본형 데이터 타입을 참조형 변수 형태로 사용하기 위해 구현한 클래스 |

게임 실행 동안 바뀔 수 있는 변수가 있으므로 원본 데이터는 그대로 둔 채 런타임 중 자유롭게 활용할 수 있는 복사본 데이터를 만들어 의도치 않은 영구 변경을 해결할 수 있다.
2. 함수 재사용 문제
// 상품 리스트 갱신
public void UpdateProductList()
{
// 범위 설정
int startIndex = (currentPage - 1) * 4;
int endIndex = startIndex + MAX_PRODUCTSLOT;
int slotIndex = 0;
// 4가지 아이템 슬롯을 생성 및 리스트 저장
for (int i = startIndex; i < endIndex; i++)
{
if (i >= productList.Count || productList[i] == null) break;
productSlotList[slotIndex].GetComponent<ProductSlot>().SlotUpdate(productList[i]);
productSlotList[slotIndex].SetActive(true);
slotIndex++;
}
// 빈 상품 슬롯 비활성화
for(int i=slotIndex; i < MAX_PRODUCTSLOT; i++)
{
productSlotList[i].SetActive(false);
}
// 상품 페이지 갱신
pageTxt.text = currentPage.ToString() + "/" + maxPage.ToString();
}
기존 코드에서는 한 함수에서 처리하는 기능이 많아 함수의 재사용 면에서 최적화가 안되는 문제가 발생하였다.
private void CreateSlotsIfNeeded()
{
for (int i = productSlotList.Count; i < maxProductSlots; i++)
{
var go = Instantiate(productSlotPrefab, listParent);
go.SetActive(false);
productSlotList.Add(go);
var slot = go.GetComponent<ProductSlot>();
// 콜백은 bool 반환(구매 성공 여부)
slot.OnBuyRequested = (itemId, qty) =>
{
var s = shopItems.FirstOrDefault(x => x.id == itemId);
return Purchase(s, qty); // Purchase는 bool 반환으로 구현되어 있어야 함
};
}
}
// 페이지 초기화
private void RefreshPagination()
{
maxPage = Mathf.Max(1, Mathf.CeilToInt(shopItems.Count / (float)maxProductSlots));
currentPage = Mathf.Clamp(currentPage, 1, maxPage);
productPageTxt.text = currentPage + "/" + maxPage;
}
CreateSlotsIfNeeded() 함수를 통해 Slot 오브젝트의 생성의 재사용을 늘려주고 페이지 초기화 기능도 함수로 따로 구현하였다.
또한, 델리게이트 변수를 사용하여 ShopManager 내에서 구매 기능을 담당하도록 코드를 바꾸었다.
| Func<T1, T2, ..., TResult> | 반환값이 있는 델리게이트 |
'프로그래밍 > Unity' 카테고리의 다른 글
| Unity 내장 메서드 비용(Cost) 문제 (0) | 2025.10.28 |
|---|---|
| Unity 장바구니 시스템 만들기 (0) | 2025.09.18 |
| Unity 상점 시스템 만들기 - (1) (0) | 2025.09.16 |
| Unity 탑 뷰 형태 캐릭터 움직임 구현 (0) | 2025.09.10 |
| Unity Analytics 사용하기 (0) | 2025.09.09 |