-
20230829 - 일지C#/C# 미니실습 2023. 8. 30. 23:26
과제 : 콘솔로 텍스트 게임 만들기.
1. 몬스터, 아이템의 정보를 .csv 파일로 따로 관리하기
랜덤하게 몬스터를 젠하는 메서드를 구현해야 했다.
이전까지는 일일히 객체의 정보를 입력하여 관리하였는데, 이번에는 외부 파일로 정보를 관리하는 것이 더 효율적일 것 같아서, 시도해보았다.
A. 현재 파일의 디렉토리 얻기
검색을 통해 다음과 같이 구현하였다.
public static string localPath = Directory.GetParent(Path.GetFullPath(@"..\Data\MonsterData.csv")).Parent.Parent.Parent.Parent.ToString();
Path.GetFullPath
: 절대 경로 정보를 string 타입으로 반환
public static string GetFullPath (string path);
static void Main(string[] args) { var fullPath = Path.GetFullPath(@".."); Console.WriteLine($"fullPath의 타입 : {fullPath.GetType().Name}"); Console.WriteLine($"fullPath : {fullPath}"); var memoPath = Path.GetFullPath(@"..memoPad.cs"); Console.WriteLine($"fullPath의 타입 : {memoPath.GetType().Name}"); Console.WriteLine($"fullPath : {memoPath}"); }
>
의 위치가 반환되길 기대했는데, bin 폴더 안의 파일의 경로를 반환하였다.
일단은, .Parent로 상위폴더로 직접 이동하는 식으로 원하는 경로를 반환하려 하였다.
Path.GetFullPath(@"..\Data\MonsterData.csv").Parent.Parent.Parent.Parent
그런데, Path.GetFullPath(@"..\Data\MonsterData.csv")) 의 반환값은 string 타입이기 때문에, Parent를 써서 상위 디렉토리를 구하려 했는데, 타입이 맞지 않아 에러가 발생했다.
Directory 클래스와 DirectoryInfo 클래스
: Directory 클래스는 정적 메서드를 사용하여 문자열을 반환.
: DirectoryInfo 클래스는 인스턴스화한 객체를 반환. 이 객체와 메서드를 통하여 디렉토리 관리 가능(ex. Delete())
따라서, DirectoryInfo 클래스로 경로를 인스턴스화한 객체를 반환한 뒤, Parent 와 같은 메서드를 사용하여 Data 폴더를 갖고 있는 디렉토리까지 접근 가능하다.
static void Main(string[] args) { DirectoryInfo currentDirInfo = new DirectoryInfo(Directory.GetCurrentDirectory()); DirectoryInfo parentDirInfo = currentDirInfo.Parent.Parent.Parent.Parent; Console.WriteLine($"현재 디렉토리 (DirectoryInfo): {currentDirInfo.FullName}"); Console.WriteLine($"부모*4 디렉토리 (parentDirInfo): {parentDirInfo.FullName}"); }
>
원하는 디렉토리까지 접근한 후, ToString()으로 문자열로 반환.
!! 수정 코드
public class Pathes { public static DirectoryInfo currentPath = new DirectoryInfo(Directory.GetCurrentDirectory()); public static string localPath = currentPath.Parent.Parent.Parent.Parent.ToString(); //... 생략 ... }
B. 각 csv 파일의 Path 얻기
Path.Combine
: 문자열 배열을 한 경로로 결합.
public static string Combine (params string[] paths);
public static string MonsterDataPath() { var dataPath = @"Data"; var fileName = @"MonsterData.csv"; var fullPath = Path.Combine(localPath, dataPath, fileName); return fullPath; }
2. 몬스터의 정보를 클래스에 적용시키기.
A. 클래스와 필드에 대한 고민
작업을 하면서, 몬스터 클래스에 지정한 필드는 다음과 같다.
internal class Monster : Character { public int Level { get; } public int Hp { get; set; } public int Atk { get; } public int Def { get; } public int Gold { get; } public int Exp { get; } public string Name { get; } public string Chrd { get; } int _critical; public Monster(string name, string chrd, int level) { Name = name; Chrd = chrd; Level = level; Atk = level * 3; Def = level * 2; Hp = level * 20; } }
이렇게 하면, 다양한 종류의 몬스터를 인스턴스화하여도, 같은 레벨이라면, 공격력, 방어력, Hp 가 같아 몬스터의 특색이 없어진다고 생각하였다.
static void Main(string[] args) { Monster StrongRabbit = new Monster("우람한 토끼", "짐승", 3); Monster WeakRabbit = new Monster("비실비실한 토끼", "짐승", 3); Console.WriteLine($"우람한 토끼의 공격력 : {StrongRabbit.Atk}"); Console.WriteLine($"비실비실한 토끼의 공격력 : {WeakRabbit.Atk}"); Console.WriteLine($"우람한 토끼의 방어력 : {StrongRabbit.Def}"); Console.WriteLine($"비실비실한 토끼의 방어력 : {WeakRabbit.Def}"); Console.WriteLine($"우람한 토끼의 체력 : {StrongRabbit.Hp}"); Console.WriteLine($"비실비실한 토끼 체력 : {WeakRabbit.Hp}"); }
>
그래서, 필드값을 컬럼으로 하여 각 몬스터의 정보를 csv 파일에 저장하고, 불러오려고 했다.
Monster 클래스는 생성자의 파라미터로 level을 받고 있기 때문에, level별로 정보를 csv 파일에 저장하면, 동일 몬스터에 대한 정보가 많아져 관리하기 어렵다고 생각했다.
=> level이 주어지면, 계산가능한 정보를 csv 파일에 저장하기로 하였다. (ex. 공격력 -> 공격계수)
이 때의 컬럼은 Monster 클래스의 필드와 달라지기 때문에, 컬럼과 동일한 필드를 지닌 MonsterInfo 클래스를 만들어 csv 파일의 정보를 MonsterInfo 인스턴스를 요소로 하는 배열로 관리하기로 하였다.
public class MonsterInfo { public string Name { get; } public string Chrd { get; } public float MonAtkCoeff { get; } // 공격계수 public float MonDefCoeff { get; } // 방어계수 public int MonHPCoeff { get; } // HP 계수 public int Exp { get; } // 경험치 계수 public int StageRank { get; } // 스테이지별 등장하는 몬스터를 차별화하기 위해 추가한 인덱스 public int DropGold { get; } // 드롭하는 골드 계수 public MonsterInfo(string name, string chrd, float monAtkCoeff, float monDefCoeff, int monHPCoeff, int exp, int dropGold, int stageRank) { Name = name; Chrd = chrd; MonAtkCoeff = monAtkCoeff; MonDefCoeff = monDefCoeff; MonHPCoeff = monHPCoeff; Exp = exp; DropGold = dropGold; StageRank = stageRank; } // ... 생략 ...
그림의 csv 파일을 MonsterInfo 인스턴스를 요소로 하는 배열로 만든 코드는 다음과 같다.
class MonsterInfo { //... 생략... public static MonsterInfo[] GetMonsterInfo() { MonsterInfo[] AllMonsters = new MonsterInfo[] { }; string fullPath = Pathes.MonsterDataPath(); string[] monsterData = File.ReadAllLines(fullPath, Encoding.UTF8); string[] fieldNames = monsterData[0].Split(','); // 컬럼 부분(첫줄)은 MonsterInfo의 필드 이름을 지니고 있음. for (int monIdx = 1; monIdx < monsterData.Length; monIdx++) { string[] monsterEach = monsterData[monIdx].Split(','); string name = monsterEach[Array.IndexOf(fieldNames, "Name")]; string chrd = monsterEach[Array.IndexOf(fieldNames, "Chrd")]; float monatkcoeff = float.Parse(monsterEach[Array.IndexOf(fieldNames, "MonAtkCoeff")]); float mondefcoeff = float.Parse(monsterEach[Array.IndexOf(fieldNames, "MonDefCoeff")]); int monHPcoeff = int.Parse(monsterEach[Array.IndexOf(fieldNames, "MonHPCoeff")]); int exp = int.Parse(monsterEach[Array.IndexOf(fieldNames, "Exp")]); int dropGold = int.Parse(monsterEach[Array.IndexOf(fieldNames, "DropGold")]); // 몬스터 스테이지 조정 int stageRank = int.Parse(monsterEach[Array.IndexOf(fieldNames, "StageRank")]); MonsterInfo monsterDict = new MonsterInfo(name, chrd, monatkcoeff, mondefcoeff, monHPcoeff, exp, dropGold, stageRank); Array.Resize(ref AllMonsters, AllMonsters.Length + 1); AllMonsters[monIdx - 1] = monsterDict; } return AllMonsters; }
static 메서드
: 클래스로부터 객체를 생성하지 않고 직접 호출하는 메서드.
Monster 클래스에서 몬스터 인스턴스를 초기화 할 때, MonsterInfo 인스턴스를 요소로 하는 배열에서 해당 Monster의 이름(Monster.Name)을 검색하여, 동일한 이름(MonsterInfo.Name)을 가지고 있는 MonsterInfo를 가지고와, 파라미터로 주어진 level 과의 계산을 수행하여, 필요한 필드값을 입력할 계획
=> 외부 클래스에서 객체 생성 없이 가져와야 함
=> GetMonsterInfo()를 public static 으로 선언.
File.ReadAllLines()
: 텍스트 파일을 열고 파일의 모든 줄을 문자열 배열로 읽어 들인 다음 파일을 닫는 메서드
public static string[] ReadAllLines (string path, System.Text.Encoding encoding);
: Encoding이 맞지 않는 경우, 글자가 깨지는 현상이 발생하여, csv 파일을 UTF-8로 인코딩한 후, 메서드 파라미터에도 파일에 적용된 UTF8 을 입력함.
string fullPath = Pathes.MonsterDataPath(); string[] monsterData = File.ReadAllLines(fullPath, Encoding.UTF8);
이후, Monster 클래스에 MonsterInfo.GetMonsterInfo() 메서드를 통하여, MonsterInfo 인스턴스를 요소로 하는 배열을 통해 생성자에서 필드값 초기화를 수행할 수 있게끔 생성자를 수정하였다.
internal class Monster : Character { public int Level { get; } public int Exp { get; } public int Hp { get; set; } public int Atk { get; } public int Def { get; } public int Gold { get; } public string Name { get; } public string Chrd { get; } int _critical; public MonsterInfo[] MonsterInfoArray = MonsterInfo.GetMonsterInfo(); public Monster(string name, string chrd, int level) { Level = level; Name = name; Chrd = chrd; MonsterInfo[] monsterInfo = MonsterInfoArray.Where(mon => mon.Name == name).ToArray(); Atk = (int)(Level * monsterInfo[0].MonAtkCoeff); Def = (int)(Level * monsterInfo[0].MonDefCoeff); Hp = Level * monsterInfo[0].MonHPCoeff; Exp = Level * monsterInfo[0].Exp; Gold = Level * monsterInfo[0].DropGold; _critical = 20; } //... 생략....
B. 활용
이후, Dungeon 클래스에서 몬스터의 임의 소환 기능을 구현하는 메서드 MonsterGen() 에 활용할 수 있었다.
class Dungeon { private int _stage; public int Stage { get { return _stage; } set { _stage = value; } } private int _round; public int Round { get { return _round; } set { _round = value; } } //... 생략 ... public Monster[] BossMonsterGen() { Random random = new Random(); int bossMonsterLevel = random.Next(Stage, Stage + 4); // MonsterInfo.GetMonsterInfo()를 통해 모든 몬스터의 MonsterInfo를 요소로 하는 배열 생성 // Where을 통해 MonsterInfo.StageRank 와 현재 Dungeon.Stage를 비교하여 배열 제약 MonsterInfo[] bossMonsterInfo = (Stage < 4) ? MonsterInfo.GetMonsterInfo().Where(mon => mon.StageRank < 0 && mon.StageRank > -Stage - 1).ToArray() : MonsterInfo.GetMonsterInfo().Where(mon => mon.StageRank == -Stage).ToArray(); int bossIdx = random.Next(0, bossMonsterInfo.Length); // MonsterInfo에서 Name, Chrd, 해당 메서드에서 level을 파라미터로 새로운 Monster 클래스의 인스턴스 초기화. Monster bossMonster = new Monster(bossMonsterInfo[bossIdx].Name, bossMonsterInfo[bossIdx].Chrd, bossMonsterLevel); Monster[] getMonsterArray = new Monster[1]; getMonsterArray[0] = bossMonster; return getMonsterArray; }