ItemStatusView 클래스에 EquipBtn 변수가 있고 해당 Button변수에 컨트롤러에 있는 데이터를 인자값으로 받아와 이벤트를 등록해놨는데 이때 인자값의 초기값은 null값으로 설정이 되있었다.
ItemSlot 클래스에 버튼을 눌러 Item 데이터를 컨트롤러로 보내 초기화를 하였기에 null값은 사라졌다고 생각했다
왜냐하면 Class는 참조값이기에 변경될것이기 때문이다),
그리고 실제로 콘솔창에서는 null값이 들어온다고 오류가 발생하였다.
원인은 item이 참조형 변수라고 해도, 이 참조가 유효하려면 메모리 상의 객체를 가리키고 있어야 하는데 이미 처음 초기값을 null로 설정 한 뒤였고, 해당 변수는 다른 클래스(ItemStatusView)로 넘어가 있는 상태였기에 아무리 Controller에서 item이 초기화가 되더라도 view에서 받는 이벤트의 인자값은 null이 들어오는 것이였다.
즉, 이 문제를 일으킨 이유는
null이 빈 데이터라고만 착각을 해버려서 발생한 문제였다.
null은 빈데이터가 아닌 주소값이 없다라는 의미이기 떄문이다.
C++에서는 포인터 개념이 있기에 주소값 및 할당을 하게되면 무조건 해제를 해야만하지만
C#을 계속 사용하다보니 주소값에 대해서 깜빡했던것 같다.
C#은 포인터 개념이 없기 때문에 자동으로 Heap영역에 알아서 할당이 되었기 때문에 간단히 생각을 했던것이다.
ItemDB.Json을 통하여 ItemSlot들을 생성하여 인벤토리를 구현하였고 , ItemSlot은 현재 생성/파괴를 하고있기에 , 오브젝트 풀링을 이용하여 리팩토링 해야된다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class InventoryController : UIController
{
private InventoryModel inventoryModel;
private InventoryView inventoryView;
public InventoryModel Model { get => inventoryModel; set => inventoryModel = value; }
public InventoryView View { get => inventoryView; set => inventoryView = value; }
public override void Initialize(IUIBase view, UIModel model)
{
inventoryModel = model as InventoryModel;
inventoryView = view as InventoryView;
base.Initialize(inventoryView, inventoryModel);
}
public override void OnShow()
{
view.ShowUI();
UpdateView(); // 초기 View 갱신
}
public override void OnHide()
{
view.HideUI();
}
public override void UpdateView()
{
// Model 데이터를 기반으로 View 갱신
view.UpdateUI();
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using UnityEngine.U2D;
public class InventoryModel : UIModel
{
public List<Item> Items = new List<Item>(); // 소지하고있는 아이템 리스트
public void Initilaize()
{
foreach (ItemDB Data in DataManager.Instance.ItemDB.ItemsDict.Values)
{
Item itemObj = new Item();
itemObj.Initialize(Data);
Items.Add(itemObj);
}
}
public void AddItem(string item)
{
}
public void RemoveItem(string item)
{
}
}
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.InputSystem.XR;
public class InventoryView : MonoBehaviour, IUIBase
{
[SerializeField] private Transform itemSlotParent;
[SerializeField] private ItemSlot itemSlotPrefab;
[SerializeField] private RectTransform itemSlotBoundary;
public InventoryController Controller;
private List<ItemSlot> itemSlots = new List<ItemSlot>();
public void Initialize()
{
if (itemSlotPrefab == null)
{
itemSlotPrefab = Resources.Load<ItemSlot>("Prefabs/Item/ItemSlot");
}
for (int i = 0; i < Controller.Model.Items.Count; i++)
{
itemSlots.Add(Instantiate(itemSlotPrefab, itemSlotParent));
itemSlots[i].Initiliaze(Controller.Model.Items[i]);
}
}
public void ShowUI()
{
Vector2 size = itemSlotBoundary.sizeDelta;
size.y = 135 * ((itemSlots.Count / 4) + 1);
itemSlotBoundary.sizeDelta = size;
}
public void HideUI()
{
}
public void UpdateUI()
{
Debug.LogAssertion("인벤토리 UI 업데이트");
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class UIInventory : MonoBehaviour
{
public string UIKey;
private InventoryModel model;
[SerializeField] private InventoryView[] views;
private InventoryController controller;
private void Start()
{
//Model(Data) 초기화
model = new InventoryModel();
model.Initilaize();
GameManager.Instance._player.Inventory = model;
//컨트롤러 초기화 및 View 등록
controller = new InventoryController();
for (int i = 0; i < views.Length; i++)
{
views[i].Controller = controller;
controller.Initialize(views[i], model);
}
//UI매니저에 UI 등록
UIManager.Instance.RegisterController(UIKey, controller);
gameObject.SetActive(false);
}
public void UIShow()
{
controller.OnShow();
}
}
현재 로직만 구현 중이고 , 아직 Player 캐릭터와는 연결이 되어있지 않다. ItemStatus UI와 연결후 PlayerStat과 연동해서 값을 변경 해야 될것 같다.
[ 결론은 Scene 재로드시에 ObjectPool을 비우는 구조를 설계를 해야만 한다. ]
였기에 , 우선 호출시점을 오브젝트 풀링을 셋팅하기 전으로 설계를 해야되고 , 각 클래스별로 오브젝트 풀링세팅하는것을 한 곳으로 모아서 작업을 하였으며 , 경로 같은 고정 문자열은 따로 Const 라는 고정 상수,문자열을 저장하는 클래스를 따로 만들어 관리하기로 하였다.
이렇게 한 이유는
첫번째 : 오브젝트 풀링 세팅은 한 Scene에서 세팅 후에 호출 및 반납만하기에 한 곳에서 해도 된다고 판단하였고
Scene 로드 할때 처음으로 오브젝트 풀을 비우면 중복 생성이나 파괴된 오브젝트에 접근하지 않을거라고 판단하였다.
두번째 : 경로는 보통 문자열로 이루어져있고 수정이 되지 않아야되고 접근이 가능해야되기에 Static Class로 생성 한뒤 readonly 기능을 이용하어 수정을 막았다.
GameSceneTrigger.cs
using System;
using UnityEngine;
public class GameSceneTigger : MonoBehaviour
{
private void Start()
{
ObjectPoolManager.Instance.ObjectPoolAllClear();
PlayerObjectPoolSetting();
EnemyObjectPoolSetting();
InventoryObjectPoolSetting();
GameManager.Instance.StartGame();
}
private void InventoryObjectPoolSetting()
{
}
private void PlayerObjectPoolSetting()
{
ObjectPool playerProjectilePool = new ObjectPool(Const.POOL_KEY_PLAYERPROJECTILE, Const.PLAYER_INITIAL_POOL_SIZE, Const.PLAYER_PROJECTILE_ENERGYBOLT_PATH);
ObjectPoolManager.Instance.AddPool(Const.PLAYER_PROJECTILE_ENERGYBOLT_KEY, playerProjectilePool);
}
private void EnemyObjectPoolSetting()
{
ObjectPool goblinPool = new ObjectPool(5000, 60, Const.ENEMY_PREFEB_GOBLIN_PATH);
ObjectPool goblinMagicianPool = new ObjectPool(5001, 60, "Prefabs/Enemy/GoblinMagician");
ObjectPool slashPool = new ObjectPool(6000, 60, "Prefabs/Enemy/Effects/Slash");
ObjectPool energyBoltPool = new ObjectPool(6001, 60, "Prefabs/Enemy/Effects/EnergyBolt");
ObjectPool slashBossPool = new ObjectPool(6002, 60, "Prefabs/Enemy/Effects/SlashBoss");
ObjectPool skillBoss1Pool = new ObjectPool(6003, 10, "Prefabs/Enemy/Effects/SkillBoss1");
ObjectPool goblinBossPool = new ObjectPool(5500, 3, "Prefabs/Enemy/GoblinBoss");
ObjectPoolManager.Instance.AddPool(Const.ENEMY_POOL_KEY, goblinPool);
ObjectPoolManager.Instance.AddPool(Const.ENEMY_POOL_KEY, goblinMagicianPool);
ObjectPoolManager.Instance.AddPool(Const.ENEMY_EFFECT_POOL_KEY, slashPool);
ObjectPoolManager.Instance.AddPool(Const.ENEMY_EFFECT_POOL_KEY, energyBoltPool);
ObjectPoolManager.Instance.AddPool(Const.ENEMY_EFFECT_POOL_KEY, slashBossPool);
ObjectPoolManager.Instance.AddPool(Const.ENEMY_EFFECT_POOL_KEY, skillBoss1Pool);
ObjectPoolManager.Instance.AddPool(Const.ENEMY_BOSS_POOL_KEY, goblinBossPool);
}
}
해당 코드의 기능은 GameScene을 로드시에 필요한 곳에서 ObjectPool을 세팅하는 Class입니다.
SceneManager의 SceneLoad를 사용해도 되지만, 세팅해야될 데이터가 많고 , 확장성을 대비해서 Class로 만든뒤 오브젝트에 컴포넌트로 적용시켜 사용 중입니다.