Unity_본캠프

[내일배움캠프 8일차] TextRPG 제작기 3

티백고래 2025. 4. 16. 21:04

1. 오늘의 할 일

RPG의 꽃은 던전, 그리고 몬스터이다. 하지만 모든 플레이어가 맨몸으로 탐험에 나서는 것은 아니다.

(물론 아예 서바이벌 게임이라면 가능하지만. RPG 게임은 아니다. 최소한 시작할 때 굵은 나뭇가지라도 주고 시작한다.)

 

그리고 나는 어제 아이템리스트를 만들었다. (이후, 아이템 리스트의 수정이 조금 더 거쳐졌다.)

하지만 리스트 만으로는 게임에 출력되지 않는다.

이건 따지고보면 아이템 프리펩에 들어갈 데이터를 만들어둔 것이지, 아직 생성은 하지 않은 상태와 같다.

즉, 상호작용이 전혀 불가능한 상태라는 것이다.

 

 

무릇 모험을 떠나려는 모험가라면 자신의 몸을 지킬 방어구와 무기는 필수!

던전이 모험의 꽃이라면 꽃구경하기 위해 모험가방을 싸야 한다.

 

아무튼, 그래서 내가 만들어야 하는 것
: 상점! 그리고 인벤토리!!! (  ^ ^)9 예에에

#가보자고

 

2. 작업 개시🛠️

 

1. 아이템 전시

 

우선 작업 시작 전, 아이템의 생성자를 다음과 같이 바꾸었다.

 

변경점

1. 해당 아이템의 장착여부(bool Equipped)가 true가 되면, 아이템의 앞에 -[E] 표시가 붙도록 해주었다.

2. 가격표시를 하는 것을, PriceGetPrice 두 개로 나누었다. Price는 콘솔창에 표시되는 string값, GetPrice는 해당 아이템이 실제 구매 및 판매될 때 사용하는 int 값이다.

(구매 완료시 Price는 "구매완료"로 변경되지만, GetPrice는 유지되어 판매에도 사용될 수 있다.)

 

 

그리고 형성된 리스트를 이용해, 구매한 아이템 리스트도 따로 형성할 수 있도록 구조를 잡아주었다.

 

아이템 리스트 코드

더보기

            public static List<Item> itemList = new List<Item>()
            {  //장착여부, 아이템이름, 설명, 가격, 공격력, 방어력
                new Item("-[ ]","수련자 갑옷", "수련에 도움을 주는 갑옷입니다.", 1000, 0, 5),
                new Item("-[ ]","무쇠갑옷", "무쇠로 만들어져 튼튼한 갑옷입니다.", 1800, 0, 9),
                new Item("-[ ]","스파르타의 갑옷", "스파르타 전사들이 입던 갑옷입니다.", 3500, 0, 15),
                new Item("-[ ]","낡은 검", "쉽게 볼 수 있는 낡은 검입니다.", 600, 2, 0),
                new Item("-[ ]","청동 도끼", "청동으로 만들어진 도끼입니다.", 1500, 5, 0),
                new Item("-[ ]","스파르타의 창", "스파르타 전사들이 사용하던 창입니다.", 2000, 7, 0)
            };

            //구매한 아이템 리스트(SoldOut이 true인 아이템만 출력)
            public static void reciptItem()
            {
                foreach (var item in Item.itemList)
                {
                    if (item.SoldOut == true)
                    {
                        Console.WriteLine($"{item.EquipItem} {item.Name} \t| 공격력 +{item.Attack} \t| 방어력 +{item.Defense} \t| 가격: {item.Price}G");
                    }
                }
            }

 

그리고 이 상점의 아이템들을 골조로 잡아서, 상점 기능에 집어넣었다.

더보기

protected static void Store()
{
    Console.Clear();
    Console.WriteLine("상점에 오신 것을 환영합니다.");
    Console.WriteLine($"{player.name}님의 골드: {player.gold}G");

    Console.WriteLine("==Store==");

    Item.itemList.ForEach(item =>
    {
        Console.WriteLine($"{item.EquipItem} {item.Name} \t| 공격력 +{item.Attack} \t| 방어력 +{item.Defense} \t|{item.Description}\t|가격: {item.Price}G");
    });

    Console.WriteLine("1. 아이템 구매");
    Console.WriteLine("2. 아이템 판매");
    Console.WriteLine("0. 나가기");
    string Sselect = Console.ReadLine();
    switch (Sselect)
    {
        case "1":
            ItemPurchase(player);
            break;
        case "2":
            ItemSell(player);
            break;
        case "0":
            GameStart(player);
            break;
        default:
            Console.WriteLine("잘못된 입력입니다.");
            Store();
            break;
    }
}

이제 리스트를 넣어봤으니, 잘 출력되는지 확인해보도록 한다.

실행하면, 이렇게 나온다.

 

player 닉네임 OK.

아이템 리스트: 이름/공격력/방어력/설명/가격 OK


문제

1. 정렬을 하기 위해 \t 를 사용했는데, 글자의 길이가 제각각이다보니 정렬이 된듯만듯 하다...

    안 쓰는 것보단 낫나? 싶은데 글자수가 작은 파트(공격력, 방어력) 은 빈 공간이 너무 크다.

2. -[ ]을 그대로 출력해버렸다. :9 실수.

3. {item.Price}G를 이용해 넣긴 했는데, 생각해보니까 저렇게 두면 구매가 완료되었을 때 "구매완료"G가 된다. Price는 어차피 String값이므로, G를 Price에 포함시키자.(어차피 GetPrice는 표시되지 않는다.)

 

일단 1은 그대로 두고, 2와 3만 가볍게 수정했다.

 

결과

오히려 없애니까 더 깔끔한 것 같은데...기분탓인가?

혼자 이름이 긴 스파르타의 갑옷 빼고는 깔끔하게 정렬된 기분을 준다.(설명 뒤에 가격만 위치가 다른 건 감수할 수 있다.)

 

그리고 이 상태에서 아이템 구매로 들어가면, 아이템 앞에 넘버링이 되어야 한다.

더보기

static void ItemPurchase(Player player)
{

    //중략

    int index = 1; //아이템 리스트 넘버링
    Item.itemList.ForEach(item =>
    {
        Console.WriteLine($"{index++}. {item.Name} \t| 공격력 +{item.Attack} \t| 방어력 +{item.Defense} \t|{item.Description}\t|가격: {item.Price}");
    });
    //아직 판매메서드로 넘길 로직을 만들지 못했다.
}

index를 이용하여, 1부터 마지막 아이템까지 넘버링이 되어 출력되도록 만들었다.

 

결과

 

휴, 무사히 아이템 이름 앞에 1부터 6까지의 넘버링이 들어갔다.

 

Q. 2번 아이템 판매는 확인하지 않으시나요?

A. 아직 장비도 없어서 확인도 못해요... 구매/판매 메서드를 만들어놨는데 적용이 아직입니다.

 

그리고 기능이 없다면?

 

만드는 것입니다. 그것이 코딩이니까.

 

2. 아이템 구매/판매

헤헤 구매로직 짰고 판매로직도 짰는걸~
그럼 넣기만 하면 되겠는걸~

 

현실: 

하하! 멍청한놈! 어림도 없다!!!

 

 

확인차 다시 실행해서 똑같은 행동을 반복했다.(오류아님)

 

현시점 문제
1. 아이템 구매는 정상적으로 되었으나, 구매한 아이템의 가격이 "판매 완료"로 변경되지 않음.

2. 소지금이 부족한데도 구매가 가능함. (빚을 진 모험가)

3. 아이템 장착을 선언했음에도 아이템 장착 표시가 뜨지 않음

4. 값이 변한 것을 보니까 일단 어찌저찌 무쇠갑옷은 장착한 것 같긴 한데... 이거 구매리스트가 아니라 아이템 리스트 번호대로 가는 것 같다.(+n 값이 출력되지 않는 것은, 아직 값을 넣지 않았기 때문이다)

 

 

수정방안
1&3. class 생성자에서 선언한 Equipped=true 시 변하는 조건문은 의미가 없다. Price 역시 마찬가지이다.

    →하지만 구매로직에서 Equipment 를 변경하면 적용될 것이다. Price 역시 마찬가지.

2. 구매 로직에 금액 비교 로직이 없는지 확인할 것.

4. 1~3을 해결한 후, 리스트의 변경상태를 확인할 것.

 

일단 가장 빠른 2번부터 확인하기로 한다.

아이고 맙소사

이걸…빼먹어?

그래 코드가 500줄을 넘었으니까 그럴 수 있지....

그럼 지금부터 수정 들어간다.

 


 

아이템 구매 매서드

더보기

 public void Buy()
 {
     if (SoldOut == false) //안 팔렸다면
     {
         if (player.gold >= GetPrice) //돈 비교
         {
             Console.WriteLine("상점 주인: 크하하, 어때, 마음에 드나?");
             Console.WriteLine($"{Name}을(를) 구매하였습니다.");
             player.gold -= GetPrice; //구매 후 골드 감소
             SoldOut = true; //판매 완료
             Price = "구매완료"; //구매가 완료로 변경
             //아이템을 구매하면 reciptItem에 add됨
             reciptItem.Add(this); //구매한 아이템 리스트에 추가

             Console.WriteLine("상점 주인: 가방에 넣어줄까, 아니면 입고 갈래?");

             Console.WriteLine($"{Name}을(를) 장착하시겠습니까? (Y/N)");
             string answer = Console.ReadLine();
             switch (answer)
             {
                 case "Y":
                     Console.WriteLine($"{Name}을(를) 장착하였습니다.");
                     //플레이어의 공격력과 방어력 증가
                     player.attack += Attack;
                     player.defense += Defense;

                     //장착된 아이템의 리스트 넘버링을 [E]로 출력
                     EquipItem = "[E]"; //장착 후 아이템 리스트에서 [ ] 제거

                     //아이템의 장착 여부를 true로 설정
                     Equipped = true;
                     break;
                 case "N":
                     Console.WriteLine($"{Name}을(를) 장착하지 않았습니다.");
                     break;
                 default:
                     Console.WriteLine("잘못된 입력입니다.");
                     break;
             }
         }
         else
         {
             Console.WriteLine("상점 주인: 이런, 돈이 부족한 건 아닌가?");
             Console.WriteLine($"{Name}을(를) 구매할 수 없습니다. 골드가 부족합니다.");
         }

     }
     else
     {  
         Console.WriteLine("상점 주인: 그 물건은 이미 팔렸어. 다른 걸 찾아봐.");
         Console.WriteLine($"{Name}은(는) 이미 판매되었습니다.");
     }
     ItemPurchase(player); //구매 후 상점으로 이동
 }

해당 메서드 추가요소

1. 현재 소지금과 아이템 가격을 비교하여 구매 불가 판정

구매할 돈이 부족하면, 구매 실패 판정

 

2. 구매 시 즉시 착용여부 선택

구매 후, 상점에서 즉시 해당 아이템을 착용할지 질문한다.

Y를 선택하면 즉시 장착하고, 현재 스탯창에 반영된다.

 

또한, 인벤토리에도 정상적으로 착용표시가 나는 것을 확인할 수 있다.

벤토리 내에서 표시되는 '가격'은, 상점에 판매할 때의 가격(원가*0.8)으로 변경된다.

 

3. 구매가 완료되면, 구매완료 표시로 변경됨

가장 상단에 있던 수련자의 갑옷이 구매완료로 변경되었다.

그리고 구매 완료된 아이템을 구매하려고 하면 어떻게 나오는가.

 

 

이미 구매가 완료되었다며 골드 차감도 되지 않는다! 중복 구매가 방지되었다는 뜻!

 

 

그리고, 예시 내용들을 보면 알겠지만 내친김에 'NPC'도 추가해 보았다.

털털하고 호탕한 상점 주인을 상상해주면 좋겠다.

아무 말 없이 진행하는 것보다, 상점이 있는 게 좀 더 마을같다는 느낌이지 않은가.

 

여기서 위에서 말한 1~4번 문제가 해결되었다.

(사실, 4번은 수정이 더 필요하다. 지금은 기본값이 상수이니, 그냥 player attack과 defense 수치에서 기본치를 상수로 뺐다. 기본능력치가 계속 상수라면 모르겠지만, 나중에 레벨업을 하면 기본 능력치도 상승해야 한다.)
(player.attack과 defense의 계산 방식을 즉시 계산이 아니라, +item.attack 값을 더하는 식으로 변경해야 한다.)

 

일단, 구매가 해결되었으니 판매부분도 봐야 한다.


아이템 판매 메서드

더보기

public void Sell()
{
    //판매할 수 있는 조건: SoldOut이 true이고 Equipped가 false일 때
    //Equipped가 true일 때: 장착 해제 후 판매 가능
    //판매가는 구매가의 80%로 설정
    if (SoldOut == true && Equipped == false)
    {
        Console.WriteLine($"{Name}을(를) 판매하였습니다.");
        player.gold += (int)(GetPrice * 0.8); //판매 후 골드 증가
        Price = GetPrice.ToString() + "G"; //"판매완료"를 다시 원가로 표시
        SoldOut = false;

        //판매하면 reciptItem에서 삭제
        reciptItem.Remove(this);
    }
    else if (SoldOut == true && Equipped == true)
    {
        Console.WriteLine("상점 주인: 그 물건은 장착중이잖아? 정말 팔 건가?");
        Console.WriteLine(" ");
        Console.WriteLine(Name + "을(를) 장착 해제 하고 판매하시겠습니까? (Y/N)");
        string answer = Console.ReadLine();
        switch (answer)
        {
            case "Y":
                Console.WriteLine($"{Name}을(를) 장착 해제 하였습니다.");
                Console.WriteLine($"{Name}을(를) 판매하였습니다.");

                Equipped = false;
                SoldOut = false;

                EquipItem = "[ ]"; //장착 해제 후 아이템 리스트에서 [E] 제거
                Price = GetPrice.ToString() + "G"; //판매 후 가격 초기화

                player.attack -= Attack; //플레이어의 공격력 감소
                player.defense -= Defense; //플레이어의 방어력 감소
                player.gold += (int)(GetPrice * 0.8); //판매 후 골드 증가

                //판매하면 reciptItem에서 삭제
                reciptItem.Remove(this); //구매한 아이템 리스트에서 삭제

                ItemSell(player); //판매 후 상점으로 이동
                break;
            case "N":
                Console.WriteLine("상점 주인: 뭐야, 그만 둘건가?");
                Console.WriteLine($"{Name}은(는) 판매되지 않았습니다.");
                ItemSell(player);
                break;
            default:
                Console.WriteLine("잘못된 입력입니다.");
                ItemSell(player);
                break;
        }
    }
    else
    {
        //정상적인 플레이에서는 이 조건이 발생하지 않음.
        Console.WriteLine("상점 주인: 그 물건은 안 살 거야. 다른 데 알아보라고.");
        Console.WriteLine($"{Name}은(는) 판매할 수 없습니다.");
    }
    ItemSell(player); //판매 후 상점-판매로 이동

}

해당 메서드 추가요소

1. 장비된 아이템을 판매할 시, 장비를 해제하고 판매할 것인지 재질문함.

아이템을 장비한 경우에는 장비 해제를 한 후에 판매하도록 만들었다.

그렇기 때문에, 판매 버튼을 눌렀어도 N을 누르면 판매 취소가 된다!

 

정상적으로 판매한 모습

판매가 완료되면 판매할 아이템이 존재하지 않게 된다. ( ' ')9

당연히, 해당하는 키도 없어지므로 존재하지 않는 아이템을 판매할 일도 없다.

그럼 판매된 아이템은 정상적으로 돌아오는가?

정답: 예

구매완료 버튼이 사라지고, 600G라는 가격으로 되돌아온다.

판매가는 480G 였으니 구매/판매 로직이 정상적으로 작동함을 알 수 있다.

물론, 아이템을 제거하면 능력치도 되돌아간다. 참고하자.

 

같은 아이템을 계속 사고 팔고를 반복하면 거지꼴을 못 면하게 될 테니 아이템 구매는 신중하게( ^^)9


특별상점 추가 및 기타 사항

만들다보니까, '특별상점'도 만들어보고 싶어져서 급하게 만들었다.

특별 상점의 진입 조건은 상점 10회 방문. 단, 반드시 "마을"에서 "상점"으로 들어간 것만 카운트 된다.

(그야, 상점에서 물건 사고 상점으로 들어가면 그냥 상점에 있는 것 아닌가.)

 

우선, class Player의 요소 중에, storecount를 생성했다.

 

이것을 상점방문 횟수로 지정하고, 마을→상점 으로 이동하는 로직에 player.storecount++ 를 추가했다.
또한, 상점 방문 횟수에 따라 NPC인 상점 주인의 반응을 다르게 했다.

그도 그럴 것이, 처음 찾아온 모험가를 냅다 상점 주인이 아는 체를 하는 건 어색한 걸!

 

상점 장면 중, NPC 대화문+선택지

더보기

 if (player.storecount == 1)
 {
     Console.WriteLine("상점 주인: 오, 처음보는 얼굴이군. 새로운 모험가인가? 어서와, 어서와!");
     Console.WriteLine("상점 주인: 필요한 게 있으면 얼마든지 말하라고!");
 }
 else if(player.storecount > 1&&player.storecount< 10)
 {
     Console.WriteLine("상점 주인: 오, 또 왔군. 이번엔 무엇을 사러왔나?");
 }
 else if (player.storecount == 10)
 {
     Console.WriteLine("상점 주인: 이야, {player.name} 아닌가! 오늘은 무슨 일이야?");
     Console.WriteLine("상점 주인: 이렇게 자주 와주니 내가 다 기쁘군.");
     Console.WriteLine("단골에게는, 특별 상점을 열고 있다네. 조금 비싸긴 하지만 말이야! 하하!");
 }
 else
 {
     Console.WriteLine("상점 주인: 어서오게, {player.name}!");
     Console.WriteLine("상점 주인: 오늘 모험은 어땠는가?");
 }

 

 Console.WriteLine("=============================");
 Console.WriteLine("  ");
 Console.WriteLine("1. 아이템 구매");
 Console.WriteLine("2. 아이템 판매");
 if(player.storecount >= 10) //플레이어의 방문 횟수가 10번이 넘으면 항상 공개
 {
     Console.WriteLine("3. 특별 상점");
 }
 Console.WriteLine("0. 나가기\n ");

물론, 3번에 특별 상점을 할당했으니 굳이 열지 않아도 눌러볼 수는 있다.

하지만…그렇게 눌렀는데 바로 진입되면 괜히 조건이 아니다.

ㅠㅠ!


조건을 만족하지 못하면, 애초에 상점 주인이 허락하지 못하게 만들었다.

그럼 반대로 조건을 만족하면 어떨까?

조건을 만족할 때까지 왔다갔다 하겠다.

일단 두 번째 방문부터는 이런 문구가 뜬다. 노가다로 왔다갔다 해보도록 하자.

(민폐 모험가)

10번을 채우면 이렇게 나온다!(와!)

10번을 넘기자 이젠 이름까지 불러주며 아는 척을 해준다.

 

이제는 사담을 나눌 수 있을 정도로 친해진 모험가와 상점주인.

이렇게, 특별 상점 칸도 뜬다. 그럼 이제부터 3번을 눌러 들어가보자.

 

YES!
이녀석들도 제대로 장착이 되는지 확인해보자.

장착도 정상적으로 되고, 장착 표시도 정상적으로 들어간다. b

 

특별상점 완성!

 


 

3. 스탯 픽스

아까 아이템 구매/판매 문제에서 주먹구구식으로 해결된 문제가 있었다.

바로 『아이템 공격력』이 정상적으로 더해지지 않는 문제.

좀 더 정확하게는, "공격력"과 "방어력"이, player.attack/player.defense로만 결정되다 보니 상수로 빼는 수밖에 없게 되는 문제였다.

어떻게 처리해야할지 고민하다가 떠올렸다.

attack, defense와 별개의, itemAttack, itemDefense를 플레이어 클래스 생성자에 넣으면 어떨까?

즉, attack과 defense는 기본치를, itemAttack과 itemDefense는 0에서 시작해서 아이템을 장착할 때 +하면 된다!

그리고 이렇게 하면, 각 직업마다 다른 능력 기본치를 가질 수도 있게 만들 수 있다!!

 

수정된 Player Status 코드

더보기

 static void Status(Player player)
 {
     //플레이어 스탯 반영
     //장비 착용 시 공격력, 방어력 증가를 표시함 (공격력 7 장비를 장비시, 기본값+장비보정치 (+장비보정치)로 표시)

     Console.WriteLine($"{player.name}님의 현재 상태입니다.");
     Console.WriteLine("==Status==");
     Console.WriteLine($"이름: {player.name}");
     Console.WriteLine($"레벨: {player.level}");
     Console.WriteLine($"직업: {player.job}");
     Console.WriteLine($"공격력: {player.attack+player.itemAttack} (+{player.itemAttack})");
     Console.WriteLine($"방어력: {player.defense+player.itemDefense} (+{player.itemDefense})");
     Console.WriteLine($"체력: {player.hp}");
     Console.WriteLine($"골드: {player.gold}");

// 중략
}

이렇게 해두면, 아이템의 공격력과 방어력이 따로 계산되기 때문에 이후 레벨업에도 문제없다.

수련자 갑옷이 잘 적용되고, 아이템의 보정치도 잘 보이는 상태.

하지만 여러 아이템을 착용했을 때도 잘 적용되는 지, 확인해봐야 한다.

때문에 플레이어 생성 시 골드를 5000으로 변경하고, 아이템 3개를 구매해 장착해 보았다.

 

결론: 여러 아이템을 착용해도 내가 원하는 의도대로 잘 작동한다!

끼얏호 ( ' ')9!

스탯 부분은 이것으로 클리어 된 것이다.

 

4. 내일의 할 일

내일은 마침내, RPG의 꽃 던전을 만들 생각이다.

정확히는

1. 던전 구현

2. 여관 구현

3. 무기/갑옷의 구분(각각 1개씩만 장착하도록!)

4. Console.Clear를 할 수 있는 단축키 생성(언제든 화면을 깨끗하게 정리할 수 있도록)