-
[TIL] C# (얇은 복사 깊은 복사, Json 파일 이용하여 저장하기)TIL 2024. 5. 1. 22:46
1. 얉은 복사 깊은 복사
class에 몬스터를 정의하고 던전에 몬스터를 추가해서 스폰할 몬스터를 정의했을 때 이상한 오류를 발견했다.
- 1. 같은 종류의 몬스터가 있으면 피가 2번단다.
- 2. 던전을 끝내고서도 몬스터의 피가 달아있다.
처음에는 전투중에 뭔가 잘못해서 그런줄 알았지만. 알고보니 던전에서 사용할 몬스터를 생성하는 함수에 문제가 있다는걸 알아차렸다.
문제의 스크립트
더보기public Monster[] GenerateMonster()//몬스터 생성 { Random random = new Random(); int monsterCount = random.Next(1, 5); Monster[] battleMonster = new Monster[monsterCount]; for (int i = 0; i < monsterCount; i++)//배열에 몬스터 추가 { battleMonster[i] = monster[random.Next(Dungeonlevel - 1, Dungeonlevel + 1)]; } return battleMonster; }
값형과 참조형
예전에도 강의에서 들었지만 C#에 참조형과 값형이 있다
값형(Value Type): int, bool, float 등
참조형(Reference Type): class, interface, arr 등
값형은 복사본을 전달한다. (깊은 복사)
더보기int a= 10 int b= a; b=20; Console.WriteLine(a); //10 Console.WriteLine(b); //20
a에 10을 선언하고
b에 a를 대입했을 때
a와 b는 각각의 값을 가지게 된다.
참조형은 원본의 주소값을 전달한다.(얇은 복사)
더보기List <int> test1 = new Test<int> (); List <int> test2= Test1; Test1[0].Add(10); Test2[0].Add(20); Console.WriteLine(test1[0]); //20 Console.WriteLine(test2[0]); //20
리스트로 test1 을 선언하고
test2에 test1 을 선언했을때
각가의 리스트에 다른 값을 넣어도 마지막에 넣은 값이 출력되는것이 보인다.
내가 생성한 battleMonster 배열에 monster 리스트 값을 넣었기 때문에 (둘다 참조형식이다.)
값이 복사된게 아니라 주소 값이 복사 되었기에 (얇은 복사) 이런 문제가 발생한 것이다.
(정보만 복사해야하는데 몬스터 데이터 베이스에 연결되어 있었던 것)
그렇다면 깊은복사를 해주면 되는 문제인데
어떻게 해줘야할까?
인터넷에 검색했을때 나오는 방법이 있다.
List
List<int> test1 = new List<int>() {1,2,3,4,5,}
List<int> test2 = new List<int>(test1);
List<int> test2 = test1.ToList();
Arr
int[] test1 = {1,2,3,4,5};
int[] test2 = (int[]) test1.Clone();
test2 .CopyTo( test1 , 복사를 시작할 값 위치);
이것 말고 여러가지 방법이 있다.
중요한건 내가 직면한 문제에는 둘다 큰 도움이 안되었다.
결국 그냥 튜터님한테 물어보기로 결정했고 알려주신 2가지 방법 중 하나로 해결되었다.
MemberwiseClone();
Object의 단순 복사본을 만든다고 설명되어있다고 정의 되어있다.(사실 이 말은 설명들어도 감이 안잡힌다,)
https://learn.microsoft.com/ko-kr/dotnet/api/system.object.memberwiseclone?view=net-7.0
적용한 코드
더보기//Monster.cs public Monster ReturnDeepCopy() { return (Monster)this.MemberwiseClone(); }
//Dungeon.cs public Monster[] GenerateMonster()//몬스터 생성 { Random random = new Random(); int monsterCount = random.Next(1, 5); Monster[] battleMonster = new Monster[monsterCount]; for (int i = 0; i < monsterCount; i++)//배열에 몬스터 추가 { battleMonster[i] = monster[random.Next(Dungeonlevel - 1, Dungeonlevel + 1)].ReturnDeepCopy(); } return battleMonster; }
MemberwiseClone()을 쓸 때 주의해야할 점이 있다.
1. 이 함수 자체는 얇은 복사를 반환한다.
2. 따라서 사용한 class 안에 참조형인 변수가 있다면, 사용하더라도 내 상황에서 효과가 없었을 것이다.
3. 다만 이 함수를 사용한 class가 현재는 값형 변수들만 선언되어 있기 때문에 깊은 복사가 이루어져서 문제를 해결했다.
함수자체를 완벽하게 이해하지는 못했지만 어떻게 활용할지는 감을 잡은것 같다.
2. 파일생성 폴더 생성게임에 저장하는 기능을 만들어 봤다.
저장을 Json파일을 이용할 거기 때문에 가장 먼저 프로젝트에 외부 라이브러리를 다운받아야한다.
도구 - NuGet 패키지 관리자 ->솔루션용 NuGet패키지 관리 -> 찾아보기에서 Json 입력 -> 프로젝트 선택 후 설치
설치후 프로젝트에 두가지 using 문을 추가한다.
using Newtonsoft.Json.Linq;
using System.IO;
세이브 파일이 들어갈 폴더 자동 생성
//저장해야할 요소, 이름, 직업,레벨 등등, 던전 층, 아이템 public SaveLoad() { string folderPath = "./Save"; // ./ 실행한 폴더 경로 DirectoryInfo SaveFolder = new DirectoryInfo(folderPath); if (SaveFolder.Exists == false)//경로에 Save 폴더가 없다면 생성 { SaveFolder.Create(); } }
./ : 프로젝트가 실행된 경로를 말한다
따라서 ./Save는 프로젝트가 실행된 경로의 Save 폴더를 말한다.
DirectoryInfo에 폴더 경로를 할당하고
만약 경로에 Save라는 폴더가 없다면 자동으로 생성해주게 작성하였다.
https://learn.microsoft.com/ko-kr/dotnet/api/system.io.directoryinfo?view=net-8.0
Json 파일 생성
static string filePath = "./Save/SaveFile.json"; static public void Save(Player player)//현재는 플레이어 스텟만 저장 { JObject Save = new JObject( new JProperty("playerName", player.name), new JProperty("playerJob", player.job), new JProperty("playerLevel", player.level), new JProperty("playerAttack", player.attack), new JProperty("playerDefense", player.defense), new JProperty("playerHealth", player.health), new JProperty("playerGold", player.gold) ); File.WriteAllText(filePath, Save.ToString());//Save 폴더 안에 json 파일 만들기 }
아직 다른 요소는 작성되지 않아서 플레이어의 정보만 저장해보기로 했다.
Jobject를 새로 생성해 준 다음 저장해야할 요소를 new Jproperty("저장할이름","들어갈 값") 로 넣어주었다.
File.WriteAllText({파일 경로}, JObject.ToString())
File을 생성하고 저장하려는 변수를 적어준다.
저장한 Json 파일 불러오는법
저장한 파일에 playerName이 입력받은 값과 같다면 저장한 파일을 불러오게 설정했다. (세이브 하나만 이용)
//ConsoleUtility.cs string name = Console.ReadLine(); if (File.Exists("./Save/SaveFile.json")) { JObject jobject = SaveLoad.Read();//저장한 파일과 이름이 같은지 확인 if (name == jobject["playerName"].ToString()) { strings[0] = jobject["playerName"].ToString(); strings[1] = jobject["playerJob"].ToString(); return strings; } } strings[0] = name; strings[1] = SelectJob(); return strings;
- 먼저 파일이 존재하는지 확인
- 파일이 존재한다면 입력받은 이름값과 Json파일 내 이름이 같은지 비교한다.
- 만약 같다면 저장해놨던 값을 대입, 아니라면 새로 저장한다.
Json 파일을 읽어오는 함수
//SaveLoad.cs static public JObject Read() { string readJson = File.ReadAllText(filePath);//json 파일 불러오기 JObject jobject = JObject.Parse(readJson); // Console.WriteLine(jobject["playerName"]);//저장한 값 잘 나옴 return jobject; }
일단 오늘은 평범한 변수들은 잘 저장했지만... 리스트나 배열은 어떻게 저장해야할지 좀 더 알아봐야할 것 같다.
'TIL' 카테고리의 다른 글
[TIL] 2024-05-03 팀 프로젝트 마무리 (0) 2024.05.03 [TIL]C# Json 리스트 저장 (0) 2024.05.02 [TIL]2024-04-30 VisualStudio (0) 2024.04.30 [TIL]특강 (C#, 코드컨벤션,깃허브) (0) 2024.04.29 [TIL]C# 알고리즘 (0) 2024.04.28