Unity 내일배움캠프 TIL 0817 | 텍스트 RPG 설계 및 구현 | 근데 이제 맨땅2

cheeseonrose·2023년 8월 17일
1

Unity 내일배움캠프

목록 보기
13/89
post-thumbnail

오늘은 4주차 과제 뿌시는 날 ~.~
스크롤 압박? 공포? 뭐 암튼? 주의?

텍스트 RPG 만들기

게임 흐름

  1. 플레이어 닉네임 정하기
  2. 플레이어 생성
  3. 플레이 선택하기
    • 사냥하기
      • 자동 사냥 시스템
      • 플레이어는 몬스터를 사냥해서 돈과 경험치를 얻을 수 있음
    • 아이템 인벤토리 열기
      • 플레이어가 가진 아이템 목록 표시
      • 플레이어는 아이템을 사용하거나 인벤토리를 닫을 수 있음
      • 인벤토리가 비었다면 잠시 뒤 자동으로 닫힘
    • 상점 열기
      • 상점에 현재 진열된 아이템 목록 표시
      • 상점 업데이트 가능
      • 플레이어는 아이템을 구매하거나 상점을 닫을 수 있음
  4. 게임 종료
    • 플레이어가 잡은 몬스터 수, 플레이어 경험치 표시 후 종료

전체 설계

  • 진짜 오랜만에 클래스 다이어그램이라는걸 그려봤는데 사실 가물가물해서 이게 맞는지도 모르겠음
  • 물론 기억 안 나니까 일단 상속만 표시함 저게 상속 아니면 어쩌지.

Player

  • 프로퍼티
    • 이름
    • 체력
    • 방어구 체력
    • 공격력
    • 경험치
    • 현재 플레이어 인벤토리에 있는 아이템 리스트
  • 메서드
    • 공격(몬스터 이름)
      • Player가 몬스터를 공격합니다!
    • 아이템 구매(아이템)
      • 플레이어 인벤토리 리스트에 아이템 추가
      • 플레이어 돈에서 아이템 가격만큼 빼기
    • 인벤토리 크기 반환
      • 아이템 인벤토리 리스트 크기 반환
    • 인벤토리 열기
      • 아이템 인벤토리 리스트 요소 출력
    • 아이템 사용(아이템 인덱스)
      • 아이템 인벤토리 리스트의 인덱스번째 아이템을 사용합니다 출력
      • 아이템 타입에 따라 체력, 방어구 체력, 공격력을 증가
      • 리스트에서 아이템 제거

Monster

  • 프로퍼티
    • 이름
    • 체력
    • 공격력
    • 보상
    • 경험치
  • 메서드
    • 공격
      • 몬스터 누구가 얼마만큼의 공격을 합니다 출력
  • Monster 종류
    • 슬라임
    • 멧돼지
    • 독버섯
    • 메이플아님.

Item

  • 프로퍼티
    • 이름
    • 타입 (0: HP, 1: MP, 2: SP)
    • 가치 (HP 가치 100이면 체력 100 회복)
    • 가격
  • 메서드
    • 아이템 사용
      • 체력/방어구체력/공격력이 얼마만큼 증가합니다 출력

구현

  • Main 함수
    • Main 함수의 로직은 별거 없다
      플레이어 닉네임을 정하고, while문을 돌면서 플레이어 선택에 따라 플레이 방식에 맞는 함수를 실행시켜준다.
      게임 종료 선택시 while문
static void Main(string[] args)
{
	Console.Title = "Welcome to Text RPG!";
    Console.Write("Player 닉네임을 입력해주세요 : ");
    playerName = Console.ReadLine();
    player = new Player(playerName, PLAYER_HP, PLAYER_SHIELD, PLAYER_POWER, 500, 0, new List<Item>());
    
    UpdatePlayerInfo();
    
    bool isGameOver = false;
    
    while (!isGameOver)
    {
    	Console.Clear();
        UpdatePlayerInfo();
        
        switch (playState)
        {
        	case 0:
            	SelectPlayState();
                break;
            case 1:
            	Hunting();
            	break;
            case 2:
            	OpenItemInventory();
            	break;
            case 3:
            	OpenStore();
            	break;
            case 4:
            	EndGame();
            	isGameOver = true;
            	break;
        }
     }
 }
  • UpdatePlayerInfo()
    • 플레이어의 정보를 업데이트하는 함수
    • 처음 시작할 때와 아이템 사용, 몬스터에게 공격당했을 때 상태를 보여주기 위함
static void UpdatePlayerInfo()
{   
	Console.WriteLine();
    Console.WriteLine("닉네임 {0}\t체력 {1}\t보호구 체력 {2}\t공격력 {3}\t$ {4}\t경험치 {5}",
    	player.Name,
    	player.HP,
    	player.Shield,
    	player.Power,
    	player.Money,
    	player.Exp);
}
  • SelectPlayState()
    • 플레이어의 상태를 설정하는 함수
static void SelectPlayState()
{
	Console.WriteLine();
    Console.WriteLine("1: 사냥하기\t2: 아이템 인벤토리 열기\t 3: 상점 열기\t4: 게임 종료");
    Console.Write("플레이를 선택하세요 : ");
    playState = getPlayerSelect(1, 4);
}
  • getPlayerSelect(int start, int end)
    • 플레이어가 선택할 일이 생길 때 호출하는 함수
    • start부터 end 사이의 숫자를 입력해서 입력값이 유효한지 확인해줌
    • 올바른 값을 입력하면 입력한 값을 리턴
static int getPlayerSelect(int start, int end)
{
	int select = 0;
    bool isNum = false;
    while (true)
    {
    	isNum = int.TryParse(Console.ReadLine(), out select);
        if (!isNum || (select < start || select > end))
        {
        	Console.WriteLine("잘못된 선택입니다. 다시 고르세요");
        }
        else break;
    }
    return select;
}
  • Hunting()
    • 중간중간 있는 Thread.Sleep은 게임 속도 조절을 위해 막 넣은거라서..무시 부탁..
    • 우선 현재 사냥할 몬스터가 없으면 UpdateMonsterList 함수를 통해 몬스터 리스트를 갱신한다.
    • 몬스터 리스트를 foreach로 돌면서 하나씩 몬스터 처리
      • 플레이어는 공격 -> 몬스터 체력 감소
      • 1/5의 확률로 몬스터도 공격을 함 (몬스터 타입에 따라 확률 바꿔줘도 좋을 듯)
      • 만약 플레이어의 방어구가 있다면 방어구 체력 먼저 감소 뒤 플레이어 체력 감소
      • 몬스터로 인해 체력이 줄었으므로 플레이어 상태 갱신 UpdatePlayerInfo 실행
    • 만약 몬스터가 죽었다면 처치했다는 문구 출력
      • 플레이어의 경험치와 돈에 몬스터의 경험치 및 보상을 더함
      • 처리한 몬스터 수 증가
    • 만약 플레이어가 죽었다면 플레이어가 사망했다는 문구 출력
      • 게임 상태를 게임 종료 값으로 변경
      • 바로 return해서 함수 종료
    • 하나의 몬스터 리스트를 다 처리했다면 계속 진행할지 사냥을 종료할지 정함
      • 사냥 종료시 게임 상태 값 변경
      • 계속 진행시 몬스터 리스트를 새로 만들어서 비워줌
static void Hunting()
{
	while (playState == 1)
    {
    	if (monsterList.Count == 0)
        {
        	UpdateMonsterList();
        }
        else
        {
        	Console.WriteLine();
        	Console.WriteLine("사냥을 시작합니다!");
        	foreach (Monster monster in monsterList)
        	{
            	Console.WriteLine();
                Console.WriteLine("앗! 야생의 {0}이(가) 나타났다!", monster.Name);
                while (monster.HP > 0 && player.HP > 0)
                {
                	Thread.Sleep(700);
                	Console.WriteLine();
                    player.Attack(monster.Name);
                    monster.HP -= player.Power;                         
                    int monsterAttack = new Random().Next(0, 5);
                    if (monsterAttack == 1)
                    {
                    	Console.WriteLine();
                    	monster.Attack();
                    	if (player.Shield > 0)
                        {
                        	if (player.Shield - (int)(monster.Power * PLAYER_SHIELD_REDUCE) >= 0)
                            {
                            	player.Shield -= (int)(monster.Power * PLAYER_SHIELD_REDUCE);
                            } else
                         	{
                            	int restDamage = monster.Power - (player.Shield * 2);
                                player.Shield = 0;
                                player.HP -= restDamage;
                            }
                        } else
                        {
                        	player.HP -= monster.Power;
                        }
                        UpdatePlayerInfo();
                        Thread.Sleep(500);
                    }
                }
                
                Thread.Sleep(700);
                
                if (monster.HP <= 0)
                {
                	Console.WriteLine();
                    Console.WriteLine("{0}을(를) 처치했습니다!", monster.Name);
                    Console.WriteLine("경험치 {0}을(를) 얻었습니다!", monster.Exp);
                    player.Exp += monster.Exp;
                    player.Money += monster.Reward;
                    huntedMonsterCnt++;
                }
                else if (player.HP <= 0)
                {
                	Console.WriteLine();
                    Console.WriteLine("{0}이(가) 사망했습니다!", player.Name);
                    playState = 4;
                    Thread.Sleep(800);
                    return;
                }
            }
            Console.WriteLine();
            Console.WriteLine("1: 계속 진행\t0: 사냥 종료");
            int select = getPlayerSelect(0, 1);
            if (select == 0)
            {
            	playState = 0;
            } else
            {
            	monsterList = new List<Monster>();
                Console.Clear();
                UpdatePlayerInfo();
            }
        }
    }    
}
  • UpdateMonsterList()
    • 몬스터 리스트 갱신 함수
    • 각 몬스터는 1/3의 확률로 만들어짐
    • 리스트 하나당 5마리의 몬스터 생성
      • 스테이지 같은걸 만든다면 for 문의 5를 상수로 바꿔줘서 조절해주면 좋을 듯
static void UpdateMonsterList()
{
	Console.WriteLine();
    Console.WriteLine("새로운 몬스터를 생성합니다!");
    
    for (int i = 0; i < 5; i++)
    {
    	int monsterType = new Random().Next(0, 3);
        switch (monsterType)
        {
        	case 0:
            	monsterList.Add(new Slime(SLIME_NAME, SLIME_HP, SLIME_POWER, SLIME_REWARD, SLIME_EXP));
                break;
            case 1:
            	monsterList.Add(new Boar(BOAR_NAME, BOAR_HP, BOAR_POWER, BOAR_REWARD, BOAR_EXP));
            	break;
            case 2:
            	monsterList.Add(new Toadstool(TOADSTOOL_NAME, TOADSTOOL_HP, TOADSTOOL_POWER, TOADSTOOL_REWARD, TOADSTOOL_EXP));
            	break;
        }
    }
}
  • OpenItemInventory()
    • 플레이어의 인벤토리를 여는 함수
    • 보유하고 있는 아이템이 없으면 2초 뒤 자동 종료
    • 아이템이 있다면 player.OpenItemInventory 함수로 인벤토리의 아이템 리스트 출력
    • 아이템을 사용할 것인지 인벤토리를 닫을 것인지 물어봄
      • 아이템 사용이면 아이템 번호를 선택한 뒤, 그 값을 player.UseItem으로 전달
      • 인벤토리 닫기면 플레이 상태 업데이트 후 종료
static void OpenItemInventory()
{
	while (playState == 2)
    {
    	if (player.GetItemCount() == 0)
        {
        	Console.WriteLine();
            Console.WriteLine("보유하고 있는 아이템이 없습니다!");
            Console.WriteLine("인벤토리를 닫습니다");
            Thread.Sleep(2000);
            playState = 0;
            break;
        }
        
        Console.WriteLine();
        Console.WriteLine("{0}의 아이템 인벤토리", player.Name);
        player.OpenItemInventory();
        
        Console.WriteLine();
        Console.WriteLine("1: 아이템 사용\t0: 인벤토리 닫기");
        Console.Write("선택: ");
        int select = getPlayerSelect(0, 1);
        if (select == 0)
        {
        	playState = 0;
        }
        else
        {
        	Console.WriteLine();
            Console.Write("아이템 번호 선택: ");
            int itemIdx = getPlayerSelect(1, player.GetItemCount()) - 1;
            player.UseItem(itemIdx);
            Thread.Sleep(1000);
            Console.Clear();
            UpdatePlayerInfo();
        }
    }
}
  • OpenStore()
    • 상점을 여는 함수
    • 상점 아이템 리스트가 비었다면 UpdateStore 함수로 상점 업데이트
    • 상점 아이템 목록 출력 후 아이템을 구매할 것인지, 상점을 업데이트할 것인지, 상점을 나갈 것인지 고름
      • 아이템 구매시 BuyItem 함수 실행
      • 상점 업데이트시 상점 아이템 리스트 새로 생성
      • 상점 닫기시 게임 플레이 상태 업데이트 후 종료
static void OpenStore()
{
	while (playState == 3)
    {
    	if (storeItemList.Count == 0)
        {
        	UpdateStore();
        }
        else
        {
        	Console.WriteLine();
            Console.WriteLine("상점 아이템 목록");
            int idx = 1;
            foreach(Item item in  storeItemList)
            {
            	Console.WriteLine("{0}. 종류 {1}\t효능 +{2}\t가격 {3}", idx, item.Name, item.Value, item.Price);
                idx++;
            }
            
            Console.WriteLine();
            Console.WriteLine("1: 아이템 구매\t2: 상점 업데이트\t0: 상점 나가기");
            Console.Write("선택: ");
            int select = getPlayerSelect(0, 2);
            switch (select)
            {
            	case 0:
                	playState = 0;
                    break;
                case 1:
                	BuyItem();
                	Thread.Sleep(1000);
                	Console.Clear();
                	UpdatePlayerInfo();
                	break;
                case 2:
                	storeItemList = new List<Item>();
                	break;
            }
        }
    }
}
  • UpdateStore()
    • 상점 업데이트 함수
    • 5개의 상점 아이템을 업데이트 함 (몬스터 리스트 갱신과 비슷)
    • 각 아이템의 가치와 가격은 min 값과 max 값 사이에서 랜덤으로 정해짐
      • randomValueAndPrice 함수를 통해 랜덤한 값을 생성해와서 아이템 생성자에 전해줌
static void UpdateStore()
{
	Console.Clear();
    UpdatePlayerInfo();
    Console.WriteLine();
    Console.WriteLine("상점을 업데이트합니다!");
    
    for (int i = 0; i < 5; i++)
    {
    	int potionType = new Random().Next(0, 3);
        switch (potionType)
        {
        	case 0:
            	int[] randomHPValues = randomValueAndPrice(HP_MIN_VALUE, HP_MAX_VALUE, HP_MIN_PRICE, HP_MAX_PRICE);
            	storeItemList.Add(new HealthPotion(HP_NAME, HP_TYPE, randomHPValues[0], randomHPValues[1]));
            	break;
            case 1:
            	int[] randomMPValues = randomValueAndPrice(MP_MIN_VALUE, MP_MAX_VALUE, MP_MIN_PRICE, MP_MAX_PRICE);
            	storeItemList.Add(new ManaPotion(MP_NAME, MP_TYPE, randomMPValues[0], randomMPValues[1]));
            	break;
            case 2:
            	int[] randomSPValues = randomValueAndPrice(SP_MIN_VALUE, SP_MAX_VALUE, SP_MIN_PRICE, SP_MAX_PRICE);
            	storeItemList.Add(new ShieldPotion(SP_NAME, SP_TYPE, randomSPValues[0], randomSPValues[1]));
            	break;
        }
    }
}

static int[] randomValueAndPrice(int minValue, int maxValue, int minPrice, int maxPrice)
{
	int value = new Random().Next(minValue, maxValue);
    int price = new Random().Next(minPrice, maxPrice);
    return new int[] { value, price };
}
  • BuyItem
    • 아이템 구매 함수
    • 상점에서 아이템 구매시 플레이어의 Money 값에 따라 구매 여부 결정
      • 아이템 구매시 player.BuyItem 함수에 인자로 구매하는 아이템을 전달해줌
      • 상점 아이템 리스트에서 구매한 아이템 삭제
 static void BuyItem()
 {
 	Console.WriteLine();
    Console.Write("구매할 아이템 번호를 입력하세요: ");
    int itemIdx = getPlayerSelect(1, storeItemList.Count) - 1;
    Item selectedItem = storeItemList[itemIdx];
    if (player.Money < selectedItem.Price)
    {
    	Console.WriteLine("돈이 부족해 구매할 수 없습니다! 몬스터를 사냥해서 돈을 모으세요!");
    } else
    {
    	Console.WriteLine("{0}을(를) 구매했습니다.", selectedItem.Name);
        player.BuyItem(selectedItem);
        storeItemList.Remove(selectedItem);
    }
}
  • EndGame()
    • 게임 종료 함수
    • 사냥한 몬스터 수와 플레이어의 경험치를 출력함
 static void EndGame()
 {
 	Console.WriteLine();
 	Console.WriteLine("게임을 종료합니다!");
    Console.WriteLine("사냥한 몬스터 수 {0}", huntedMonsterCnt);
    Console.WriteLine("{0}의 경험치 {1}", player.Name, player.Exp);
}

전체 코드

요기서 확인
GitHub Text RPG Game

실행모습


더 구현해보면 좋을 것들

  • 보스몹 추가
    • 일정 확률로 겁나 쎈 보스가 등장하는데 이제 돈이랑 경험치 많이 주는...근데 보스 처리하려면 그만큼 체력이랑 공격력도 높아야 하는 그런...
  • 스테이지 추가
    • 일정 경험치를 모으면 다음 스테이지로 넘어가기
    • 스테이지에 따라 몬스터 종류도 늘어나고, 더 쎈 몬스터가 등장하는 식으로



머.. RPG를 해본 적이 없어서 이렇게 하는게 맞는진 모르겠지만 아무튼 뭐 구현했다~!
추가로 구현해볼 것들은 일단 내일 개인 과제가 나와서 그거 먼저 끝내고 시간 남으면 시도해볼 듯!

그리고 외면하고 있던 3주차 과제 블랙잭 게임에서 스플릿도 함...구현해봐야지....ㅔ..........



오늘은 하루종일 4주차 과제만 하다가 끝났다!!!
아침에 어제 들은 4주차 강의 정리본 깃허브 README에 추가해놓고 그 뒤로 쭉 설계하고 구현하고..

어제 튜터님한테 질문했던 내용 정리하다가 질문할거 또 생각나서 갔다왔당
아무래도 운영체제는 적당히만 알면 될 듯 .. 힝
전공 중에 젤 열심히 한게 운영체제인데 .... ! 크흡

그리고 3주차 스네이크 게임 단일 스레드로 구현하신 분이 계시길래 질문하러 또 총총 갔다왔다
갑자기 불쑥 방문했는데 친절하게 알려주셔서 감사했음! 와! 좋은 분!
처음에 멀티 스레드로 하셨다가 딜레이 때문에 단일 스레드로 바꾸셨다고 했는데, 되게 참신한 방법이어서 놀랐음
코드 설명 듣고 파이팅 외치고 다시 돌아와서 TIL 작성했당

암튼 오늘은 여기까지
끗 ~

인줄 알았는데 아니 진짜 황당하네 .
강의 자료 노션에 요구사항 있다고 말 안 하셨잖아요...............
졸지에 그냥 맨땅에 구현한 사람 됨
머임?

내가..블랙잭 규칙 이해한다고...얼마나 힘들었는데......
허탈

0개의 댓글