내일배움캠프 - TIL/내일배움캠프 - TIL

내일배움캠프 6일차 TextRPG

rudals4469 2025. 4. 17. 18:48

이번주차 개인 과제 TextRPG

이번 포스트에서는 2주차 개인 과제였던 TextRPG 게임을 씹고 뜯고 맛보고 즐길것이다.

대략적으로 내가 어떻게 생각하고 구현했는지를 쭈욱 의식의 흐름따라 정리해볼 것이다.

음 게임을 대략적으로 설명해보자면 콘솔 창에서 원하는 숫자를 입력하여 게임을 진행해 나간다고 생각하면 될 것 같다.

 

필수적으로 구현해야하는 기능에는

 

  1. 게임 시작화면
  2. 상태보기
  3. 인벤토리
  4. 장착 관리
  5. 상점 - 구매

정도가 있는데 하나 씩 찬찬히 살펴보자

가장 먼저 게임 시작화면이다.

스파르타 마을에 오신 여러분 환영합니다.
이곳에서 던전으로 들어가기전 활동을 할 수 있습니다.

1. 상태 보기
2. 인벤토리
3. 상점

원하시는 행동을 입력해주세요.
>>

 

게임을 시작하면 위와 같이 텍스트로 화면이 뜨게 출력하면 된다.

사실 이 파트는 입출력을 받을 수 있냐? 라고 묻는 파트라고 생각한다.

어렵지 않게 쭉쭉 구현을 해나가다 조건을 하나 보게 되는데 

바로 "1 ~ 3 이외 입력시 - 잘못된 입력입니다 출력" 이다. 즉 특정입력 외에는 입력을 막아라 라는 소리이다.

어떻게 할까 생각하다 TryParse라는 구문을 사용하면 되는 구나를 알게되었고 이를 적용시켜 구현을 해본다.

먼저 TryParse를 간단히 설명하자면 숫자 형식에 있는 내장 함수이며 문자열 반환에 성공할 경우 true를 실패할 경우 false를 반환하게되고 out 매개변수에 변환된 숫자가 리턴되는 형식이다.

즉 [숫자 형식].Tryparse(string [변환할 문자열],out [매개변수] 의 형식을 가졌는데

여기서 나는 1,2,3 와 같은 int형 숫자를 받아와야되고 변환할 문자열을 입력으로 받아왔다.

그리고 그 입력을 매개변수에 담는 것이다.

int.TryParse(Console.ReadLine(), out int Input)

 

대충 이런식으로 작성하면 된다. 이 구문을 통해 입력을 제한하고 switch문을 통해 각 입력(숫자)에 대해 대응하도록 구현을 시작한다. 또한 위 구문은 게임 전반적인 입력을 제한하는 곳에서 모두 사용되었다.

 

두 번째로 상태보기이다. 상태보기에서는 캐릭터의 정보를 표시하라고 되어있다.

상태 보기
캐릭터의 정보가 표시됩니다.

Lv. 01      
Chad ( 전사 )
공격력 : 10
방어력 : 5
체 력 : 100
Gold : 1500 G

0. 나가기

원하시는 행동을 입력해주세요.
>>

 

위와 같이 캐릭터의 정보값을 한 곳으로 묶기 위해 class를 사용할 수 있느냐를 묻는 파트였던 것 같다.

그래서 나는 Player라는 클래스에 위와 같은 속성을 받아 줄 수 있도록 처리하였고 생성자도 잊지 않고 만들어 주었다.

더보기
public class Player
{
    // 플레이어 정보도 클래스로 빼자
    // 레벨, 이름 직업, 공격력, 방어력, 체력, 골드 , 인벤토리, 무기공격력, 무기방어력, 경험치, 레벨업경험치
    public int level { get; set; }
    public string name { get; set; }
    public string Job {  get; set; }
    public float Att { get; set;}
    public int Def { get; set;}
    public int HP { get; set;}
    public float Gold { get; set;}
    public List<Item> Inventory { get; set; }
    public int WeaponAtt { get; set;}
    public int WeaponDef { get; set; }
    public int EXP { get; set;}
    public int LevelEXP { get; set;}

    // 생성자
    public Player()
    {
        name = "김경민";
        level = 1;
        Job = "전사";
        Att = 10f;
        Def = 5;
        HP = 100;
        Gold = 500;
        Inventory = new List<Item>();

        WeaponAtt = 0;
        WeaponDef = 0;
        EXP = 0;
        LevelEXP = 1;
        
    }

기본 구현에서 보다 더 많은 파라미터가 있긴 한데 추가 구현까지 다 하고 글을 작성하는 것이기 때문에 양해바란다.

쨋든 이렇게 플레이어를 클래스로 받아오게 되었고 메인화면에서 1번을 입력하면 위의 상태 보기 창이 뜨게 구현하였다.

 

세번째로 인벤토리 창 구현이다.

인벤토리
보유 중인 아이템을 관리할 수 있습니다.

[아이템 목록]
- [E]무쇠갑옷      | 방어력 +5 | 무쇠로 만들어져 튼튼한 갑옷입니다.
- [E]스파르타의 창  | 공격력 +7 | 스파르타의 전사들이 사용했다는 전설의 창입니다.
- 낡은 검         | 공격력 +2 | 쉽게 볼 수 있는 낡은 검 입니다.

1. 장착 관리
2. 나가기

원하시는 행동을 입력해주세요.
>>

 

위와 같이 아이템이 있을 때 플레이어의 인벤토리에는 아이템 목록이 출력되도록 하였다. 이 파트는 사실 상점에서 아이탬을 구매해야 내 인벤토리에 들어오기 때문에 구현 순서가 조금 뒤긴 하지만 간략히 설명하겠다.

상점에서 아이탬 구매 시 플레이어 클래스에 있던 인벤토리 리스트에 아이탬이 추가된다. 

그 뒤 인벤토리 메뉴에 들어오게 되면 리스트를 돌아 아이탬 목록을 출력하게 되는 것이다.

처음엔 인벤토리 리스트를 만들 생각을 못해서 해메긴 했지만 아이탬을 그냥 한 곳에 받아오면 안되나? 라는 생각에 배열과 리스트가 떠올랐고 그 중 내가 좀 더 편한 리스트로 구현을 해보았다. 물론 리스트를 사용하기 위해 아이탬들을 아이탬 클래스에 넣어서 관리해야 되긴 한다. 그 구현은 좀 더 밑에 나오기에 언급만 하고 넘어가겠다.

 

네 번째로는 장착 관리였다.

 

인벤토리 - 장착 관리
보유 중인 아이템을 관리할 수 있습니다.

[아이템 목록]
- 1 [E]무쇠갑옷      | 방어력 +5 | 무쇠로 만들어져 튼튼한 갑옷입니다.
- 2 [E]스파르타의 창  | 공격력 +7 | 스파르타의 전사들이 사용했다는 전설의 창입니다.
- 3 낡은 검         | 공격력 +2 | 쉽게 볼 수 있는 낡은 검 입니다.

0. 나가기

원하시는 행동을 입력해주세요.
>>

 

상태 보기
캐릭터의 정보가 표시됩니다.

Lv. 01      
Chad ( 전사 )
공격력 : 17 (+7)
방어력 : 10 (+5)
체 력 : 100
Gold : 1500 G

0. 나가기

원하시는 행동을 입력해주세요.
>>

 

인벤토리 - 장착 관리 메뉴에서 아이템 목록 번호를 입력 시 E의 문자와 실질적으로 플레이어에게 관여를 하게 되고

그로 인해 상태 보기 메뉴에서 스텟이 변하는 것을 구현하는 파트이다.

이 또한 플레이어 클래스에서 관리를 하게 하여서 어렵지 않게 구현했던 파트이다. 

2가지 정도 에러사항이 있었다면  장착 시 [E]를 표시하는 것과 스텟에 17 (+7) 처럼 따로 나눠저 있던 부분이다.

앞의 문제는 물음표 연산자를 처음에 사용할 줄 몰랐어서 해맸었고

뒤의 문제는 플레이어 클래스에는 내가 그냥 공격력 하나로만 관리를 하고 있었다. 그래서 플레이어 공격력과 장착한 무기 공격력을 별개로 관리하여 두 개를 합한 것을 17, 무기 공격력이 7 이런 식으로 계산을 해주었다.

 

5번째로는 위에서도 언급했던 상점이다. 상점에는 몇 가지 아이탬들이 되어있고 구매를 했는지 여부까지 알 수 있다.

나는 아이탬 또한 클래스로 관리했고 이 아이탬들이 모여있는 상점 또한 클래스로 구현하였다.

사실 이것보다 훨씬 좋은 구조가 있을 것 같긴 하지만 일단은 내가 할 수 있는 구조로 표현했다.

 

더보기
 public class Store
 {
     public const int SWORD = 1;
     public const int ARMOR = 2;
     // 상점에서 사고 팔고, 구매 여부
     public List<Item> Item {  get; set; }

     public Store() // 생성자
     {
         Item = new List<Item> // 음 어 아 예 배열로 어캐 바꾸냐 ;;
         {
             // 아이템 추가
             new Item(1,"수련자의 갑옷     ",  "방어력 +3  | 수련에 도움을 주는 갑옷이다.\t\t\t",0 ,3,1000,ARMOR),
             new Item(2,"무쇠갑옷          ",  "방어력 +9  | 무쇠로 만들어져 튼튼한 갑옷입니다.\t\t\t",0 ,9,1500, ARMOR),
             new Item(3, "스파르타 갑옷     ",  "방어력 +15 | 스파트라의 전사가 사용했다는 전설의 갑옷이다.\t",0 ,15,3500, ARMOR),
             new Item(4,"낡은 검           ",  "공격력 +2  | 쉽게 볼 수 있는 낡은 검이다\t\t\t",2 ,0,600, SWORD),
             new Item(5,"청동 도끼         ",  "공격력 +5  | 어디선가 사용됐던거 같은 도끼이다.\t\t\t",5 ,0,1500, SWORD),
             new Item(6,"스파르타의 창     ",  "공격력 +7  | 스파르타의 전사들이 사용했다는 전설의 창이다.\t",7 ,0,3500, SWORD),
             new Item(7,"크고 우람한 무언가",  "공격력 +15 | 굉장합니다.\t\t\t\t\t",15 ,0,4000,SWORD),
             new Item(8,"작고 초라한 어떤것",  "공격력 +6  | ...\t\t\t\t\t\t",6 ,0,600,SWORD)

         };
     }

 } // 상점 클래스

 

더보기
 public class Item // 아이템 클래스
 {
     // 식별번호,이름, 스텟(공격력, 방어력), 설명, 가격, 구매 여부, 장착 여부, 타입
     public int Id { get; set; }
     public string ItemName { get; set; }
     public string Tooltip { get; set; }
     public int ItemAttack { get; set; }
     public int ItemDefense { get; set; }
     public int Price { get; set; }
     public bool IsPruchase { get; set; }

     public bool IsEquip {  get; set; }

     public int type { get; set; } // 무기랑 방어구랑 나눠야되니깐

     public Item(int id, string itemName, string tooltip, int itemAttack, int itemDefense, int price,int type) // 생성자 
     {
         Id = id;
         ItemName = itemName;
         Tooltip = tooltip;
         ItemAttack = itemAttack;
         ItemDefense = itemDefense;
         Price = price;
         IsPruchase = false;
         IsEquip = false;
         this.type = type;
     }
 }

먼저 밑의 아이탬 클래스 부터 살펴 보면 필요한 파라미터를 정의하고 생성자를 만들어 주었다. 그 뒤 상점 클래스에서는 아이탬을 실질적으로 추가하고 그것을 리스트로 하여 관리하게 만들어 주었다. 

이번 파트에서 어려웠던 점을 꼬집자면 역시 구조였다. 아이탬들을 어디서 관리할 지, 스토어를 굳이 클래스로 또 따로 빼야되나 라는 고민을 꽤나 했던거 같은데 어찌저찌 구글링하다보니 굴러는 가서 수정은 안하는 걸로 .. 했다. 하하..

 

다음으로 기본 기능 구현의 마지막인 상점에서 아이탬 구매인데 이것 또한 스토어에어 아이탬 리스트를 관리하고 있고 아이탬 클래스에서 구매 여부를 플래그를 주어 사면 true, 아니면 false로 바꾸면서 구매여부를 확인하게 하였다.

상점 - 아이템 구매
필요한 아이템을 얻을 수 있는 상점입니다.

[보유 골드]
800 G

[아이템 목록]
- 1 수련자 갑옷    | 방어력 +5  | 수련에 도움을 주는 갑옷입니다.             |  1000 G
- 2 무쇠갑옷      | 방어력 +9  | 무쇠로 만들어져 튼튼한 갑옷입니다.           |  구매완료
- 3 스파르타의 갑옷 | 방어력 +15 | 스파르타의 전사들이 사용했다는 전설의 갑옷입니다.|  3500 G
- 4 낡은 검      | 공격력 +2  | 쉽게 볼 수 있는 낡은 검 입니다.            |  600 G
- 5 청동 도끼     | 공격력 +5  |  어디선가 사용됐던거 같은 도끼입니다.        |  1500 G
- 6 스파르타의 창  | 공격력 +7  | 스파르타의 전사들이 사용했다는 전설의 창입니다. |  구매완료

0. 나가기

원하시는 행동을 입력해주세요.
>>

 

그렇게 얼추 기본 기능 구현은 완료하게 되었고, 사실 여기까진 구글링해서 뒤적거리면서 그렇게 어렵지 않게 구현하였다.

그 뒤 내가 해볼 수 있는 도전 기능을 해보자 하고 시작하였는데 생각보다 시간도 많이 들었고 몰라서 같은 학우분들께 많이 물어보고 조언도 많이 들었던 시간이었다.

 

도전 기능에는

  1. 아이탬 정보를 클래스 / 구조체로 활용해보기
  2. 아이템 정보를 배열로 관리하기
  3. 아이템 추가 - 나만의 새로운 아이템을 추가
  4. 휴식기능 추가
  5. 판매하기 기능 추가
  6. 장착 개선
  7. 레벨업 기능 추가
  8. 던전입장 기능추가
  9. 게임 저장하기 추가

총 9가지가 있었는데 2, 9번 빼고 일단 기능은 구현은 됐다.

 

가장 먼저 아이탬 정보는 애초에 클래스로 관리했기 때문에 따로 구현안해도 구현되어 있었고

배열로 관리하기가 참 난관이었다. 배열로 바꾸다 보니 코드 전체를 다 뜯어 고쳤어야했고 막상 뜯어 고치기 실행도 안되는 오류 투성이라 어떻게 수습할 방법이 없었다. 작은 프로젝트를 만들어서 해봤을 땐 돌아갔는데 정작 본 게임에 이식하니 갑자기 오류만 한 300개 정도 뜨길래 구현하는 것을 포기하고 말았다. 다음에 기회가 되면 다시 구현해보도록 하겠다.

 

다음의 아이템 추가는 스토어 클래스에 있는 아이템 리스트에 2개 정도 추가로 입력해서 추가하는 걸로 구현하였고,

휴식 기능은

```csharp
스파르타 마을에 오신 여러분 환영합니다.
이곳에서 던전으로 들어가기전 활동을 할 수 있습니다.

1. 상태 보기
2. 인벤토리
3. 상점
4. 던전입장
5. 휴식하기

원하시는 행동을 입력해주세요.
>>
```
------------------------------------------------------------ 다음 화면

**휴식하기**
500 G 를 내면 체력을 회복할 수 있습니다. (보유 골드 : 800 G)

1. 휴식하기
0. 나가기

원하시는 행동을 입력해주세요.
>>
```

 

처럼 메인 메뉴에서 5번을 누르면 일정 골드를 차감하고 체력이 100으로 회복되는 기능을 만드는 것이었다.

물론 일정 골드 미만이면 경고를 출력하는 것 까지 사항이었고

입력을 받을 시  플레이어 클래스에 있는 파라미터 변수를 수정하는 방법을 사용하여 골드는 - 하고 체력은 100으로 바꿔주었다.

if(player.Gold >= 500)
{
    player.HP = 100; // 체력을 몇 채워야 될질 모르겠넹 
    player.Gold -= 500;
    Console.WriteLine("휴식을 완료했습니다.");
    Console.ReadKey();
}
else
{
    Console.WriteLine("Gold가 부족합니다.");
    Console.ReadKey();
}

 

다음으로 판매하기 기능 추가이다.

상점 - 아이템 판매
필요한 아이템을 얻을 수 있는 상점입니다.

[보유 골드]
800 G

[아이템 목록]
- 무쇠갑옷      | 방어력 +9  | 무쇠로 만들어져 튼튼한 갑옷입니다.           |  1800 G
- 스파르타의 창  | 공격력 +7  | 스파르타의 전사들이 사용했다는 전설의 창입니다. |  2700 G

0. 나가기

원하시는 행동을 입력해주세요.
>>

 

판매 하기 메뉴에 들어오면 원래의 85%의 금액으로 다시 되 팔 수 있다.

if (Input > 0 && Input <= player.Inventory.Count)
{
    Item sellItem = player.Inventory[Input - 1];
    sellItem.IsPruchase = false;
    player.UnEuqipItem(sellItem); // 장착 해제 이걸 못해서 시간을 얼마나 쓴겨..
    player.Inventory.Remove(sellItem);
    player.Gold += (int)(sellItem.Price * 0.85f); // 일단 정가로 팔게 냅둬봐
}
else
{
    Console.WriteLine("\n판매할 수 없는 아이템입니다.");
    Console.ReadKey();
}

 

위와 같이 입력 1부터 을 플레이어 인벤토리에 있는 수 사이로 제한을 하고 1번을 입력하면 실질적으로 아이탬 리스트에 있는 0번 아이템을 팔게 되기 때문에 Input - 1을 해주었다. 그 뒤 구매 여부를 false로 바꾸고 인벤토리에서 삭제시키고 골드를 85% 금액을 받는 식으로 구현이 되어있다.

 

다음으론 정말 오래 걸렸던 장착 개선이다.

지금까지 기능에선 무기나 방어구를 무한정 낄 수 있었다. 이제 여기서 최대로 무기 1가지, 방어구 1가지로 제한하는 기능을 만드는 것이다.

처음엔 플레이어 클래스에 무기칸, 장비칸을 만들어 1번 무기를 장착하면 무기칸에 1번 무기가 있다가 2번 무기로 바꾸면 무기칸에 2번 무기가 들어가게 만들어 볼려고 했었다. 사실 이것도 이렇게 구현하면 되겠지만 실제로 구현으로 들어가보니 로직 구현을 실수 했는지 장착이 계속 안되는 버그가 걸렸었다. 그래서 무기는 무기끼리 같은 타입, 방어구는 방어구끼리 같은 타입을 주어

무기 타입이 장착될 때 모든 무기를 장착 해제하고 내가 입력한 무기가 장착 되도록 구현하였다. 확장성을 고려하면 각 각의 장비칸을 만들어 주는 게 훨씬 좋을거 같다.

 

 if (temp >= 0 && temp < player.Inventory.Count)
 {
     Item selectItem = player.Inventory[temp];
     // 장착 정보는 플레이어가 가지고 있을 테니깐 플레이어 클래스에 장착 여부 판정 메서드 추가

     for (int i = 0; i < player.Inventory.Count; i++) // 그냥 다돌아서
     {
         if (player.Inventory[i].type == selectItem.type && !selectItem.IsEquip) // 타입이 같고, 끼고 있으면
         {
             player.UnEuqipItem(player.Inventory[i]); // 내 인벤토리에 있는 아이탬을 다 끈다.
             // 장착도 안되있는데 끄니깐 스텟이 이상해지는구나.
         }
     }

     if(!selectItem.IsEquip)
     {
         player.EuqipItem(selectItem);
         Console.WriteLine("장착 성공");
         Console.ReadKey();
     }
     else
     {
         player.UnEuqipItem(selectItem);
         Console.WriteLine("해제 성공");
             Console.ReadKey();
     }
 }

 

for구문을 보면 그냥 다돌아서 장비를 끼고 있고 타입이 같으면 인벤토리에 있는 모든 아이템을 장착 해제 시키고

장비를 안끼고 있으면 장착 성공 장비를 끼고 있으면 해제 성공의 조건문으로 들어가는 식으로 구현이 되었다.

 

다음은 레벨업 기능 추가이다.

- 던전을 여러번 클리어할 수록 레벨이 증가합니다.
    - Lv1 → Lv2 - 1회 클리어
    - Lv2 → Lv3 - 2회 클리어
    - Lv3 → Lv4 - 3회 클리어
    - Lv4 → Lv5 - 4회 클리어
- 레벨업시 기본 공격력이 0.5 방어력이 1 증가합니다.

 

레벨업에 필요한 건 경험치와 레벨마다 요구되는 경험치 통, 레벨, 이 3가지 정도이다.

레벨은 원래 플레이어 클래스에 들어있었으니 경험치와 경험치 통만 플레이어 클래스에 추가시켜 주었다.

그 뒤 경험치가 경험치 통과 같아지면 경험치를 0으로 초기화하고 레벨이 오르고 경험치 통을 +1 시켜주고 플에이어의 스텟을 올려주는 메서드를 플레이어 클래스에 추가해주어 쉽게 만들었다. 이것으로 플레이어가 던전에 들어가 클리어를 할 때 경험치만 +1 씩 해주면 레벨업 시스템이 정상적으로 작동할 것이다.

 

다음은 던전입장 기능 추가이다.

 

던전에는 3가지 난이도로 나뉘어져 있으며 각 각 권장 방어력이 있다. 권장 방어력 보다 높다면 던전을 클리어하게 되고 

체력이 20(+내 방어력 - 권장 방어력)~35( +내 방어력 - 권장 방어력)의 랜덤 값으로 줄어들게 된다. 권장 방어력 보다 내 방어력이 높다면 40% 확률로 실패하고 보상은 없이 체력만 감소하게 된다. 던전은 난이도와 그에 따른 추가 보상 퍼센트를 인자로 받은 메서드에서 생성되게 하였으며 20 ~35의 값은 Random을 통해 난수를 받아오게 되고 그 값에 자신의 방어력과 권장 방어력의 계산식에 의해 플레이어 체력이 감소하게 구현했고 클리어 보상은 플레이어 공격력과 보상 계산식을 조합하여 플레이어 골드를 더하는 식으로 계산했다. 사실 이 파트는 지금 코드를 안 보여주고 있는데, 글을 쓰고 보니 더 좋은 방향이 생각나서 내일 수정해서 제출을 해볼 생각이라 수정한 파트로 코드를 올리고 싶은 생각에 글로만 설명하였다.

 

마지막으로 저장 기능 추가인데 사실 저장 기능은 내가 비쥬얼스튜디오를 만지는 유니티를 만지든 두 곳 모두에서 안 써본 기능이라 지식이 아예 0이다. 그래서 구글링을 해서 필요한 변수들만 파일에 저장하는 식인거 같던데 아무리 해도 오류만 나고 진도는 안나가고 무슨 소리인지 이해가는 부분이 없어 일단은 스킵했다. 다음에 이 파트를 제대로 배우게 되면 수정 해보겠다.

 

 


 

회고

이렇게 이번주 개인 과제였던 TextRPG를 어떤식으로 구현하였는지 코드를 일부 설명하며 리뷰해보는 시간이 끝났다.

사실 월요일 이 과제를 설명받았을 때 부터 굉장히 재밌을 것 같아서 빨리 구현해보고 싶다. 라는 생각에 이번 주 강의를 후딱 끝내고 화요일부터 구현하기 시작했는데 강의 내용도 중요한 파트가 너무 많아서 TIL 작성이 좀 많이 밀려서 세세하게 설명하지 못한게 조금 아쉽긴하다. 생각보다 구현이 쉬운건 쉽게 풀었는데 애매한 파트에서 이상하게 해맸다. 내가 배운 것 내에서는 최대한 안헤매고 구조 설계하고 로직 짜고 하고 싶은데 아직 코딩 실력이 많이 부족한거 같다.