이전에 C#으로 Json데이터를 불러와서 Datamanager 스크립트를 작성해서 관리 하게 한게 있어요.
[C#] Json 데이터 불러와서 활용하기 :: 별빛상자 (tistory.com)
public sealed class DataManager
{
// 외부에서 접근 읽기 상태로만 접근할 수 있어 데이터 수정을 막고 데이터를 읽기만 할 수 있는 싱글톤 DataManager 선언
public static readonly DataManager instance = new DataManager();
// 딕셔너리로 데이터관리
private Dictionary<int, RawData> dicDatas = new Dictionary<int, RawData>();
private DataManager()
{
}
// json 데이터를 불러와서 딕셔너리에 저장하는 메소드
public void LoadData<T>(string path) where T : RawData
{
var json = File.ReadAllText(path);
T[] arr = JsonConvert.DeserializeObject<T[]>(json);
arr.ToDictionary(x => x.id).ToList().ForEach(x => dicDatas.Add(x.Key, x.Value));
}
// id 값으로 딕셔너리에서 검색하는 메소드
public T GetData<T>(int id) where T : RawData
{
var collection = this.dicDatas.Values.Where(x => x.GetType().Equals(typeof(T)));
return (T)collection.ToList().Find(x => x.id == id);
}
// 특정 데이터 그룹을 검색하고싶을대 해당 데이터 타입을 호출하면 해당 데이터타입 객체만 반환한다.
// ex(GetDatas<Student>()) = 딕셔너리에 저장된 데이터들중 Student타입을 가진 객체들만 반환
public IEnumerable<T> GetDatas<T>() where T : RawData
{
IEnumerable<RawData> col = this.dicDatas.Values.Where(x => x.GetType().Equals(typeof(T)));
return col.Select(x => (T)Convert.ChangeType(x, typeof(T)));
}
이때 당시에는 Datamanager에서 Json 데이터를 로드화는 과정에서 넣어야 하는 데이터의 타입과 Json데이터 파일 위치를 받아서 로드하는 과정을 가졌는데 이렇게 하면 데이터를 로드하는 과정이 아래처럼 나와요.
public class DataTest
{
public DataTest()
{
// json데이터를 불러와서 딕셔너리에 저장
DataManager.instance.LoadData<StudentData>("./student_data.json");
DataManager.instance.LoadData<StudyData>("./study_data.json");
DataManager.instance.LoadData<UserData>("./user_data.json");
}
}
이코드를 처음 사용할때는 문제점을 못느꼈는데 Unity에서 게임을 만들때 아래처럼 데이터가 로드되는 과정을 연출하고싶을때 문제가 되더라구요.
먼저 이런 연출을 하기 위해선 데이터파일의 총 갯수를 알아야 해요.
그래서 Datamanager에 데이터파일의 경로를 관리하는 리스트를 선언하고 데이터들의 경로가 담겨있는 데이터파일을 작성한다음 Init함수 에서 dataPathList에 데이터를 추가 해줬어요.
DatapathData.cs
public class DatapathData
{
public int id;
public string res_name;
public string type;
}
datapath_data.json
[
{
"id": 0,
"res_name": "basket_data",
"type": "BasketData"
},
{
"id": 1,
"res_name": "item_data",
"type": "ItemData"
},
{
"id": 2,
"res_name": "item1_data",
"type": "ItemData"
},
{
"id": 3,
"res_name": "item2_data",
"type": "ItemData"
},
{
"id": 4,
"res_name": "item3_data",
"type": "ItemData"
}
]
id는 중복값을 피하기 위함이고, res_name에는 데이터파일의 경로가 들어가요. type은 해당 json데이터들의 클래스를 적어줘요.
private IEnumerator LoadAllDataRoutine()
{
int idx = 0;
foreach (var data in this.dataPathList)
{
var path = string.Format("Datas/{0}", data.res_name);
ResourceRequest req = Resources.LoadAsync<TextAsset>(path);
yield return req;
float progress = (float)(idx + 1) / this.dataPathList.Count;
TextAsset asset = (TextAsset)req.asset;
LoadData<DataType>(asset.text);
yield return new WaitForSeconds(0.3f);
idx++;
}
yield return null;
}
자 이제 위에서 Init 함수를 정상적으로 작동 했다면 dataPathList에는 json 파일들의 경로가 저장되어 있어요.
리소스폴더 경로 + json파일의 경로를 해서 데이터의 Full 경로를 가져왔다면 이 경로로 Resource.LoadAsync<TextAsset>(풀경로)를 이용해서 ResourceRequest 데이터를 받아올수 있어요.
ResourceRequest.asset은 바로 Newtonsoft의 JsonConvert로 역직렬화를 할수 없기 때문에 string형식을 지원하는 TextAsset.text를 사용하기 위해서 TextAsset형식으로 바꿔준다음 LoadData에 Json문자열을 넣고 를 호출해주면 되요
근데 이때 기존 LoadData를 사용하면 문제가 발생해요.
// json 데이터를 불러와서 딕셔너리에 저장하는 메소드
public void LoadData<T>(string json) where T : RawData
{
var datas = JsonConvert.DeserializeObject<T[]>(json);
datas.ToDictionary(x => x.id).ToList().ForEach(x => dicDatas.Add(x.Key, x.Value));
}
Json 문자열을 역직렬화한후에 딕셔너리에 넣는 과정에 데이터 타입을 알아야해서 <T> 동적으로 데이터타입을 받아야하는데 지금 코드로는 LoadData를 호출할때 데이터타입을 넣어줄수가 없어요. 동적으로 데이터타입을 받는 방법은 없을까? 알아보던중 해결방법이 담긴 링크를 찾았는데 자세히 이해는 못해서 링크만 남겨요.
c# - Using a string value as a generic type parameter - Stack Overflow
직역 하면 문자열 값을 제네릭 형식 매개 변수로 사용하는 방법에 대한 방법이에요.
그래서 LoadAllDataRoutine를 조금 수정 해줬어요.
private IEnumerator LoadAllDataRoutine()
{
int idx = 0;
foreach (var data in this.dataPathList)
{
var path = string.Format("Datas/{0}", data.res_name);
ResourceRequest req = Resources.LoadAsync<TextAsset>(path);
yield return req;
float progress = (float)(idx + 1) / this.dataPathList.Count;
this.onDataLoadComplete.Invoke(data.res_name, progress);
TextAsset asset = (TextAsset)req.asset;
var typeDef = Type.GetType(data.type);
this.GetType().GetMethod(nameof(LoadData))
.MakeGenericMethod(typeDef).Invoke(this, new string[] { asset.text });
idx++;
}
yield return null;
this.onDataLoadFinished.Invoke();
}
여기서 봐야하는부분이 LoadData를 호출하는 방법이 바꼈는데 Json 파일 데이터 경로를 넘기는 방법에서 직접 Json문자열을 넘기고 이때 데이터타입을 넘겨 줘야하는데 기존에는
public class DataTest
{
public DataTest()
{
// json데이터를 불러와서 딕셔너리에 저장
DataManager.instance.LoadData<StudentData>("./student_data.json");
DataManager.instance.LoadData<StudyData>("./study_data.json");
DataManager.instance.LoadData<UserData>("./user_data.json");
}
}
이런식으로 데이터 타입을 넘겨주었지만 이방법을 지금은 쓸수 없으니
var typeDef = Type.GetType(data.type);
this.GetType().GetMethod(nameof(LoadData))
.MakeGenericMethod(typeDef).Invoke(this, new string[] { asset.text });
데이터타입을 문자열로 먼저 저장해놓은 다음 Type.GetType으로 데이터타입을 받아온다음에 해당형식을 써주면
public void LoadData<T>(string json) where T : RawData
{
var datas = JsonConvert.DeserializeObject<T[]>(json);
datas.ToDictionary(x => x.id).ToList().ForEach(x => dicDatas.Add(x.Key, x.Value));
}
이렇게 데이터타입을 받을수 있게 됬어요.
이때 고민점은 DataManager가 아닌 외부에서는
public void LoadAllData()
{
App.instance.StartCoroutine(LoadAllDataRoutine());
}
이런식으로 LoadAllData 메소드를 통해서 LoadLoadAllDataRoutine 코루틴만 실행하면 정상적으로 모든데이터를 불러올수있어서 사실상 LoadData<T>를 외부에서 호출할 이유는 없어요. 그래서 private 으로 잠굴려고 했으나
외부에서는 처음에 Json데이터들의 타입과 주소를 저장하는 InIt메소드와 LoadAllData메소드만 호출함에도 불구하고 에러가 나오고 있는데 해결방법이 있으신분은 알려주세요.
DataManager.cs
using Newtonsoft.Json;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Events;
using System;
public class DataManager
{
public UnityEvent<string, float> onDataLoadComplete = new UnityEvent<string, float>();
public UnityEvent onDataLoadFinished = new UnityEvent();
public static readonly DataManager instance = new DataManager();
private Dictionary<int, RawData> dicDatas = new Dictionary<int, RawData>();
private List<DatapathData> dataPathList;
public void Init()
{
var datapath = "Datas/datapath_data";
var asset = Resources.Load<TextAsset>(datapath);
var json = asset.text;
var datas = JsonConvert.DeserializeObject<DatapathData[]>(json);
this.dataPathList = datas.ToList();
}
public void LoadData<T>(string json) where T : RawData
{
var datas = JsonConvert.DeserializeObject<T[]>(json);
datas.ToDictionary(x => x.id).ToList().ForEach(x => dicDatas.Add(x.Key, x.Value));
}
public void LoadAllData()
{
App.instance.StartCoroutine(LoadAllDataRoutine());
}
private IEnumerator LoadAllDataRoutine()
{
int idx = 0;
foreach (var data in this.dataPathList)
{
var path = string.Format("Datas/{0}", data.res_name);
ResourceRequest req = Resources.LoadAsync<TextAsset>(path);
yield return req;
float progress = (float)(idx + 1) / this.dataPathList.Count;
this.onDataLoadComplete.Invoke(data.res_name, progress);
TextAsset asset = (TextAsset)req.asset;
Debug.Log(req.asset);
var typeDef = Type.GetType(data.type);
this.GetType().GetMethod(nameof(LoadData))
.MakeGenericMethod(typeDef).Invoke(this, new string[] { asset.text });
yield return new WaitForSeconds(0.3f);
idx++;
}
yield return null;
this.onDataLoadFinished.Invoke();
}
// id 값으로 딕셔너리에서 검색하는 메소드
public T GetData<T>(int id) where T : RawData
{
var collection = this.dicDatas.Values.Where(x => x.GetType().Equals(typeof(T)));
return (T)collection.ToList().Find(x => x.id == id);
}
// 특정 데이터 그룹을 검색하고싶을대 해당 데이터 타입을 호출하면 해당 데이터타입 객체만 반환한다.
// ex(GetDatas<Student>()) = 딕셔너리에 저장된 데이터들중 Student타입을 가진 객체들만 반환
public IEnumerable<T> GetDataList<T>() where T : RawData
{
IEnumerable<RawData> col = this.dicDatas.Values.Where(x => x.GetType().Equals(typeof(T)));
return col.Select(x => (T)Convert.ChangeType(x, typeof(T)));
}
}
UnityEvent를 이용해서 데이터가 로드될때마다 이벤트를 호출하여 현재진행상황을 알려주어서 로딩씬 연출을 했어요.
지금은 데이터가 너무 적어서 너무 빠르게 진행되서 올라가는게 잘안보여서 중간에
yield return new WaitForSeconds(0.3f); 를써서 올라가는게 눈으로 보이게 테스트를 한 모습이에요.
감사합니다.
'[개인공부] > Unity' 카테고리의 다른 글
[Unity 2D] error CS0122: 'EBrushMenuItemOrder' is inaccessible due to its protection level (0) | 2022.08.11 |
---|---|
[Unity 2D] 캐릭터 마우스 이동 및 체력바 구현하기 (0) | 2022.08.08 |
[Unity UI] 스킬버튼 구현하기 (4) | 2022.08.07 |
[Unity 2D] Player 총알 발사 (0) | 2022.07.11 |
[Unity 2D] Player 이동 (0) | 2022.07.11 |