1. 엑셀 파일 만든 후 .csv파일로 저장

2. 유니티의 Resource -> Data -> 지정한 폴더에 파일 넣기
3. 클릭해보면 인스펙터에 기본으로 쉼표 구분선으로 되어있을텐데
아래 블로그를 참고하여 구분 기호를 가장 다른곳에서 사용하지 않는 @로 지정 (인스펙터에 , 대신 @로 구분되어있는 것 확인 가능)
https://learnandcreate.tistory.com/560
엑셀에서 저장한 csv의 구분기호 변경하기
엑셀에서 저장한 csv의 구분기호 변경하는방법 csv는 쉼표(comma)를 기준으로 항목을 구분하여 저장한 데이터 형식이다. 기본적으로 쉼표로 설정되어있는 구분 문자(delimiter)를 사용자 지정할수있
learnandcreate.tistory.com

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보고 생각한 내용
1. 엑셀 파일 만든 후 .csv파일로 저장

2. 유니티의 Resource -> Data -> 지정한 폴더에 파일 넣기
3. 클릭해보면 인스펙터에 기본으로 쉼표 구분선으로 되어있을텐데
아래 블로그를 참고하여 구분 기호를 가장 다른곳에서 사용하지 않는 @로 지정 (인스펙터에 , 대신 @로 구분되어있는 것 확인 가능)
https://learnandcreate.tistory.com/560
엑셀에서 저장한 csv의 구분기호 변경하기
엑셀에서 저장한 csv의 구분기호 변경하는방법 csv는 쉼표(comma)를 기준으로 항목을 구분하여 저장한 데이터 형식이다. 기본적으로 쉼표로 설정되어있는 구분 문자(delimiter)를 사용자 지정할수있
learnandcreate.tistory.com

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보고 생각한 내용