본 캠프 TIL

2025.04.17 TextRPG 개발일지

mrmuffin 2025. 4. 17. 21:09

오늘은 어제 설계한 Player.cs를 기반으로 적(Enemy)과 아이템(Equipment, Potion) 정보를 구조화하고, 게임 내 기능들을 분리하여 체계적으로 관리할 수 있도록 설계를 진행했다.

 

1. 적과 아이템 정보 구성

  • 어제 만든 Player.cs에서 사용했던 Stat 클래스 구조를 재활용하여, 적(Enemy)의 능력치도 동일한 방식으로 정리했다.
  • EnemyType을 enum으로 선언하여 일반/정예/보스 몬스터를 나누고, EnemyStat 클래스 내부에 Dictionary<EnemyType, Stat>으로 몬스터 정보를 저장하여 관리의 효율성을 높였다.
  • 아이템 또한 Equipment.cs와 Potion.cs를 통합하여 Equipment, Potion 클래스로 설계하고, 각 아이템의 정보를 Dictionary<string, T> 형식으로 저장하여 효율적인 접근이 가능하도록 구성했다.

2. ItemManager.cs 설계 및 기능 분리

  • 장비 장착, 해제, 포션 사용, 인벤토리 출력 등의 기능을 ItemManager 클래스에 정리해 게임 로직과의 의존성을 낮췄다.
  • 특히, 포션 사용 시 체력과 마력을 비율로 회복하도록 구현하였고, 아이템 개수(Count)를 관리하여 소비성 아이템의 사용 여부를 제어할 수 있게 만들었다.
  • 직업별 장비 필터링 기능도 구현하여, Warrior는 sword, Archer는 bow, Mage는 wand만 사용할 수 있게 제한했다.

Intro.cs와 Menu.cs 분리 및 흐름 정리

  • Intro.cs: 게임 시작 시 플레이어의 직업 선택과 난이도 선택 흐름을 담당. 미구현된 난이도(쉬움, 어려움)는 안내 메시지와 함께 기본 난이도(보통)로 진행되게 설정.
  • Menu.cs: 플레이어의 현재 상태와 인벤토리를 출력할 수 있는 메인 메뉴 구성. DisplayStats, ShowInventory 등을 선택지를 통해 확인할 수 있게 구성하였다.
게임 시작
   ↓
IntroStart() → 직업 선택
   ↓
IntroSelect() → 난이도 선택
   ↓
기본 장비 지급 (ItemManager.AddEquipment)
   ↓
메인 메뉴(Menu.MainMenu)
   ↳ 내 정보 보기 / 인벤토리 보기

 

지금까지 만든 게임의 흐름이다. 제출 기한이 머지 않아 구상했던 것은 대부분 구현 못하게 되었지만, 필수 기능만이라도 구현해보고자 한다.

 

다음은 오늘 구현한 내용들에 대해 설명하겠다.

 

1. 포션 기능 설계

  • 포션 회복량은 퍼센트 기반 (예: 20%, 40%, 60%, 100%)
  • PotionGrade 열거형(enum)으로 등급 구분
  • Potion.PotionHeal() 메서드로 회복률 반환
  • ItemManager.UsePotion() 메서드에서 회복 적용 및 개수 차감
  • MaxHP를 통해 최대 체력 이상으로 회복되는걸 방지
  • 포션 개수 관리: Count 프로퍼티를 추가하여 Dictionary에서 개수 추적

Potion클래스

public enum PotionGrade
{
    Low = 0, Mid = 1, High = 2, Max = 3
}

public class Potion
{
    public string Name { get; set; }
    public PotionGrade Grade { get; set; }
    public int Price { get; set; }
            
    public Potion(string name, PotionGrade grade, int price)         
        {
        Name = name;
        Grade = grade;
        Price = price;
        }
    public int Count { get; set; }

    public static Dictionary<string, Potion> PotionList = new Dictionary<string, Potion>()
    {
        { "low_potion", new Potion("하급 포션", PotionGrade.Low, 30) { Count = 3 } },
        { "mid_potion", new Potion("중급 포션", PotionGrade.Mid, 50) { Count = 0 } },
        { "high_potion", new Potion("상급 포션", PotionGrade.High, 80) { Count = 0 } },
        { "max_potion", new Potion("최상급 포션", PotionGrade.Max, 0) { Count = 0 } }
    };

    public static double PotionHeal(PotionGrade grade)
    {
        switch (grade)
        {
            case PotionGrade.Low:
                return 0.2;
            case PotionGrade.Mid:
                return 0.4;
            case PotionGrade.High:
                return 0.6;
            case PotionGrade.Max:
                return 1.0;
           default:
                return 0.0;  
        }  }}

 

2. 아이템 분리 및 관리

  • ItemManager.cs 파일을 새로 만들어 다음 기능들을 분리함:
    • 포션 사용 (UsePotion)
    • 장비 착용 (EquipItem)
    • 장비 해제 (Unequip)
    • 인벤토리 표시 (ShowInventory)
  • EquipmentList와 PotionList를 static Dictionary로 관리

그 중 일부인 포션 사용에 대한 코드이다.

 public static void UsePotion(Player player, Potion potion)
 {
     if (potion.Count <= 0)
     {
         Console.WriteLine($"{potion.Name}이 없습니다!");
         return;
     }

     double rate = Potion.PotionHeal(potion.Grade);

     int healHP = (int)(rate * player.MaxHP);
     int healMP = (int)(rate * player.MaxMP);

     player.HP += healHP;
     player.MP += healMP;

     potion.Count--;

     Console.WriteLine($"{potion.Name}을 사용하여 HP를 {healHP}, MP를 {healMP}만큼 회복했습니다!");
     Console.WriteLine($"현재 HP: {player.HP}/{player.MaxHP} | MP: {player.MP}/{player.MaxMP}");

 }

 public static void AddPotion(Player player, string key)
 {
     if (Potion.PotionList.ContainsKey(key))
     {
         var potion = Potion.PotionList[key];

         if (!player.PotionInventory.ContainsKey(key))
             player.PotionInventory[key] = potion;

         player.PotionInventory[key].Count++;
         Console.WriteLine($"{potion.Name}을(를) 획득했습니다!");
     }
 }

 

 

Main()함수에 다른 클래스와 메서드 호출하기

 

static void Main(string[] args)
{
    // 인트로 출력
    Intro.IntroStart();

    // 플레이어 생성 및 설정
    Player player = new Player();
    Intro.IntroSelect(player);

    // 난이도에 따른 초기 장비 지급(구현을 보통 난이도밖에 못함)
    ItemManager.AddEquipment(player, "common_sword");
    ItemManager.AddEquipment(player, "common_armor");

    // 메인 메뉴로 진입
    Menu.MainMenu(player);
}

 

Intro.IntroStart();

Intro라는 클래스 안에 정의된 IntroStart라는 static 메서드를 실행한다.

  • Intro는 클래스 이름 (정적 메서드가 정의된 클래스)
  • IntroStart는 public static 메서드
  • static이기 때문에 객체 생성 없이 클래스이름.메서드() 형태로 바로 호출이 가능하다.

ItemManager.AddEquipment(player, "common_sword");

 

  • ItemManager → 아이템 관련 기능을 모아놓은 클래스 (보통 static으로 선언해서 공용으로 사용)
  • AddEquipment() → 플레이어에게 장비를 추가하는 메서드
  • player → 아이템을 추가할 대상 (Player 클래스의 인스턴스)
  • "common_sword" / "common_armor" → 딕셔너리 키값, Equipment.EquipmentList에서 장비를 찾아서 넣는 기준

다음은 ItemManager.cs에서 일부 가져온 코드이다.

public static void AddEquipment(Player player, string key)
{
    if (Equipment.EquipmentList.ContainsKey(key))
    {
        Equipment equip = Equipment.EquipmentList[key];
        player.Inventory.Add(equip);  // 예시: 인벤토리에 추가
        Console.WriteLine($"{equip.Name}을(를) 획득했습니다!");
    }
}

 

 

 

오늘 발생한 문제

 

주로 접근 제한자 관련 문제가 잦았다. 

"보호 수준 때문에 'Menu.MainMenu(Player)'에 액세스할 수 없습니다." 라는 문제는 Menu 클래스 안의 MainMenu 메서드가 private (기본 접근 제한자)로 선언되어 있어서, Program.cs 등 외부 클래스에서 접근할 수 없었기 때문이었다.

 

따라서  MainMenu 메서드에 public 접근 제한자를 추가해서 외부에서 호출 가능하게 수정하여 해결하였다.

 

이 외에도 Player 클래스가 internal인데 UsePotion이 public이라 접근이 안되는 오류라던가, 이런 클래스간 접근제한자의 차이와 사용에 대해 더 잘 알게 된 것 같다. 그 외에도 Main() 함수에서 다른 클래스나 메서드를 호출하는 법에 대해 잘 몰랐었는데, 오늘 과제를 수행하면서 좀 알게 된 것 같아 뿌듯하다.