
TEXT 게임 해설 요약
간단하게 도식화해서 화면 구성 정리
ex)
오프닝 화면 에서 그 다음 씬 생각
각 페이지에서 0,1,2 등을 입력해서 input을 받는 과정이 공통적으로 들어가기 때문에 이 부분을 모듈화, 함수화 시킬 필요가 있겠다.
인벤토리-장착관리에서 보여주는 아이템 목록을 보여주는 기능 같은 경우도 함수로 만들어서 편리하게 활용 할 수 있겠다.
구현하기 힘들어하는 초보 입장에서 진행
샘플 코드 기반으로 시작
하나의 Item을 각각의 개별 변수로 저장하는 것보다 클래스로 저장 -> 훨씬 편리, 실수를 방지
다시 만들면서 최대한 코드를 해석하는 방식으로 작성
본인에게는 아직 복잡한 부분이 많아서 과정과 그 이유, 모르는 개념도 최대한 찾아서 다 주석으로 처리 함.
최종 완성 후 코드의 순서 정리 예정.. 쉽지 않다.
namespace TextRPG2
{
//구성
// 0. 데이터 초기화
// 1. 스타팅 로고를 보여줌 (게임 처음 킬 때만)
// 2. 선택 화면을 보여줌 (기본 구현사항 - 상태 / 인벤토리)
// 3. 상태 화면을 구현함 ( 필요 구현 요소 : 캐릭터, 아이템)
// 4. 인벤토리 화면 구현함
// 4-1. 장비장착 화면 구현
// 5. 선택화면 확장
// P.s -> 숫자. 은 코드 작업 순서를 표시 한 것.
// 3. 플레이어를 생성하기 위해 클래스가 필요함, 캐릭터와 아이템 클래스를 구현할 예정
public class Character // 3-1. 캐릭터 클래스 정의, 캐릭터의 구성도
{
public string Name { get; } // 한 번 정의되면 바꿀 수 없도록 함
public string Job { get; } // Enum은 아직 배우지 않았으므로 pass
public int Level { get; }
public int Atk { get; }
public int Def { get; }
public int Hp { get; }
public int Gold { get; }
public Character(string name, string job, int level, int atk, int def, int hp, int gold)
// 3-2. 캐릭터 클래스의 생성자, 기본적으로 클래스의 이름과 같은 함수, 캐릭터를 실제로 생성하는 과정 = 인스턴스를 만든다
{
Name = name;
Job = job;
Level = level;
Atk = atk;
Def = def;
Hp = hp;
Gold = gold;
// 조금 이따가 GameDataSetting()에 new 캐릭터를 생성 할 때(인스턴스를 만들 때)
// 클래스 - 생성자에서 설정한 기본 세팅 조건들이 자동으로 대입 됨.
}
}
public class Item // 4. 아이템 클래스 정의
{
public string Name { get; }
// get 접근자 : 속성 값을 읽는 데 사용, 클래스의 외부에서 속성의 값을 요청할 때 get 접근자의 코드가 실행
// set 접근자 : 속성 값을 할당 하는데 사용, 클래스의 외부에서 속성에 특정 값을 할당. (현재는 사용 x)
// get; 만 있다 = 읽기 전용, 객체가 생성될 때 설정 되고 이후에는 변경 할 수 없음.
// 둘 다 있다 = 읽기/쓰기 전용, 클래스 외부에서 값을 변경할 수 있음, 속성이 동적으로 변경될 필요가 있는 경우 사용.
// ∴ 플레이어의 레벨이나 체력이 게임 도중 변경될 수 있다면(사냥을 한다거나) set 접근자가 필요할 수 있다.
// public void LevelUp() => 레벨업 메서드 예시
// {
// Level += 1;
// (기타 구현 코드 생략)
// }
// 이런 경우 캐릭터 클래스의 public int Level {get;}의 get 옆에, set 도 추가해줘야 한다.
public string Description { get; } // 템 설명
public int Type { get; } // 무기 or 방어구 타입
public int Atk { get; } //
public int Def { get; }
public int Gold { get; }
public bool IsEquipped { get; set; } // 4-1. 장착 유무, 장착이 실제로 되었는지 확인하기 위함
public static int ItemCnt = 0; // 5-2. static int -> 클래스에 공유가 되는 int형 변수
// 각각의 인스턴스가 아닌 아이템 클래스에 귀속 되어 게임에 전반적으로 공유 되는 변수, additem 함수에 활용 될 예정
// 아이템이 만들어 질 때마다 변수를 1만큼 올리고 싶을 때, 일일히 인스턴스에서 만들기 보단 전체에 공유 되는 값을 위함.
public Item(string name, string description, int type, int atk, int def, int gold, bool isEquipped = false)
// 4-2. 아이템 클래스의 생성자, 장착 유무는 false로 설정(처음에 안 끼고 있으니)
{
Name = name;
Description = description;
Type = type;
Atk = atk;
Def = def;
IsEquipped = isEquipped;
}
public void PrintItemStatDescription(bool withNumber = false, int idx = 0)
// 7-1. 아이템 설명 출력
{
Console.Write("- ");
if (withNumber) // 7-4 아이템 장착관리 번호 컬러
{
Console.ForegroundColor = ConsoleColor.DarkMagenta;
Console.Write("{0} ", idx);
Console.ResetColor();
}
if (IsEquipped) // 아이템 착용 시
{
Console.Write("[");
Console.ForegroundColor = ConsoleColor.Cyan; // [ 의 뒤의 색 변경
Console.Write("E"); // cyan 컬러
Console.ResetColor(); // 색 리셋
Console.Write("]");
Console.Write(PadRightForMixedText(Name, 9)); // 장착 중인 경우는 9글자 출력
}
else
Console.Write(PadRightForMixedText(Name, 12)); // 장착 중이 아닌 경우 12글자 출력
Console.Write(" | ");
// 수치가 0이 아니라면 작동, [ 삼항 연산자 활용 => 조건 ? 조건이 참이라면 : 조것이 거짓이라면 ]
if (Atk != 0) Console.Write($"Atk {(Atk >= 0 ? " + " : "")}{Atk}"); // 공격력이 0보다 크거나 같으면 "+" 를 붙이고 아니면 " "해라(붙이지 마라).
if (Def != 0) Console.Write($"Def {(Def >= 0 ? " + " : "")}{Def}");
Console.Write(" | ");
Console.WriteLine(Description); // 설명 String 출력
}
public static int GetPrintableLength(string str) // 아이템 텍스트 정렬을 위해 Length의 길이를 구함
{
int length = 0;
foreach (char c in str)
{
if (char.GetUnicodeCategory(c) == System.Globalization.UnicodeCategory.OtherLetter)
{
length += 2; // 한글과 같은 넓은 문자는 길이를 2로 취급
}
else
{
length += 1; // 나머지 문자는 길이를 1로 취급
}
}
return length;
}
public static string PadRightForMixedText(string str, int totalLength)
{
int currentLength = GetPrintableLength(str); // 텍스트의 실제 길이
int padding = totalLength - currentLength; // 총길이 - 실제길이 = int padding 추가해야 할 길이
return str.PadRight(str.Length + padding); // padding 만큼 PadRight(문자열의 오른쪽)에 공백을 추가
}
}
internal class Program
{
static Character _player; // 5. 실제 플레이에서 쓸 캐릭터와 아이템 추가, class Program 내에서 플레이어 관련에 주구장창 사용 예정
static Item[] _items; // 아이템은 여러 개 이므로 배열[] 사용, class Program내에서 아이템 관련 사용 예정
static void Main(string[] args) // 메인 함수
{
GameDataSetting(); // 1. 게임 데이터 세팅
// new 라는 키워드를 통해 새로운 캐릭터를 메모리에 할당 지시, Character 생성자 발동
PrintStartLogo(); // 5-4. 게임 스타트 화면 함수
StartMenu(); // 5-5. 스타트 메뉴
}
private static void GameDataSetting() // 2. 게임 데이터 메서드 생성, Private(비공개)
{
_player = new Character("정진", "백수", 1, 10, 5, 100, 1500); // 5-1. new 플레이어 변수 선언(실제로 사용할 캐릭터의 데이터)
// 아까 3-2 에서 만든 캐릭터 클래스 생성자의 세팅 값들을 받아 옴.
// () 괄호 안에 각각 순서대로 Name, Job, Level, Atk, Def, HP, Gold 순서이며, 입력 값이 세팅 됨.
// _ (언더스코어) 사용 이유? = Private(비공개) 필드 구분을 위한 것.
// 클래스 내부에서만 사용되는 변수임을 쉽게 식별할 수 있고, 외부에서 접근되는 공개 속성이나 메서드와의 혼동을 방지.
// 복습 : 클래스의 필드(field)란 클래스에 포함된 변수(Variable)를 말한다. ( 결국 같은 뜻? )
// 변수에는 특정 값을 할당할 수 있고, 이를 통해 객체의 특성을 만들어줄 수 있다.
_items = new Item[8]; // 이번에는 List 대신 배열을 사용, 추후 5-3. additem 메서드를 정의 할 예정, (위의 5-2 로 이동)
// 5-4. 아이템 추가
AddItem(new Item("무쇠 갑옷", "무쇠로 만들어진 튼튼한 갑옷입니다.", 0, 0, 5, 1000)); // 맨 앞의 숫자가 0이면 방어구, 1이면 무기
AddItem(new Item("수련자 갑옷", "수련에 도움을 주는 갑옷입니다.", 0, 0, 9, 2000));
AddItem(new Item("스파르타의 갑옷", "스파르타의 전사들이 사용했다는 전설의 갑옷입니다.", 0, 0, 15, 3500));
AddItem(new Item("전신 방탄복", "총탄 및 파편 등으로부터 보호하기 위해 특수제작된 보호구", 0, 0, 30, 5500)); // 추가 아이템 1
AddItem(new Item("낡은 검", "쉽게 볼 수 있는 낡은 검입니다.", 1, 2, 0, 600));
AddItem(new Item("청동 도끼", "쉽게 볼 수 있는 낡은 검입니다.", 1, 5, 0, 1500));
AddItem(new Item("스파르타의 창", "스파르타의 전사들이 사용했다는 전설의 창입니다.", 1, 7, 0, 3000));
AddItem(new Item("AK - 47", "왜 갑자기 총기가 튀어나왔는지는 모르겠지만, 너도 나도 사이 좋게 한 방입니다.", 1, 15, 0, 6000)); // 추가 아이템 2
}
static void StartMenu() // 5-5. 스타트 메뉴
{
Console.Clear(); // 게임 스타트 화면 정리
Console.WriteLine("■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■");
Console.WriteLine("스파르타 마을에 오신 여러분 환영합니다.");
Console.WriteLine("이곳에서 던전으로 들어가기전 활동을 할 수 있습니다.");
Console.WriteLine("■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■");
Console.WriteLine(); // Console.WriteLine(); 단축키 -> C + W + Tab
Console.WriteLine("");
Console.WriteLine("1. 상태 보기");
Console.WriteLine("2. 인벤토리");
Console.WriteLine("3. 상점");
Console.WriteLine("4. 던전 입장");
Console.WriteLine("5. 휴식");
Console.WriteLine("6. 게임 저장 및 불러오기");
Console.WriteLine("7. 게임 종료");
Console.WriteLine("");
// int keyInput = int.TryParse(Console.ReadLine(), out keyInput); 는 안 쓰고 아래 따로 함수 생성
// CheckValidInput(1, 7); // 5-6. 1에서 7까지 유효성 확인, 일단 관련 함수들을 만들고 switch 문의 매개변수로 사용될 예정
switch (CheckValidInput(1, 7))
// 5-8. switch문, CheckValidInput함수로 유효 값(1~7)을 입력 받으면 그에 맞는 함수 호출 후 break;로 벗어남.
{
case 1:
StatusMenu(); // 플레이어 상태
break;
case 2:
InventoryMenu(); // 인벤토리
break;
case 3:
//StoreMenu(); // 상점
break;
case 4:
//DungeonMenu(); // 던전 입장
break;
case 5:
//RestMenu(); // 휴식
break;
case 6:
//SaveGameMenu(); // 게임 저장 및 불러오기
break;
case 7:
//GameOverMenu(); // 게임 종료
break;
}
}
private static void StatusMenu() // 6. 플레이어 상태
{
// 6-3. 상태창 꾸미기 작업 시작
Console.Clear();
ShowHighlightText("■ 상태 보기 ■"); // 제목에 첫 줄 색 변경 6-2 함수 활용
Console.WriteLine("캐릭터의 정보가 표기됩니다.");
PrintTextWithHighlights("Lv ", _player.Level.ToString("00")); // 문자열 하이라이트 6-2 함수 활용, "00" 은 01,02,03 이런 식으로 두 자릿수로 표현
Console.WriteLine("");
Console.WriteLine("{0} ({1})", _player.Name, _player.Job);
// 7-8. 합산 공격력, 방어력 구현 추가
int bonusAtk = getSumBonusAtk(); // 밑에 넣어줄 예정
int bonusDef = getSumBonusDef();
PrintTextWithHighlights("공격력 : ", (_player.Atk + bonusAtk).ToString(), bonusAtk > 0 ? string.Format(" (+{0})", bonusAtk) : "");
// 플레이어 공격력 + 보너스 공격력을 문자열로, (삼항연산자) 보너스 공격력이 0보다 크면 +(보너스 어택)을 출력해주고, 아니면은 빈칸을 추가
PrintTextWithHighlights("방어력 : ", (_player.Def + bonusDef).ToString(), bonusDef > 0 ? string.Format(" (+{0})", bonusDef) : "");
// 각각 PrintTextWithHighlights 함수의 s1 , s2, s3인데 Atk(공격력) 의 자료형은 Int이므로 s2의 노란색 컬러를 적용 시키기 위해 Tostring 해줌
PrintTextWithHighlights("체력 : ", _player.Hp.ToString());
PrintTextWithHighlights("골드 : ", _player.Gold.ToString());
Console.WriteLine("");
Console.WriteLine("0. 뒤로가기");
Console.WriteLine("");
switch (CheckValidInput(0, 0)) // 0번 나가기
{
case 0:
StartMenu();
break;
}
}
private static int getSumBonusAtk() // 7-7. 공격력 합산 표시
{
int sum = 0; // 능력치를 다 더 할 것
for (int i = 0; i < Item.ItemCnt; i++) // 아이템을 전부 확인
{
if (_items[i].IsEquipped) sum += _items[i].Atk;
// 아이템 목록의 아이템이 장착되어 있다면, 아이템의 Atk를 다 더해라.
}
return sum; // 그 다음에 리턴해라.
}
private static int getSumBonusDef() // 방어력 합산
{
int sum = 0;
for (int i = 0; i < Item.ItemCnt; i++) // 아이템을 전부 확인
{
if (_items[i].IsEquipped) sum += _items[i].Def;
}
return sum;
}
private static void InventoryMenu() // 7. 인벤토리
{
Console.Clear();
ShowHighlightText("■ 인벤토리 ■");
Console.WriteLine("보유중인 아이템을 관리 할 수 있습니다.");
Console.WriteLine("");
Console.WriteLine("[아이템 목록]");
Console.WriteLine("");
// 위쪽 Item 클래스로 이동해서 작업 7-1로.
// 7-2 아이템 설명 출력 반복문
for(int i = 0; i<Item.ItemCnt; i++) // 아이템의 Itemcnt 반복문, 아이템의 가짓 수 만큼 출력되어 보임
{
_items[i].PrintItemStatDescription(true, i+1); // true면, _items의 i번째에서 7-1의 PrintItemStatDescription();(아이템 설명) ++ 출력
}
Console.WriteLine("");
Console.WriteLine("0. 나가기");
Console.WriteLine("1. 장착관리");
Console.WriteLine("");
switch (CheckValidInput(0, 1)) // 0 or 1
{
case 0:
StartMenu();
break;
case 1:
EquipMenu(); // 장착 관리
break;
}
}
private static void EquipMenu() // 7-3. 장착관리 메뉴
{
Console.Clear();
ShowHighlightText("■ 인벤토리 - 장착 관리 ■");
Console.WriteLine("보유중인 아이템을 장착/해제 할 수 있습니다.");
Console.WriteLine("");
Console.WriteLine("[아이템 목록]");
Console.WriteLine("");
// 잠시 PrintItemStatDescription함수로 이동, withNumber 변수를 활용 예정
for (int i = 0; i < Item.ItemCnt; i++)
{
_items[i].PrintItemStatDescription(true, i+1);
}
Console.WriteLine("");
Console.WriteLine("0. 나가기");
// 7-5. default를 활용한 switch문 -> 모든 케이스가 아니면, 마지막에 케이스 default가 실행
int keyInput = CheckValidInput(0, Item.ItemCnt); // 아까 만든 입력 유효성 확인 함수(0에서 아이템 숫자만큼) 인풋값에 활용
switch (keyInput)
{
case 0:
InventoryMenu();
break;
default:
ToggleEquipStatus(keyInput - 1); // ToggleEquipStatus는 곧 만들고(아이템 장착 상태 변경),
// 유저 입력값은 123이며 실제 배열에는 012 이므로 -1해서 맞춰줌
EquipMenu();
break;
}
}
private static void ToggleEquipStatus(int idx) // 7-6. 아이템 장착 상태 변경 , IsEquipped;가 true면 [E]가 나온다.
{
_items[idx].IsEquipped = !_items[idx].IsEquipped; // _items의 목록[idx]에 들어가서 IsEquipped이면, !(bool값을 반대로)로 장착 상태 변경
}
private static void ShowHighlightText(string text) // 6-1. 첫 줄 색 변경 함수, 마젠타 색
{
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine(text);
Console.ResetColor();
}
private static void PrintTextWithHighlights(string s1, string s2, string s3 = "") // 6-2. 문자열 하이라이트 효과 함수
{
Console.Write(s1);
Console.ForegroundColor = ConsoleColor.Yellow; // 노란색 발동 -> s2에 적용
Console.Write(s2);
Console.ResetColor (); // 색 리셋
Console.WriteLine(s3);
}
private static int CheckValidInput(int min, int max) // 5-6. 입력 값 유효성 확인 함수 (int 반환)
{
// 아래 두 가지 상황은 비정상, 재입력 수행
// 1. 숫자가 아닌 입력을 받은 경우, 2. 숫자가 최솟값 에서 최댓값의 범위를 벗어난 경우
int keyInput; // tryParse(정수화)에 필요
bool result; // while 반복문에 필요
do // 일단 한 번 실행
{
Console.WriteLine("원하시는 행동을 입력 해주세요.");
result = int.TryParse(Console.ReadLine(), out keyInput);
// 입력을 정수로 변환하여 int KeyInput에 저장하고, 결과 값을 result로 설정.
// 결과 값이 숫자(정수)면 가져오고, 그 외면 안 가져옴(실행 안한다는 뜻)
}
while (result == false || CheckIfVaild(keyInput, min, max) == false); // result가 false거나 CheckIfVaild함수가 false면 반복
//여기에 도착했다는 것은 (아래 유효성 확인 bool 함수를 통해) 제대로 입력을 받았다는 것.
return keyInput;
}
private static bool CheckIfVaild(int keyInput, int min, int max) // 5-7. 유효성 확인 함수2 (bool 반환)
{
if (min <= keyInput && keyInput <= max) return true; // 키 입력값이 min ~ mix 사이면 return이 참 = 실행
return false; // 그 외면 false
}
static void AddItem(Item item) // 5-3. 아이템 추가 함수
{ // ( ) 안에 Item item ? -> AddItem 메서드의 매개 변수 = 외부에서 Item 객체의 item 매개 변수를 받아 온 것.
// Item 객체의 속성과 메서드에 접근 할 수 있음(데이터 읽기, 수정, 필요 기능 수행)
// 타입 유연성, 재사용성, 안정성을 보장하고 잘못된 타입의 객체 전달 방지.
if (Item.ItemCnt == 8) return; // Item클래스 객체의 ItemCnt 변수가 10이면 아무 것도 안 만든다.
_items[Item.ItemCnt] = item; // 0개 -> 0번 인덱스, 1개 -> 1번 인덱스
Item.ItemCnt++;
}
private static void PrintStartLogo() // 5-4. 게임 스타트 화면
{
Console.WriteLine("==================================================================");
Console.WriteLine(" ___________________ _____ __________ ___________ _____");
Console.WriteLine(" \\_____ \\ | ___// /_\\ \\ | _/ | | / /_\\ \\ ");
Console.WriteLine(" / \\ | | / | \\| | \\ | | / | \\");
Console.WriteLine(" /_______ / |____| \\____|__ /|____|_ / |____| \\____|__ /");
Console.WriteLine(" \\/ \\/ \\/ \\/");
Console.WriteLine("________ ____ ___ _______ ________ ___________________ _______ ");
Console.WriteLine("\\______ \\ | | \\\\ \\ / _____/ \\_ _____/\\_____ \\ \\ \\");
Console.WriteLine(" | | \\ | | // | \\ / \\ ___ | __)_ / | \\ / | \\");
Console.WriteLine(" | ` \\| | // | \\\\ \\_\\ \\ | \\/ | \\/ | \\");
Console.WriteLine("/_______ /|______/ \\____|__ / \\______ //_______ /\\_______ /\\____|__ /");
Console.WriteLine(" \\/ \\/ \\/ \\/ \\/ \\/");
Console.WriteLine("==============================================================================");
Console.WriteLine(" PRESS ANYWAY TO START ");
Console.WriteLine("==============================================================================");
Console.ReadKey(); // 아무 키나 입력 받음
}
}
}
추가로 배운 기능 및 사용한 함수
Console.ForegroundColor / ResetColor
글자색 변경 및 리셋(ResetColor)
string.Right(totalWidth) / LeftPad
글자 정렬 기능
아이템 텍스트 정렬용 메서드 2개
public static int GetPrintableLength(string str) // 아이템 텍스트 정렬을 위해 Length의 길이를 구함
{
int length = 0;
foreach (char c in str)
{
if (char.GetUnicodeCategory(c) == System.Globalization.UnicodeCategory.OtherLetter)
{
length += 2; // 한글과 같은 넓은 문자는 길이를 2로 취급
}
else
{
length += 1; // 나머지 문자는 길이를 1로 취급
}
}
return length;
}
public static string PadRightForMixedText(string str, int totalLength)
{
int currentLength = GetPrintableLength(str); // 텍스트의 실제 길이
int padding = totalLength - currentLength; // 총길이 - 실제길이 = int padding 추가해야 할 길이
return str.PadRight(str.Length + padding); // padding 만큼 PadRight(문자열의 오른쪽)에 공백을 추가
}
하루 종일 다시 만들어보며 분석을 했는데, 이리저리 클래스를 넘나들며 만드는 것 자체가 쉽지 않다.
모르는 부분이 있거나, 이해하지 않으면 찜찜해서 왜 저렇게 입력 했는지 일일히 주석을 달다보니 하루가 가버렸다.
이제 추가 기능 차례인데, 내일 저녁까지 완성 할 수 있을지..
추후에는 텍스트 RPG를 혼자서 처음부터 끝까지 구현 할 수 있는게 최종 목표.


어? 30분 뒤다.
오후 3시부터 새벽1시까지 오늘의 공부 끝!
이건 ASCII art 라고 텍스트를 텍스트형 아트 문자로 바꿔주는 곳, 게임 스타트 화면에 활용 됨.
https://textkool.com/en/test-ascii-art-generator?text=