일단 재사용 스크롤뷰는 구현하지 않고 Character 데이터 하나마다 캐릭터 카드 객체 하나씩 새로 생성하는 방식을 썼다. 추후 재사용 스크롤뷰 적용할 예정.
동적 그리드 스크롤뷰 코드는 이 글을 참고해서 작성했다.
동적 스크롤뷰 관련 코드만 표시
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
public class CharacterListController : MonoBehaviour
{
public GameObject characterCard;
public GameObject gridLayout;
public Toggle toggle;
private HttpClient client = new HttpClient();
private List<Character> characters;
private void Start()
{
// 캐릭터 리스트 가져오기 (서버 없이 더미데이터를 넣음)
characters = client.FetchCharacters();
// 리스트 UI에 뿌리기
Populate(characters);
}
// UI에 리스트 뿌리는 기능
private void Populate(List<Character> list)
{
// 화면 갱신시 리스트가 계속 쌓이지 않도록 기존 객체들 없애기
foreach (Transform child in gridLayout.transform)
{
Destroy(child.gameObject);
}
// GridLayoutGroup 안에 캐릭터 카드 프리팹 넣고 데이터 바인딩
foreach (Character item in list)
{
GameObject go = Instantiate(characterCard, gridLayout.transform);
go.GetComponent<CharacterCard>().Bind(item);
}
}
}
Character 객체의 numOfStar 값에 따라 하단의 별 갯수를 다르게 표시해야 했는데, 이걸 어떻게 구현할지 좀 고민을 했다. 처음엔 스프라이트 없는 Image를 5칸 만들어 두고 numOfStar 값이 3이라면 Image 3칸에 별 스프라이트를 각각 넣는 방식으로 할까 하다가 생각을 바꿨다.
우선 Image 칸은 하나만 만들고, numOfStar 값에 따라 칸 길이만 바뀌게 한 뒤 별 스프라이트를 Tiled 타입으로 넣었다. 이렇게 하니 코딩도 훨씬 간결하고 메모리도 좀더 효율적으로 쓰게 되어 잘된 것 같다.
데이터와 UI를 바인딩하는 기능을 아이템 객체 안에다 넣었는데, 이건 안드로이드에서처럼 Adapter 클래스를 따로 만들어서 수행시키는 편이 재사용성 면에서 더 좋을 것 같다. 여유가 되면 만들어봐야겠다.
using UnityEngine;
using UnityEngine.UI;
using TMPro;
public class CharacterCard : MonoBehaviour
{
// 캐릭터 카드 상의 각 UI 요소들 참조
public TMP_Text nameText;
public Image portraitImage;
public Image rarityImage;
public Image jobIconImage;
public RectTransform starImageTransform;
public TMP_Text levelText;
public TMP_Text powerText;
// UI에 표시될 별 갯수 조절을 위한 별 1개의 길이
private int widthOfOneStar = 34;
// Character 객체의 필드들을 캐릭터 카드 UI에 뿌려주는 기능
// CharacterListController에서 호출
public void Bind(Character character)
{
nameText.text = character.Name;
levelText.text = character.Level.ToString();
portraitImage.sprite = Resources.Load<Sprite>(character.PortraitUrl);
jobIconImage.sprite = Resources.Load<Sprite>(character.Job.IconUrl);
// 별 갯수에 따라 별 스프라이트가 들어갈 Image 크기 조절
starImageTransform.sizeDelta =
new Vector2(widthOfOneStar * character.NumOfStar, starImageTransform.sizeDelta.y);
// 캐릭터 태생 레어도에 따라 카드 오른쪽 띠 색상 변경
switch (character.Rarity)
{
case 1:
rarityImage.color = Color.blue;
break;
case 2:
rarityImage.color = Color.green;
break;
case 3:
rarityImage.color = Color.yellow;
break;
}
// 캐릭터 스탯으로부터 전투력을 계산하여 UI에 표시
// 전투력 계산은 다른 곳에서도 쓸 기능이라 Utils 클래스에 전역메서드로 배치
int power = Utils.CalculatePower(character.MaxHp, character.Damage, character.Armor);
powerText.text = power.ToString();
}
}
화면 상단과 우측, 그리고 팝업창 안에 들어가는 버튼들은 Button이 아닌 Toggle로 구현했다. 버튼의 역할이 정렬(혹은 필터) 적용/해제 2가지 뿐이었기 때문이다. '즐겨찾기된 캐릭터만 표시' 토글 및 '소유중인 캐릭터만 표시' 토글은 체크박스 모양의 원본 그대로 썼지만, 다른 토글들은 체크박스를 없애고 On/Off시 아이콘이나 버튼 색상이 바뀌도록 스크립트로 이벤트를 작성했다.
using UnityEngine;
using UnityEngine.UI;
public class SortToggle : MonoBehaviour
{
public Image buttonImage;
public void ChangeIcon()
{
if (GetComponent<Toggle>().isOn)
buttonImage.sprite = Resources.Load<Sprite>("Icons/triangle-down");
else
buttonImage.sprite = Resources.Load<Sprite>("Icons/triangle-up");
}
}
using UnityEngine;
using UnityEngine.UI;
using TMPro;
public class FilterToggle : MonoBehaviour
{
public TMP_Text text;
public void ChangeColor()
{
if (GetComponent<Toggle>().isOn)
{
GetComponent<Image>().color = new Color32(97, 97, 97, 255);
text.color = Color.white;
}
else
{
GetComponent<Image>().color = Color.white;
text.color = Color.black;
}
}
}
출처: 고양일산체R
UGUI Text 대신 TextMeshPro를 써보았다. 프로젝트에 별도 리소스가 추가되고 폰트 아틀라스를 직접 만들어야 하는 등 귀찮음이 있었지만 UI를 확대해도 글자 해상도가 깔끔하게 유지되어 확실히 Text보다 괜찮은 것 같다. 참고글
출처: 무료 아이콘 제공 사이트 Flaticon
재사용할 일이 많아서 그냥 Resources에 넣었다.
출처: 구글 검색
원본 게임 리소스를 구할 수 있으면 그걸로 바꾸고 싶다...
일러스트와 포트레잇은 캐릭터가 많아지면 용량을 꽤 잡아먹을 것 같아 Resources에 올리기엔 부담이 되고, 에셋 번들을 사용해볼까 하고 있다.