📕학습 개요

유니티 캠프 14일차.

TRPG 과제도 어느덧 막바지에 접어들었다. 생각보다 기능들이 잘 구현된 것 같아서 뿌듯하다.

물론 아직도 손 볼게 많다… 플레이어/몬스터 새부 스탯, 몬스터 드랍 테이블, 퀘스트 디테일, 보스 몬스터 스킬 등등… 내일이면 벌써 발표준비를 해야 하는데 조금 막막한 부분이 없지 않아 있다. 아무래도 오늘은 TIL작성 후 작업을 조금 더 하다 자야겠다.


📖학습 내용

오늘 한 일 정리

  1. 소비, 기타 아이템 거래 기능 구현
  2. 몬스터를 잡을 때 마다 보상(골드, 아이템, 경험치)지급 구현
  3. 소비 아이템 사용 구현(인벤토리)
  4. 아이템 JSON 파일 대거 수정

오늘 겪은 문제 및 해결 방법

오버라이딩이 안된다?

포션 클래스는 소비 아이템을 의미하는 Usable 클래스를 상속하도록 구현되었다.

   public class Potion : Usable
    {

        public int HealAmount { get; private set; }
        public Potion(ItemInfo itemInfo) : base(itemInfo)
        {
            HealAmount = itemInfo.HealAmount;
        }

        public override void Use(Player player)
        {
            base.Use(player);
            player.RecoverHP(HealAmount);
            Console.WriteLine($"{Name}를 사용했습니다. {(UsableType == UsableType.HealPotion ? "체력을" : "마나를")} {HealAmount} 회복했습니다.");
        }

모든 소비 아이템은 대부분 Usable 객체로 관리되기 때문에 Use 메서드는 당연히 추상 메서드로 구현되었다.

  public virtual void Use(Player player)
        {
            ChangeItemCount(-1);
        }

문제는 상점에서 구매한 포션을 아무리 사용해도 Potion 클래스의 Use 메서드가 호출되지 않는 것이다. 아무리 봐도 오버라이딩은 제대로 한 것 같은데, 아마 무조건 객체가 Potion이 아닌 Usable을 참조해서 생긴 일이라고 생각했다. 그래서 포션 객체가 생성되는 부분을 자세히 살펴봤다.

아래 코드는 게임 시작 시 상점에서 판매할 아이템을 알아서 등록하는 과정이다.

 foreach (ItemInfo itemInfo in ItemDataBase.ShopItems)
            {
                ITradable createdItem = ItemFactory.CreateItem(itemInfo);
                switch (itemInfo.ItemType)
                {
                    case ItemType.Equipment:
                        equipments.Add(createdItem);
                        break;
                    case ItemType.Usable:
                        usables.Add(createdItem);
                        break;
                    case ItemType.Other:
                        others.Add(createdItem);
                        break;
                }
            }

문제는 여전히 발견되지 않았다. ItemFactory는 아이템을 그 타입에 알맞게 알아서 생성한 객체를 반환해주기 때문이다. 즉 상점에서 구매한 포션은 Potion 클래스의 객체가 맞았다!

   //ItemInfo를 기반으로 아이템 객체를 만들어주는 기능 제공
    public static class ItemFactory
    {
        public static ITradable CreateItem(ItemInfo info)
        {
            switch (info.ItemType)
            {
                case ItemType.Equipment:
                    return new Equipment(info);
                case ItemType.Usable:
                    switch (info.UsableType)
                    {
                        case UsableType.HealPotion:
                        case UsableType.ManaPotion:
                            return new Potion(info);
                        default:
                            return new Usable(info);

                    }
                case ItemType.Other:
                    return new OtherItem(info);
            }
            return info.ItemType switch
            {
                ItemType.Equipment => new Equipment(info),
                ItemType.Usable => new Usable(info),
                ItemType.Other => new OtherItem(info),
                _ => throw new ArgumentException($"지원하지 않는 아이템 타입입니다: {info.ItemType}")
            };
        }
    }

그렇다면 뭐가 문제일까… 혼자 고민하다 지쳐 튜터님께 보여주는 과정에서 자연스래 깨닳게 되었다. 역시 코드는 남에게 보여줄 때 가장 잘 보이는 듯 하다.

문제는 오버라이딩도, 객체 생성도 아니었다. 내가 만들어놓은 소비, 기타아이템의 ‘특수성’ 때문이었다.

소비, 기타 아이템은 개체수가 많기 때문에 모든 객체를 생성하는 것이 아닌, 하나의 객체에 ItemCount 값을 추가하여 관리하기로 했다. 그리고 이 아이템을 거래할 시, 이동할 공간에 해당 아이템 객체가 존재하지 않을 시, 새로운 객체를 복사하여 전달한다. 문제는 여기서 발생한다.

 public Usable CloneItem(int itemCount)      //상점, 인벤토리에 전달 시, 새로운 객체를 복사하여 전달
        {
            var clone = new Usable(itemInfo);
            clone.ItemCount = itemCount;
            return clone;
        }

객체를 복사할 때 Usable 객체로 만들어진다! 따라서 상점에 있던 포션은 Potion 객체가 맞지만, 인벤토리로 복사되어 들어가는 객체는 Usable객체인 것이다.

 public override Usable CloneItem(int itemCount)
        {
            var clone = new Potion(itemInfo);
            clone.ItemCount = itemCount;
            return clone;
        }

이 메서드도 추상 클래스로 만든 뒤, 오버라이딩 해줘서 해결하였다.

태그: ,

업데이트:

댓글남기기