- UI는 계층적으로 만들어야한다. → 재사용이 가능하면 좋다.
- 먼저 인벤토리를 한다면 슬롯 하나를 만들고 함수를 만들고 테스트를 하고 된다면 그 다음으로 넘어가야한다.
- 비어있는 상태
- 아이템이 들어있는 상태
- 클릭을 했을때의 경우
- 정보 표시
- 클릭을 한 다음 삭제버튼을 누르면 삭제되는 경우
아이템 슬롯
- 슬롯 프리펩 생성 - 빈상태 함수, 아이템 있는 상태 함수 틀 선언
- 지금은 아이템 정보를 한번에 받지만, 원래는 개별적인 아이템 정보를 받아야한다.
- 언제 획득했냐, 랜덤으로 능력치가 붙는게 있지 않나 같은 정보를 넣어야한다.
- 빈상태 함수 : 아이템 데이터, 이미지, 이름 텍스트 비우기
- 슬롯 채우기 함수 ; 아이템 데이터, 이미지, 이름 텍스트 채우기 → 아이템 데이터로 할당한다.
//슬롯 비우기
public void SetEmpty()
{
ItemData = null;
imageIcon.sprite = null; //null이면 아무것도 안그려진다.
textName.text = string.Empty;
}
//슬롯 채우기
public void SetItem(SaveItemData data)
{
ItemData = data;
imageIcon.sprite = ItemData.itemData.SpriteIcon;
textName.text = ItemData.itemData.StringName;
}
스크롤 뷰
- Viewport안의 Content에 슬롯같은 요소들이 들어간다.
- 아무리 많아도 viewport의 영역만큼만 보이게 된다.
- 스크립트에서 content 오브젝트의 참조하기 위해선 ScrollView 클래스가 아니라 ScrollRect 클래스로 객체를 선언한 다음에 content 프로퍼티를 사용한다.
public ScrollRect scrollRect;
public void AddRandomItem()
{
var itemData = DataTableManager.ItemTable.GetRandom();
var newSlot = Instantiate(prefab, scrollRect.content); //UI객체를 생성할때는 부모 오브젝트 트랜스폼이 중요하다. / 부모 오브젝트 설정
newSlot.SetItem(itemData);
}
content 레이아웃 배치 제한
- Add Component로 레이아웃 제한이 가능하다.
- Grid Layout Group : content안에 요소를 좌우 몇개까지 배치가능한지 정하는 컴포넌트다.
- Content Size Fitter : 레이아웃 요소의 크기를 제어하는 레이아웃 컨트롤러의 기능이다.
- Unconstrained : 레이아웃 요소에 기반하여 높이를 조정하지 않는다.
- Min Size : 최소 높이에 기반하여 높이를 조정한다.
- Preferred Size : 기본 높이에 기반하여 높이를 조정한다.
스크롤 뷰 드래그 제한
- Scroll View - Scroll Rect - Horizontal 또는 Vertical : 상하 또는 좌우 드래그 제한
슬롯 데이터 저장
- 고유한 아이디는 중복되면 안된다.
- 서버가 있는게임은 아이디를 서버가 줘서 받아서 사용하면 된다.
- 고유 아이디 아이디어
- 흘러가는 시간 → 가장 속 편한 방법
- 파일로 숫자 지정 → 증가할때마다 1씩 증가
- 전제 : 서버가 없고 클라이언트만 존재할때 이 파일이 제대로 보존되어야한다.
슬롯 변경 시 업데이트
private void UpdateSlots(List<SaveItemData> itemList)
{
if (slotList.Count < itemList.Count)
{
for (int i = slotList.Count; i < itemList.Count; ++i)
{
var newSlot = Instantiate(prefab, scrollRect.content); //UI객체를 생성할때는 부모 오브젝트 트랜스폼이 중요하다. / 부모 오브젝트 설정
newSlot.slotIndex = i; //슬롯 번호
newSlot.SetEmpty();
newSlot.gameObject.SetActive(false);
slotList.Add(newSlot);
}
}
for (int i = 0; i < slotList.Count; ++i)
{
if (i < itemList.Count)
{
slotList[i].gameObject.SetActive(true);
slotList[i].SetItem(itemList[i]);
}
else
{
slotList[i].SetEmpty();
slotList[i].gameObject.SetActive(false);
}
}
}
public void AddRandomItem()
{
//var itemData = DataTableManager.ItemTable.GetRandom();
//testItemList.Add(itemData);
var itemInstance = new SaveItemData();
itemInstance.itemData = DataTableManager.ItemTable.GetRandom();
testItemList.Add(itemInstance);
UpdateSlots(testItemList);
}
public void RemoveItem(int slotIndex)
{
testItemList.RemoveAt(slotIndex);
UpdateSlots(testItemList);
}
정리
- ItemData : ItemTable에 포함되어있다.
- ItemTable은 실제 빌드할때 이미 생성되어있다.
- SaveItemData : 세이브 로드할때 세이브 파일에 포함될 데이터다.
- 아이템을 얻는 순간에 하나씩 만들어진다.
- 얘네끼리도 서로 구분이 가능해야한다. → ID가 중복되야하기 때문이다.
- 즉, 각 객체의 고유 id가 필요하기에 Guid(또는 DateTime)가 필요한 것이다.
- 차이점 : ItemData → 각 아이템의 정보다. / SaveItemData : 사용해야할 실제 아이템 정보
- 사전의 Id가 세이브 아이템 정보와 공유하고있다.
- 즉, ItemData의 Id는 해당 아이템의 id이고, SaveItemData의 Id는 해당 아이템의 고유 Id이다.
[Serializable]
public class SaveItemData
{
public Guid instanceId; //고유 아이디 클래스를 사용할때 / 뉴튼 소프트는 이게 자동으로 직렬 역직렬이 되는 기능이 있다.
[JsonConverter(typeof(ItemDataConverter))] //뉴튼Josn의 제이슨 컨버터 어트리뷰트다.
public ItemData itemData; //직렬화하기 위해서 JsonConveter를 만들어야한다.
public DateTime creationTime; //시간으로 아이디를 만들때
public SaveItemData()
{
instanceId = Guid.NewGuid(); //새로운 고유 아이디 반환 함수
creationTime = DateTime.Now; //현재시간
}
}
세이브 로드
public void Save()
{
var jsontext = JsonConvert.SerializeObject(testItemList);
var filePath = Path.Combine(Application.persistentDataPath, "test.json");
File.WriteAllText(filePath, jsontext);
}
public void Load()
{
var filePath = Path.Combine(Application.persistentDataPath, "test.json");
if (!File.Exists(filePath))
{
return;
}
var jsonText = File.ReadAllText(filePath);
testItemList = JsonConvert.DeserializeObject<List<SaveItemData>>(jsonText);
UpdateSlots(testItemList);
}
저장 후 종료 및 불러오기
private void OnEnable()
{
Load();
}
private void OnDisable()
{
Save();
}
인벤토리 정렬
- 정렬없고 필터링 없는 상태, 정렬 상태, 필터 상태 3개가 필요하다.
public readonly System.Comparison<SaveItemData>[] comparisons =
{
(lhs, rhs) => lhs.creationTime.CompareTo(rhs.creationTime),
(lhs, rhs) => lhs.creationTime.CompareTo(lhs.creationTime),
(lhs, rhs) => lhs.itemData.StringName.CompareTo(rhs.itemData.StringName),
(lhs, rhs) => lhs.itemData.StringName.CompareTo(lhs.itemData.StringName),
(lhs, rhs) => lhs.itemData.Cost.CompareTo(rhs.itemData.Cost),
(lhs, rhs) => lhs.itemData.Cost.CompareTo(lhs.itemData.Cost),
}; //정렬
private SortingOptions sorting = SortingOptions.NameAccending; //정렬 비교 객체
public SortingOptions Sorting
{
get => sorting;
set
{
//1. 필터링된 리스트 가져오기 / 2. 정렬 실행 / 3. 슬롯 업데이트
sorting = value;
UpdateSlots(testItemList);
}
}
인벤토리 필터링
public readonly System.Func<SaveItemData, bool>[] filterings =
{
x => true,
x => x.itemData.Type == ItemTypes.Weapon,
x => x.itemData.Type == ItemTypes.Equip,
x => x.itemData.Type == ItemTypes.Consumable,
}; //필터링
private FilteringOptions filtering = FilteringOptions.None; //필터링 비교 객체
public FilteringOptions Filtering
{
get => filtering;
set
{
filtering = value;
UpdateSlots(testItemList);
}
}
드롭다운
- 정렬 또는 필터링같은 특정 항목에 따라서 기능이 달라지는 상황에 사용한다.
- OnChanged 항목에 함수를 연결하면 드롭다운에서 선택한 항목의 index가 함수의 매개변수로 전달된다.
public void OnChangeSorting(int index)
{
slotList.Sorting = (UiInvenSlotList.SortingOptions)index;
}
public void OnChangeFiltering(int index)
{
slotList.Filtering = (UiInvenSlotList.FilteringOptions)index;
}