텍스트 Rpg - C#

C#으로 만드는 텍스트 Rpg (5)

게임만드는학생 2025. 2. 5. 06:25

 

이번에는 기존 미션 계획 중간에 미션 4이후, 아이템 시스템에 대한 리팩토링 및 

자동화 기능을 추가했다. 

 

델리게이트를 이용해 아이템 사용시 이벤트를 발생시킬 수 있게했다. 

또한 플레이어와 몬스터에도 델리게이트를 적용시켜 전투 시, 이벤트를 발생시킬 수 있도록 하였다. 

 

아이템 시스템 자동화부분은 json 파일을 읽어와 각 아이템 클래스의 값들을 초기화하는 것이다.

하드코딩되었던 부분을 json에서 읽어오도록 바꾸었다. 

 

//public delegate void OnDead();
public event Action OnDeadEvent;

//public delegate void OnAttack();
public event Action OnAttackEvent;

플레이어와 몬스터 클래스에 모두 Action 델리케이트를 사용해 이벤트를 만들었다. 

 

public Player() { _hp = MaxHp;OnDeadEvent += Dead; OnAttackEvent += PlayAttack; }

public void PlayAttack()
{
    Console.WriteLine("플레이어가 공격모션을 실행합니다. in Player");
}

public void Attack(Monster monster)
{
    OnAttackEvent?.Invoke();
    monster.TakeDamage(AttackPower);

}

이런식으로 이벤트에 함수를 구독시켜놓고 attack 함수 실행시, 이벤트를 발생시키게 하였다. 

앞으로 애니메이션이나 ui등, 기능 확장에 유리할 것이라 생각된다. 

 

다음은 아이템이다. 

아이템매니저와 아이템 클래스간의 결합도를 낮추었다. 

public void UsedItem(string itemName, Player player)
{
    if (Inventory.ContainsKey(itemName) == false) return;

    Item it = Inventory[itemName][0];
    IUseableItem? useableItem = it as IUseableItem;
    if(useableItem==null)
    {
        Console.WriteLine("소비아이템이 아닙니다!");return;
    }

    useableItem.Use(player);

    OnUsedItem?.Invoke($"아이템 로그 : {it.Name}을 사용!");

    RemoveItem(itemName);
    if (it is DurationItem)
        RegistItem(it);
}

외부에서 아이템 매니저의 이 함수를 호출하면 아이템을 찾아 그 아이템의 Use함수를 실행시켜준다. 

그리고 remove나 regist 도 전부 이 매니저함수에서 관리한다. 기존 아이템의 use에서 직접호출하던 방식에 비해 의존성을 낮추었다. 또한 이벤트도 여기서 발생시킨다. 앞으로 ui등에 활용가능하다고 생각하며, 

dictionary를 이용해 각 아이템마다 사용시 이벤트를 나눌수도 있을 것 같다.

 

다음은  json관련이다. 

이 부분은 내가 잘 몰라서 chatgpt에게 요청해 기능을 되게만 했다. 

코드를 직접 짠것은 아니지만 이렇게 사용하면서 확장해나가는 것도 학습방법이라고 생각한다. 

 

json파일이다. 

{
  "HpPotion": {
    "ItemType": "HpPotion",
    "Name": "체력 포션",
    "Description": "사용 시 체력을 회복합니다.",
    "Effect": 50
  },
  "AttackPotion": {
    "ItemType": "AttackPotion",
    "Name": "공격력 증가 포션",
    "Description": "사용 시 일정 시간 동안 공격력이 증가합니다.",
    "Effect": 10,
    "Duration": 3
  },
  "ShieldPotion": {
    "ItemType": "ShieldPotion",
    "Name": "방어력 증가 포션",
    "Description": "사용 시 일정 시간 동안 방어력이 증가합니다.",
    "Effect": 5,
    "Duration": 5
  },
  "RandomPotion": {
    "ItemType": "RandomPotion",
    "Name": "랜덤 포션",
    "Description": "랜덤으로 긍정적 또는 부정적 효과를 발생시킴.",
    "PositiveEffects": [
      "HpFullRecovery",
      "DoubleAttackPower"
    ],
    "NegativeEffects": [
      "HalfHp",
      "DoubleDamageTaken"
    ]
  }
}

이렇게 모든 정보를 json파일에서 읽어오면 값을 변경하고 싶을 때, json파일만 수정하고 코드는 건드릴 필요가 없게된다.

 

 public class ItemData
 {
     public string ItemType { get; set; }
     public string Name { get; set; }
     public string Description { get; set; }
     public int Effect { get; set; }
     public int Duration { get; set; }
     public List<string> PositiveEffects { get; set; }  // ✅ 랜덤 포션의 긍정 효과 리스트
     public List<string> NegativeEffects { get; set; }  // ✅ 랜덤 포션의 부정 효과 리스트
 }

이 json파일은 ItemData라는 객체로 각각 저장한다.

 

private Dictionary<string, ItemData> itemConfigs;

public void LoadItemsFromJson()
{
    string json = File.ReadAllText("items.json"); // ✅ JSON 파일 읽기
    itemConfigs = JsonConvert.DeserializeObject<Dictionary<string, ItemData>>(json); // ✅ JSON -> C# 객체 변환
}

public ItemData GetItemConfig(string itemType)
{
    return itemConfigs != null && itemConfigs.ContainsKey(itemType) ? itemConfigs[itemType] : null;
}

그리고 아이템 매니저에서 json파일을 읽어서 저장하는 함수와 아이템타입을 통해 값을 검색하는 함수이다. 

 

각 아이템 클래스의 생성자에서 세팅하게 된다.

 

public HpPotion()
{
    var config = ItemManager.Instance.GetItemConfig("HpPotion");
    Type = config != null ? Enum.Parse<ItemType>(config.ItemType) : ItemType.HpPotion; // ✅ JSON에서 `ItemType` 불러오기
    Name = config != null ? config.Name : "체력 포션";
    Description = config != null ? config.Description : "사용 시 체력을 회복합니다.";
    HealAmount = config != null ? config.Effect : 50;
}

이런식으로 관리하게 된다. 

hppotion에 대한 json에서 읽어온 값을 받아와서 세팅한다. 

attackpotion 과 shieldpotion도 동일하게 세팅한다.

 

randompotion은 긍정적,부정적효과들을 리스트로 받아와서 50% 확률로 긍정,부정을 선택하며 그 중, 리스트에서 랜덤으로 하나가 실행되게 하였다. 

 

public class RandomPotion : Item, IUseableItem
{
    private List<string> PositiveEffects;
    private List<string> NegativeEffects;
    private Random random = new Random();

    public RandomPotion()
    {
        var config = ItemManager.Instance.GetItemConfig("RandomPotion");
        Type = config != null ? Enum.Parse<ItemType>(config.ItemType) : ItemType.RandomPotion; // ✅ JSON에서 `ItemType` 불러오기
        Name = config != null ? config.Name : "랜덤 포션";
        Description = config != null ? config.Description : "랜덤으로 긍정적 또는 부정적 효과를 발생시킴.";
        PositiveEffects = config != null ? config.PositiveEffects : new List<string> { "HpFullRecovery", "DoubleAttackPower" };
        NegativeEffects = config != null ? config.NegativeEffects : new List<string> { "HalfHp", "DoubleDamageTaken" };
    }

    public void Use(Player player)
    {
        bool isPositive = random.Next(0, 2) == 0; // ✅ 50% 확률로 긍정 or 부정 효과 선택
        string selectedEffect = isPositive
            ? PositiveEffects[random.Next(PositiveEffects.Count)]
            : NegativeEffects[random.Next(NegativeEffects.Count)];

        ApplyEffect(player, selectedEffect);
    }
    private void ApplyEffect(Player player, string effect)
    {
        switch (effect)
        {
            case "HpFullRecovery":
                player.Hp = player.MaxHp;
                Console.WriteLine($"{player.Name}의 체력이 최대치로 회복되었습니다!");
                break;

            case "DoubleAttackPower":
                player.AttackPower *= 2;
                Console.WriteLine($"{player.Name}의 공격력이 2배 증가했습니다!");
                break;

            case "HalfHp":
                player.Hp /= 2;
                Console.WriteLine($"{player.Name}의 체력이 절반으로 감소했습니다!");
                break;

            case "DoubleDamageTaken":
                player.DefenseRate *= 2;
                Console.WriteLine($"{player.Name}이 받는 피해가 2배 증가했습니다!");
                break;
        }
    }

 

아이템클래스의 값을 자동으로 읽어오게 하여 하드코딩을 없앴고,

delegate를 이용해 여러 이벤트를 발생할 수 있도록 하였다.