카드 정보 UI (Dictionary 활용)
지금 프로젝트에서 만들고 있는 게임은 덱 빌딩 기반의 게임으로, 게임 내에서 등장하는 여러 종류의 카드들을 한 번에 보여주는, "카드 도감" 이 필요하다는 말과 함께 카드 도감 기획서를 기획자에게 받아서 제작에 들어갔다.
카드의 종류가 메인 캐릭터 3명에 따라 다르며, 카드의 종류가 많고, 한 번에 접근해야 할 정보의 양이 많다 보니 list보다는 읽기 속도가 빠른 dictionary로 정보를 저장하고 읽는 게 좋다고 정하고 작업을 시작했다.
먼저 카드의 데이터는 CardModel 클래스로 CardSystemInitializer에서 처음 데이터를 다 로드하고 들고 있을 수 있도록 카드 데이터를 담당하는 팀원이 만들어 두었기에, 나는 도감 오브젝트가 처음 생성될 때 해당 데이터를 3종류의 딕셔너리로 분류해서 저장할 수 있는 함수를 제작했다.
Dictionary<int, CardModel> cardForShopia = new();
Dictionary<int, CardModel> cardForKayla = new();
Dictionary<int, CardModel> cardForLeon = new();
public void InitDictionary()
{
List<CardModel> allcards = CardSystemInitializer.Instance.loadedCards;
foreach (var card in allcards)
{
if (card.characterClass == CharacterClass.Sophia)
{
cardForShopia.Add(card.index, card);
}
else if (card.characterClass == CharacterClass.Kayla)
{
cardForKayla.Add(card.index, card);
}
else if (card.characterClass == CharacterClass.Leon)
{
cardForLeon.Add(card.index, card);
}
}
}
카드의 기본 정렬순은 카드의 ID 값으로 하고자 하였기에, 딕셔너리의 key값은 카드의 index (id 값)으로 지정해 주었다.
이후 각 페이지별 보여줘야할 카드의 정보를 카드의 해금 여부와 함께 업데이트해 주는 함수를 제작해 주었다.
여기서 bookCards 변수는 List<BookCards> 로 도감 내에서 보여줄 정보를 제외한 빈 카드 형식의 오브젝트를 들고 있다.
void UpdateCard(int i)// 해당 페이지(int i)의 카드의 갯수에 따라 활성화 비활성화 여부 설정 , 카드 해금 여부에 따른 정보 표시 유무.
{
if (cards == null) return;
// 전체 카드 한번 데이터 리셋
foreach (var card in bookCards)
{
card.SetCardInfo(null);
card.gameObject.SetActive(false);
}
// 카드의 갯수에 따라 빈 카드 칸 활성화 비활성화 여부 설정
int startIndex = i * 8;
int endIndex = Mathf.Min(startIndex + 8, cards.Count);
for (int j = startIndex; j < endIndex; j++)
{
bookCards[j].gameObject.SetActive(true);
bookCards[j].SetCardInfo(cards[j]);
}
// 카드 해금 여부에 따른 정보 표시 유무
for (int j = startIndex; j < endIndex; j++)
{
int slotIndex = j - startIndex; // 슬롯 인덱스
if (!cards[j].isUnlocked) // 카드가 아직 해금되지 않았다면.
{
bookCards[slotIndex].SetCardInfo(null);// 카드에 null 값 대입.
}
else
{
bookCards[slotIndex].SetCardInfo(cards[j]);// 카드에 카드 정보 대입.
}
}
}
위 메서드를 활용하면, 페이지를 넘길 때마다 해당 페이지에 맞는 정보를 업데이트해 줄 수 있다.
다음은 3개의 캐릭터별 카테고리로 나누어진 카드 도감에서 각 카테고리를 고를 때 일치하는 딕셔너리에 따른 정보를 넣어주는 메서드를 만들어주고, 위에서 제작한 UpdateCard(int i) 메서드를 사용해 첫 페이지의 정보까지 업데이트해 주었다.
public void CardsSet(int index)
{
switch (index)
{
case 0:// 소피아 카드 페이지
// 페이지 업데이트
maxPageCount = Mathf.CeilToInt(cardForShopia.Count / 8f);
currentPage = 0;
// 페이지내 카드 정보 업데이트 (카드의 정보, 카드 보유 유무)
cards = SortByIndex(cardForShopia);// 초기 카드 설정 (인덱스 순서 정렬)
UpdateCard(0);// 페이지에 맞는 카드 정보 세팅.
UpdateArrow(); // 화살표 업데이트
break;
case 1:// 카일라 카드 페이지
maxPageCount = Mathf.CeilToInt(cardForKayla.Count / 8f);
currentPage = 0;
cards = SortByIndex(cardForKayla);
UpdateCard(0);// 페이지에 맞는 카드 정보 세팅.
UpdateArrow(); // 화살표 업데이트
break;
case 2:// 레온 카드 페이지
maxPageCount = Mathf.CeilToInt(cardForLeon.Count / 8f);
currentPage = 0;
cards = SortByIndex(cardForLeon);
UpdateCard(0);// 페이지에 맞는 카드 정보 세팅.
UpdateArrow(); // 화살표 업데이트
break;
default:
Debug.LogError("Invalid index for CardsSet: " + index);
break;
}
}
이어지는 기획서 내용에는 카드 도감에 보일 카드의 개수가 많기 때문에 원하는 카드의 정보를 찾는데 조금 수월할 수 있도록 정렬 기능이 필요하다고 기획서에 적혀 있었고, 이를 위한 각각 코스트, 타입 별 정순, 역순 정렬 기능을 넣어주고자 했다.
정렬 기능은 크게 기술을 요구하지 않고 sort , orderby의 방식만을 써도 구현이 가능하기에 아래에 간단한 코드만 넣어두겠다.
public void ClickToSortByType() // 카드 데이터 정렬 (카드 타입 순)
{
List<CardModel> sortedCards = new List<CardModel>(cards); // 이미 cards 로 정렬된 카드 리스트를 사용.
if(sortType == SortType.TypeUp) // 이미 타입 정렬이 적용 중인 경우 역순
{
sortType = SortType.TypeDown; // 타입 역순 정렬.
sortTypeText.text = sortTypeTextString + " \u25BC"; // 아래 화살표 표시
sortCostText.text = sortCostTextString; // 코스트 정렬 텍스트 초기화.
sortedCards.Sort((card1, card2) => card2.type.CompareTo(card1.type)); // 카드 타입으로 역순 정렬
}
else
{
sortType = SortType.TypeUp; // 타입 정렬.
sortTypeText.text = sortTypeTextString + " \u25B2"; // 위 화살표 표시
sortCostText.text = sortCostTextString; // 코스트 정렬 텍스트 초기화.
sortedCards.Sort((card1, card2) => card1.type.CompareTo(card2.type)); // 카드 타입으로 정렬
}
cards = sortedCards; // 정렬된 카드 리스트로 업데이트
UpdateCard(currentPage); // 페이지 업데이트
}
public void ClickToSortByCost() // 카드 데이터 정렬 (카드 코스트 순)
{
List<CardModel> sortedCards = new List<CardModel>(cards); // 이미 cards 로 정렬된 카드 리스트를 사용.
if(sortType == SortType.CostUp) // 이미 기본 코스트 정렬이 적용 중인 경우 역순
{
sortType = SortType.CostDown; // 코스트 역순 정렬.
sortCostText.text = sortCostTextString + " \u25BC"; // 아래 화살표 표시
sortTypeText.text = sortTypeTextString; // 타입 정렬 텍스트 초기화.
sortedCards.Sort((card1, card2) => card2.manaCost.CompareTo(card1.manaCost)); // 카드 코스트로 역순 정렬
}
else
{
sortType = SortType.CostUp; // 코스트 정렬.
sortCostText.text = sortCostTextString + " \u25B2"; // 위 화살표 표시
sortTypeText.text = sortTypeTextString; // 타입 정렬 텍스트 초기화.
sortedCards.Sort((card1, card2) => card1.manaCost.CompareTo(card2.manaCost)); // 카드 코스트로 정렬
}
cards = sortedCards; // 정렬된 카드 리스트로 업데이트
UpdateCard(currentPage); // 페이지 업데이트
}
메서드를 public으로 설정해서, 도감 상단의 정렬 버튼을 눌러 해당 메서드에 접근 가능하도록 하였다.
기능을 다 구현하고 나니 굳이 dictionary로 저장한 각 캐릭터별 카드 데이터를, 보일 카드의 list로 한번 거쳐 sort 정렬이나 매개 변수로 쓸 이유가 있었는지에 대한 생각을 하게 되었다.
List로 쓰는 sort의 경우 orderby를 써도 동일하게 사용가능한 정렬 방식이고, 이렇게 중간 과정을 거치게 된다면 dictionary를 쓰는 이유가 퇴색되기 때문이다.
List와 Dictionary는 서로 장단이 명확하고 단순히 데이터를 불러오는 것이 아니라 데이터를 쪼개고, 순서대로 불러온다는 부분이 있기 때문에, 읽기 속도를 다소 감안하더라도 그냥 List로 통일하는 게 나을지도 모른다는 생각이 들었다.
일단 기능 구현 및 게임의 한 사이클을 돌리는 게 더 중요하다고 생각하였기에, 나중에 중간 단계를 지나 어느 정도 뼈대가 잡히게 되면 리팩토링을 거쳐서 처음 데이터를 받아온 각 Dictionary 또는 List 만을 활용하는 구조로 바꿔야겠다.