인벤토리를 MVC패턴으로 나누고 기능의 배분도 명확해졌기 때문에
인벤토리의 기능을 물려받는 창고를 제작하려고 했다.
하지만 여기서 간과한 점이 있었는데
인벤토리는 창고와 비슷한 기능을 하는 것 처럼 보이지만
결국 다른 기능을 하고 있었다.
예를 들어, 인벤토리의 기능을 창고가 그대로 물려받는다면
창고는 퀵슬롯의 기능, 아이템의 장착 여부, 아이템 사용 등
사용하지 않는 기능의 코드가 많아져 가독성과 클래스의 명확성이
확 떨어질 것이다.
그리고 현재 만드는 게임은 AI 캐릭터가 농사와 사냥 등을 해주는
게임이고 AI 캐릭터가 빌드되는 스크립트에 얻은 아이템을 저장할
창고 객체를 생성해야 하는데, 만약 AI 캐릭터가 100개 늘어난다면
불필요한 코드도 100개만큼 늘어난다.
그래서 AI 캐릭터와 창고가 사용할 기초 Storage 클래스를
MVC패턴으로 만들어서 인벤토리가 상속을 받는 형태로 제작하기로 했다.
이렇게 된다면 Storage를 상속받은 인벤토리는 창고의 기능도 사용할 수 있고
인벤토리의 기능을 추가로 작성하여 인벤토리 역할까지 가능해진다.
사실 되도록이면 상속을 사용하지 말라는 충고를 어디선가 들은 것 같다.
그래서 창고와 인벤토리의 스크립트를 따로 제작하는 것이 옳다고 생각했지만
객체 지향 프로그래밍의 재사용성 측면에서 깊은 상속이 아니라면 사용해도 괜찮을 것이라 판단했다.
1. StorageModel
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static UnityEditor.Progress;
public class StorageModel
{
private List<ItemSlotInfo> _items;
private int _storageSize;
public event Action OnUpdateInventory;
public StorageModel(int storageSize, List<ItemSlotInfo> items)
{
_storageSize = storageSize;
_items = items;
InitializeStorageModel();
}
public void InitializeStorageModel()
{
if(_items.Count == 0)
{
for (int i = 0; i < _storageSize; i++)
{
_items.Add(new ItemSlotInfo(null, 0));
}
}
}
public int AddItem(string itemName, int amount)
{
Item item = null;
item = DataManager.Instance.GetData<Item>(itemName);
if (item == null)
{
Debug.Log("Could not find Item in Dictionary");
return amount;
}
foreach (ItemSlotInfo i in _items)
{
if (i.item != null)
{
if (i.item.Name == item.Name)
{
if (amount > i.item.GetMaxStack() - i.stacks)
{
amount -= i.item.GetMaxStack() - i.stacks;
i.stacks = i.item.GetMaxStack();
}
else
{
i.stacks += amount;
OnUpdateInventory?.Invoke();
return 0;
}
}
}
}
foreach (ItemSlotInfo i in _items)
{
if (i.item == null)
{
if (amount > item.GetMaxStack())
{
i.item = item;
i.stacks = item.GetMaxStack();
amount -= item.GetMaxStack();
}
else
{
i.item = item;
i.stacks = amount;
OnUpdateInventory?.Invoke();
return 0;
}
}
}
OnUpdateInventory?.Invoke();
return amount;
}
public void UpdateAction()
{
OnUpdateInventory?.Invoke();
}
}
2. StorageView
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.InputSystem;
using static UnityEditor.Progress;
public class StorageView
{
protected List<ItemSlotInfo> items;
public Mouse mouse;
public bool isPickUp;
private GameObject _storageMenu;
private GameObject _itemPanel;
private GameObject _itemPanelGrid;
private List<ItemPanel> _existingPanels = new List<ItemPanel>();
public StorageView(List<ItemSlotInfo> items, Mouse mouse, GameObject storageMenu, GameObject itemPanel, GameObject itemPanelGrid)
{
this.items = items;
this.mouse = mouse;
_storageMenu = storageMenu;
_itemPanel = itemPanel;
_itemPanelGrid = itemPanelGrid;
}
public void ShowStorage()
{
if (_storageMenu.activeSelf)
{
_storageMenu.SetActive(false);
mouse.EmptySlot();
RefreshStorage();
}
else
{
_storageMenu.SetActive(true);
RefreshStorage();
}
}
public async void RefreshStorage()
{
_existingPanels = _itemPanelGrid.GetComponentsInChildren<ItemPanel>().ToList();
Debug.Log(_existingPanels.Count);
List<ItemSlotInfo> itemsCopy = new List<ItemSlotInfo>(items);
int index = 0;
foreach (ItemSlotInfo i in itemsCopy)
{
i.name = "" + (index + 1);
if (i.item != null) i.name += ": " + i.item.Name;
else i.name += ": -";
ItemPanel panel = _existingPanels[index];
panel.name = i.name + "Panel";
if (panel != null)
{
panel.inventory = this;
panel.itemSlot = i;
if (i.item != null)
{
panel.itemImage.gameObject.SetActive(true);
panel.itemImage.sprite = await AddressableManager.Instance.LoadSpriteAsync(i.item.UIImagePath);
panel.itemImage.CrossFadeAlpha(1f, 0.05f, true);
panel.stackText.gameObject.SetActive(true);
panel.stackText.text = "" + i.stacks;
}
else
{
panel.itemImage.gameObject.SetActive(false);
panel.stackText.gameObject.SetActive(false);
}
}
index++;
}
mouse.EmptySlot();
}
public virtual void Refresh()
{
if (_storageMenu.activeSelf) RefreshStorage();
}
public void ClearSlot(ItemSlotInfo slot)
{
slot.item = null;
slot.stacks = 0;
}
}
3. StorageController
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static UnityEditor.Progress;
using static UnityEditor.Timeline.Actions.MenuPriority;
public class StorageController : MonoBehaviour
{
[SerializeReference] protected List<ItemSlotInfo> items = new List<ItemSlotInfo>();
protected int _storageSize = 10;
[HideInInspector] protected StorageModel storageModel;
[HideInInspector] protected StorageView storageView;
[Space]
[Header("Inventory Menu Components")]
[SerializeField] protected GameObject storageMenu;
[SerializeField] protected GameObject itemPanel;
[SerializeField] protected GameObject itemPanelGrid;
[Header("Mouse")]
[HideInInspector] protected Mouse mouse;
[SerializeField] protected string[] AddItems;
private void Start()
{
mouse = Mouse.Instance;
InitializedStorage();
}
public virtual void InitializedStorage()
{
storageModel = new StorageModel(_storageSize, items);
storageView = new StorageView(items, mouse, storageMenu, itemPanel, itemPanelGrid);
storageModel.OnUpdateInventory += storageView.Refresh;
Refresh();
}
public virtual void AddItem(string itemName, int amount)
{
storageModel.AddItem(itemName, amount);
}
private void Refresh()
{
if (storageView != null) storageView.Refresh();
}
public void CopyInventoryList(List<ItemSlotInfo> list)
{
items.Clear();
items.AddRange(list);
Refresh();
}
}
1. InventoryModel
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
[System.Serializable]
public class QuickSlot
{
public ItemSlotInfo inventorySlot;
public ItemPanel quickSlotPanel;
}
[System.Serializable]
public class ItemSlotInfo
{
public Item item;
public string name;
public int stacks;
public ItemSlotInfo(Item newItem, int newStacks)
{
item = newItem;
stacks = newStacks;
}
}
public class InventoryModel : StorageModel
{
private List<QuickSlot> _quickSlots;
private ItemPanel _curSelectedPanel;
private Dictionary<string, Item> _allItemsDictionary = new Dictionary<string, Item>();
public InventoryModel(int inventorySize, List<ItemSlotInfo> itemsList, List<QuickSlot> quickSlots) : base(inventorySize, itemsList)
{
_quickSlots = quickSlots;
}
public void EquipItem(ItemPanel seletedPanel)
{
if (_curSelectedPanel != null)
{
_curSelectedPanel.equipOutline.enabled = false;
}
if (_curSelectedPanel == seletedPanel)
{
UnEquipItem(seletedPanel);
return;
}
_curSelectedPanel = seletedPanel;
_curSelectedPanel.equipOutline.enabled = true;
UpdateAction();
}
public void UnEquipItem(ItemPanel equippedPanel)
{
_curSelectedPanel = null;
equippedPanel.equipOutline.enabled = false;
}
public void UseConsumableItem()
{
if(_curSelectedPanel != null && _curSelectedPanel.itemSlot.item.ItemType == ItemType.Consumable)
{
if(!_curSelectedPanel.UseItem()) _curSelectedPanel = null;
UpdateAction();
}
}
}
2. InventoryView
using System.Linq;
using System;
using UnityEngine.InputSystem;
using UnityEngine.UI;
using static TMPro.SpriteAssetUtilities.TexturePacker_JsonArray;
using static UnityEngine.Rendering.DebugUI;
public class InventoryView : StorageView
{
public List<QuickSlot> quickSlots;
private int _updateQuickSlotSize = 0;
private int _inventorySize = 10;
public InventoryView(List<ItemSlotInfo> items, List<QuickSlot> quickSlots, Mouse mouse, GameObject inventoryMenu, GameObject itemPanel, GameObject itemPanelGrid)
: base(items, mouse, inventoryMenu, itemPanel, itemPanelGrid)
{
this.quickSlots = quickSlots;
}
public override void Refresh()
{
base.Refresh();
RefreshQuickSlots();
}
public async void RefreshQuickSlots()
{
for (int i = 0; i < quickSlots.Count; i++)
{
quickSlots[i].inventorySlot = items[i + _updateQuickSlotSize];
quickSlots[i].quickSlotPanel.inventory = this;
quickSlots[i].quickSlotPanel.itemSlot = quickSlots[i].inventorySlot;
quickSlots[i].quickSlotPanel.isQuickSlot = true;
QuickSlot quickslot = quickSlots[i];
ItemSlotInfo inventorySlot = quickslot.inventorySlot;
ItemPanel quickSlotPanel = quickslot.quickSlotPanel;
if (inventorySlot.item != null)
{
quickSlotPanel.itemImage.gameObject.SetActive(true);
quickSlotPanel.itemImage.sprite = await AddressableManager.Instance.LoadSpriteAsync(inventorySlot.item.UIImagePath);
quickSlotPanel.itemImage.CrossFadeAlpha(1f, 0.05f, true);
quickSlotPanel.stackText.gameObject.SetActive(true);
quickSlotPanel.stackText.text = "" + inventorySlot.stacks;
}
else
{
quickSlotPanel.itemImage.gameObject.SetActive(false);
quickSlotPanel.stackText.gameObject.SetActive(false);
}
}
}
public void UpdateList()
{
if (_updateQuickSlotSize + quickSlots.Count < _inventorySize) _updateQuickSlotSize = quickSlots.Count;
else _updateQuickSlotSize = 0;
RefreshStorage();
}
}
3. InventoryController
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using UnityEditor.Build.Pipeline;
using UnityEngine;
using UnityEngine.Assertions;
public class InventoryController : StorageController
{
private int _testWheel = 0;
[Header("Quick Slot")]
[SerializeField] private List<QuickSlot> quickSlots = new List<QuickSlot>();
private InventoryModel _inventoryModel;
private InventoryView _inventoryView;
private Equipment Equipment;
private void Update()
{
if (Input.GetKeyDown(KeyCode.F))
{
_inventoryView.UpdateList();
}
}
public void Init(Equipment equipment)
{
Equipment = equipment;
SwitchSlot(0);
}
private void OnDestroy()
{
_inventoryModel.OnUpdateInventory -= _inventoryView.Refresh;
}
public override void AddItem(string itemName, int amount)
{
_inventoryModel.AddItem(itemName, amount);
}
public override void InitializedStorage()
{
_inventoryModel = new InventoryModel(_storageSize, items, quickSlots);
_inventoryView = new InventoryView(items, quickSlots, mouse, storageMenu, itemPanel, itemPanelGrid);
_inventoryModel.OnUpdateInventory += _inventoryView.Refresh;
#if UNITY_EDITOR
foreach (string itemName in AddItems)
{
AddItem(itemName, 1);
}
#endif
_inventoryView.RefreshStorage();
}
private async void SwitchSlot(int num)
{
_inventoryModel.EquipItem(quickSlots[num].quickSlotPanel);
GameObject ItemObj = null;
if (quickSlots[num].inventorySlot.item != null)
{
Item iteminfo = quickSlots[num].inventorySlot.item;
var ItemObjTask = AddressableManager.Instance.LoadObjectAsync(iteminfo.PrefabPath);
await ItemObjTask;
ItemObj = ItemObjTask.Result;
ItemObj.TryGetComponent(out ItemObject itemObject);
Assert.IsNotNull(itemObject, $"{iteminfo.Name}의 prefab에 ItemObject 설정을 해주세요");
itemObject.ItemData = iteminfo;
}
Equipment.Equip(ItemObj);
_testWheel = num;
}
private void ScrollWhell(float input)
{
if (input > 0)
{
if (_testWheel < quickSlots.Count - 1)
{
_testWheel++;
}
}
else
{
if (_testWheel > 0)
{
_testWheel--;
}
}
SwitchSlot(_testWheel);
}
public Action GetInventoryShowFunc()
{
if (_inventoryView == null)
InitializedStorage();
return _inventoryView.ShowStorage;
}
public Action<float> GetScrollFunc()
{
return ScrollWhell;
}
public Action<int> GetShortcutFunc()
{
return SwitchSlot;
}
public Action GetItemUseFunc()
{
return _inventoryModel.UseConsumableItem;
}
public List<ItemSlotInfo> GetItemList() => items;
}
'프로젝트 기록 > Project N' 카테고리의 다른 글
인벤토리 제작 과정 (아이템 소모) (0) | 2024.04.18 |
---|---|
창고 개발 기록 (인벤토리 데이터 불러오기) (0) | 2024.04.04 |
UIManager 구현 기록 (1) | 2024.02.13 |
UIManager는 왜 만드는 걸까? (0) | 2024.02.07 |
인벤토리 기능 MVC 적용 (0) | 2024.02.01 |