1. 엑셀 파일 만든 후 .csv파일로 저장
2. 유니티의 Resource -> Data -> 지정한 폴더에 파일 넣기
3. 클릭해보면 인스펙터에 기본으로 쉼표 구분선으로 되어있을텐데
아래 블로그를 참고하여 구분 기호를 가장 다른곳에서 사용하지 않는 @로 지정 (인스펙터에 , 대신 @로 구분되어있는 것 확인 가능)
https://learnandcreate.tistory.com/560
4. 저장할 때의 파일 형식 주의
위의 형식을 따라야 한다. URF-8이 없는 .csv파일로만 저장했더니 아래처럼 한글이 이상하게 보이는 오류가 났었다. 보이는 것 뿐만 아니라 출력해도 저렇게 나온다
정상적일 때 :
DatabaseLoader 스크립트
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
public class DataLoader : MonoBehaviour
{
public static List<List<string>> Load(string link)
{
var list = new List<List<string>>();
string data_String = "";
StringReader sr1 = null;
StreamReader sr2 = null;
if (Application.platform == RuntimePlatform.Android)
{
//"Data/Language.csv"
TextAsset data = Resources.Load<TextAsset>(link);
sr1 = new StringReader(data.text);
}
else
{
sr2 = new StreamReader(Application.dataPath + "/Resources/" + link + ".csv");
}
bool endOfFile = false;
while (!endOfFile)
{
if (Application.platform == RuntimePlatform.Android)
{
data_String = sr1.ReadLine();
}
else
{
data_String = sr2.ReadLine();
}
if (data_String == null)
{
endOfFile = true;
break;
}
var data_values = data_String.Split('@'); //string, string타입
var tmp = new List<string>();
for (int i = 0; i < data_values.Length; i++)
tmp.Add(data_values[i]); //int, string으로 바뀜
list.Add(tmp);
}
return list;
}
}
CharacterDB 스크립트
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System;
using UnityEngine.AI;
public class CharacterDB
{
static List<Character> characterList= new List<Character>();
public static void Loading()
{
//normalMonsterList = LoadMonster("NormalMonsters");
characterList = LoadCharacter("Character");
}
//ID로 찾기
public static Character Find(string ID)
{
foreach (Character character in characterList)
{
if (character.ID.Equals(ID))
return character;
}
Debug.Log("Error_Unknown character......");
return null;
}
//index로 찾기
public static Character Find(int index)
{
if (index < 0 || index > characterList.Count - 1)
return null;
return characterList[index];
}
public static List<Character> LoadCharacter(string name)
{
List<Character> characters = new List<Character>();
List<List<string>> List = DataLoader.Load("Data/Character/" + name);
for (int i = 1; i < List.Count; i++)
{ //만약 던전 변경점(엑셀에서 추가적인 수치 들어갈 시,)이 있다면 추가된 엑셀 칸만큼 name_Dictconary, Descript_Dictconary의 [배열 + 추가된 칸] 만큼 해주면 됨.
string ID = List[i][0];
/* float slowresist = float.Parse(List[i][4]);*/
Dictionary<LangCode, string> nameDict = new Dictionary<LangCode, string>();
Dictionary<LangCode, string> explainDict = new Dictionary<LangCode, string>();
nameDict.Add(LangCode.Kor, (List[i][1]));
explainDict.Add(LangCode.Kor, (List[i][2]));
nameDict.Add(LangCode.Eng, (List[i][3]));
explainDict.Add(LangCode.Eng, (List[i][4]));
long cost = Util.ExtraExcelConvert(List[i][7]);
int Arti = int.Parse(List[i][8]);
//monsters.Add(new Monnster(ID, Hp, armor, moveSpeed, slowresist, nameDict, explainDict));
characters.Add(new Character(ID, nameDict, explainDict, cost, Arti));
}
return characters;
}
}
LoadCharacter()에서 엑셀을 불러와 필드에 해당하는 엑셀의 인덱스를 활용해 값을 가져와 저장하고, 그 필드들을 이용해
Character객체를 생성하여 characters 리스트에 넣는다. (for문으로 엑셀의 길이만큼 반복- 캐릭터1,2,3 즉 행이 3개라면 3번반복) 반복이 끝나면 characters 리스트 리턴.
그리고 Loading()함수에서 미리 선언한 characterList에 LoadCharacter()로 리턴받은 characters리스트를 그대로 넣어준다.
그러니 데이터베이스를 활용할땐 Awake()나 생성자나 메소드 뭐든 CharacterDataBase.Loading()을 불러오고 다뤄야 한다.
하지만 이렇게 짤 경우 Find()에서 깊은복사를 활용해 새로운 캐릭터 객체를 생성해서 전달하는게 아니라,
Find()로 찾아 생성한 모든 프리팹들이 characterList의 같은 요소(ex) character1) 를 참조하기 때문에,
(* 객체를 복사할 때 참조만 살짝 복사하는 얕은 복사의 상태)
이 캐릭터의 필드들이 변하면 모든 프리팹들의 필드가 변하는
깊은복사/ 얕은복사의 문제점이 있다. 이를 직접 깊은복사를 구현해 해결해야 하는데 일단 이건 아래서 설명하고
먼저 이 스크립트에서 반환하는 대상이 되는 Character스크립트부터 보자
Character는 MonoBEHAVIOUR를 상속받지 않는 틀이고,
Character객체를 활용해 유니티상에서 다루려면(프리팹 생성 등) 모노비헤이비어를 상속받는 CharacterPrefab스크립트를 따로 만들고
생성자에서 아래와 같이 받아오면 된다.
public class CharacterPrefab : MonoBehaviour
{
Character character;
public void Init(Character character)
{
this.character = character;
}
}
Character 스크립트
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Character
{
public string BackgroundLink => "Image/Character/CharacterBackground/" + ID;
//람다라 애초에 변경시킬수 없을것
public Character(string ID, Dictionary<LangCode, string> names, Dictionary<LangCode, string> explain, long cost, int mountableArti)
{
this.ID = ID;
NameDict = names;
ExplainDict = explain;
this.Cost = cost;
this.Arti = mountableArti;
}
public string ID { get; private set; }
public string Name { get { return NameDict[Language.LangCode]; } }
public string Explain { get { return ExplainDict[Language.LangCode]; } }
//BuiffList
public Dictionary<LangCode, string> NameDict { get; private set; }
public Dictionary<LangCode, string> ExplainDict { get; private set; }
public long Cost { get; private set; }
//public int Arti{get; private set;} 이게 나을라나
int mountableArti;
int maxArti = 5; //최대 5개
public int Arti //장착 가능 유물 개수
{
get { return mountableArti; }
private set
{
if (mountableArti < 1)
{
mountableArti = 1;
}
else if (mountableArti > maxArti)
{
mountableArti = maxArti;
}
}
}
}
DB에서 엑셀의 인덱스를 가져와 값을 할당해준 필드들이 이 Character생성자의 매개변수로 쓰인다.
활용 예시
다음은 캐릭터버튼을 눌렀을때, 캐릭터의 해당정보에 맞게 디스플레이 UI를 변경해주는 UpdateText(Character selectedCharacter) 를 호출하는 로직인데, UpdatText()의 매개변수인 Character에 Find()로 찾은 Character객체를 넘기는 예시이다.
DisplayController의 매개변수로는 Tanker가 들어간다
public void DisplayController(string command)
{
Character characters = CharacterDB.Find(command);
characterInfor.UpdateText(characters);
tempCharacter = characters;
}
CharacterDB에서 발생하는 깊은복사/얕은복사 문제
ChatGpt가 대답한 c#에서의 깊은복사 구현방법 :
C#에서는 ICloneable 인터페이스를 구현하여 객체를 복제할 수 있습니다. 또한, 일부 라이브러리에서는 Json.NET, AutoMapper 등을 사용하여 깊은 복사를 수행할 수 있습니다. 또한, 일부 개발자들은 직접 객체를 복제하는 메서드를 만들어 사용하기도 합니다.
지웅인 그럴필욘없고 직접하면 될 것 같다고 한다
저 characterList를 보면 Loading() 을 불러오는 시점에 저 리스트는 바로 메모리에 올라간다.그 이후부터 Find(Tanker)로 찾아 Instantiate로 생성한 모든 CharacterPrefab이 가리키는 것은 메모리에 올라가있는 같은 캐릭터 객체이다 (ID가 Tanker인.) 그래서 만약 이 캐릭터의 데미지를 깎아준다 하면, 모든 프리팹들의 값이 똑같이 깎인다. 이를 해결하기 위해 깊은복사를 직접 구현하는데 기존의 Find는 오로지 데이터베이스의 값을 건드리지 않는, Debug.Log(Find(Tanker).Name);처럼 단순히 값을 가져올 때만 사용하고, 위와 같은 문제가 발생할 나머지 경우에는 깊은복사가 적용된 메소드를 호출하도록 따로 Copy() 메소드를 구현해 놓는다. 그리고 앞으로 프리팹을 생성할 떈 Find가 아닌 Copy를 사용하면 깊은복사가 적용되는 것.
깊은복사의 방법은 단순히 Character스크립트에서 CopyCharacter()를 구현하고 ,
새로운 Character객체를 생성한 후 그것을 반환하는 것이다. 그러면 같은 characterList의 객체를 모두가 참조하지 않고, 새로 만들어진 각자의 캐릭터 객체를 사용하기 때문에 하나의 값이 변해도 다른 객체들의 값이 바뀌지 않는 깊은복사가 구현된다.
Character스크립트에 CopyCharacter() 구현 :
CharacterDB에 Copy() ( 버전 1: ID로 찾 버전 2: index로 찾 )구현:
주의할 점
유니티 Runtime 도중에 엑셀 파일이 열려있으면 데이터를 불러오지 못하는 오류발생
+
System네임스페이스에 ICloneable이라는 인터페이스가 있다. 엑셀에서 데이터를 가져오는 CharacterDB에는
Find()로 프리팹을 생성했을 경우 얕은복사의 오류가 있으므로 Copy()를 구현했다. 만약 UnitDB라는 같은방식의 유닛데이터를 가져오는 스크립트를 만들고자 할 때 Copy를 구현하지 않는다면 또다시 얕은복사의 오류가 발생할 확률이 높으므로
깊은복사 기능을 가질 클래스가 .NET의 다른 유틸리티 클래스나 다른 프로그래머가 작성한 코드와 호환되도록 하고 싶을 때 사용하는 ICloneable인터페이스를 상속받게 하여 Copy()를 구현하게 하면 좋을 듯 하다.
ICloneable은 Clone()메소드 하나만 가지고 있는데, Copy()를 Clone으로 바꾸고 구현을 강제하면 오류의 재발을 방지할 수 있을 듯 하다 책 239pg보고 생각한 내용