TIL

2024 12 09 TIL

noc777 2024. 12. 9. 21:11

팀원 중 한 분이 중도하차를 하게 될 것 같으셔서 역할을 다시 배분하기로 하였다.

멀티플레이 담당을 제외한 나와 한 분이 역할을 나누어 맡게 될 것 같다.

그러기위해 지금까지 작업하셨던 내용들을 분석해보는 시간을 가져본다.

//Inventory.cs 분석

 public List<ItemSlot> inventorySlots = new List<ItemSlot>(); //슬롯을 담을 자료구조 리스트 초기화
 public int maxSlots = 20; //최대 슬롯수 설정
 
 [System.Serializable] //멀티플레이게임에서 데이터 교환을 위한 직렬화
public class ItemSlot  //아이템 슬롯 
{
    public ItemData item; //아이템의 데이터를 담은 스크립터블 오브젝트 할당칸
    public int amount; //현재 갯수

    public ItemSlot() //생성자 
    {
        item = null; //초기설정으로 아이템과
        amount = 0; //갯수합계를 0부터 시작
    }
}

private void Start()
{
    for (int i = 0; i < maxSlots; i++) //maxSlots의 값(20) 만큼 반복
    {
        inventorySlots.Add(new ItemSlot()); //리스트에 새로 ItemSlot 객체를 생성
    }
}

 [ClientRpc] //서버가 클라이언트에 RPC를 실행할 수 있게 해준다. (해당 메서드를 호출하면 모든 클라이언트에서 실행됨)
 private void UpdateInventoryClientRpc()
 {
     FindObjectOfType<InventoryUI>().UpdateInventoryUI(); //모든 클라이언트의 InventoryUI에서 UpdateImvemtoryUI 메서드 호출
 }


public bool AddItem(ItemData item, int amount) //외부에서 호출하는 아이템을 넣는 메서드 (매개변수로 
{
    if (!IsServer) return false; //이 메서드는 서버에서만 작동하도록 되어있다.

    foreach (var slot in inventorySlots) //리스트의 전체적으로 
    {
        if (slot.item != null && slot.item == item && slot.amount < item.MaxQuantity) //슬롯이 비어있지 않고 같은 아이템이며 최대 수량보다 적게 들어있을 경우를 먼저 찾는다.
        {
            int addable = Mathf.Min(item.MaxQuantity - slot.amount, amount); //해당 슬롯에 들어갈 수 있는 양을 계산
            slot.amount += addable; //슬롯에 들어갈 수 있는 양만큼 채우고
            amount -= addable; //남은 수량을 구한다.

            if (amount == 0) //만일 남은 수량이 없다면 
            {
                UpdateInventoryClientRpc(); //요청한 Client의 세계 UI를 변경시킨다.
                return true; //true를 호출한 곳에서 반환
            }
        }
    }

    foreach (var slot in inventorySlots) //남은 수량이 있을 경우 다시 찾아보는 과정
    {
        if (slot.item == null) //아이템이 비어있는 슬롯을 찾는다. 
        {
            slot.item = item; //해당슬롯에 아이템을 등록
            slot.amount = amount; //슬롯에 남은 수량을 넣어준다.
            UpdateInventoryClientRpc(); //UI업데이트
            return true; //true 반환
        }
    }

    return false; //만일 비어있는 슬롯이 없다면 false 반환 (이 경우 처리 있는지 확인)
}

 public bool RemoveItem(ItemData item, int amount) 
 {
     if (!IsServer) return false; //서버인 경우에만 사용됨

     foreach (var slot in inventorySlots) //리스트에서 슬롯 검색
     {
         if (slot.item != null && slot.item == item) // 슬롯 중 같은 아이템이 있는지 확인
         {
             if (slot.amount >= amount) //슬롯의 아이템의 갯수가 빼려는 갯수보다 크거나 같을 때
             {
                 slot.amount -= amount; //슬롯의 아이템 - 제거하려는 아이템 갯수
                 if (slot.amount == 0) //만약 이 행동으로 갯수가 0이 되었을 때
                 {
                     slot.item = null; //해당 슬롯의 아이템 데이터를 null로 만듬
                 }
                 UpdateInventoryClientRpc(); //UI 업데이트
                 return true; //true 반환 후 빠져나옴
             }
             else //만일 슬롯에 있는 갯수보다 값이 크다면 
             {
                 amount -= slot.amount;  //값에서 슬롯의 갯수를 뺌
                 slot.item = null; //슬롯을 비워두고
                 slot.amount = 0; //슬롯의 수량을 0으로 만듬
             }
         }
     }
     UpdateInventoryClientRpc(); //UI업데이트
     return false; //false 반환
 }

 

public class InventoryUI : MonoBehaviour
{
    public Inventory inventory; //UI 에 적용시킬 인벤토리 
    public Transform slotParent; //슬롯을 담아둘 게임오브젝트의 transform
    public GameObject slotPrefab; //슬롯 UI의 프리팹
    public TextMeshProUGUI itemName; //아이템의 이름을 출력해줄 TMP
    public TextMeshProUGUI description; //아이템의 설명을 출력해줄 TMP

    private void Start()
    {
        for (int i = 0; i < inventory.maxSlots; i++) // 인벤토리의 maxSlots만큼 반복
        {
            var slotInstance = Instantiate(slotPrefab, slotParent);//부모가 될 오브젝트에 프리팹 생성
            var slotUI = slotInstance.GetComponent<InventorySlotUI>();//해당 슬롯들마다 가지고 있는 InventorySlotUI 컴포넌트로 초기화
            slotUI.OnSlotClicked += DisplayItemInfo; //슬롯UI의 이벤트에 메서드 구독
        }
        UpdateInventoryUI(); //인벤토리 업데이트
    }

    public void UpdateInventoryUI()
    {
        for (int i = 0; i < slotParent.childCount; i++) //슬롯UI들의 수만큼 반복
        {
            var slot = slotParent.GetChild(i).GetComponent<InventorySlotUI>(); //해당 회차의 슬롯의 컴포넌트를 가져온다.

            if (i < inventory.inventorySlots.Count) //만약 i의 반복 수가 인벤토리의 슬롯들의 수와 일치한다면
            {
                var itemSlot = inventory.inventorySlots[i]; //해당회차의 슬롯을 가져온다.
                if (itemSlot.item != null) //이 회차의 슬롯의 아이템 데이터가 비어있지않다면
                {
                    slot.SetItem(itemSlot.item, itemSlot.amount); //슬롯UI에 해당 아이템 데이터와 수량을 등록
                }
                else
                {
                    slot.ClearSlot(); //아니라면 비워둔다.
                }
            }
        }
    }

    public void DisplayItemInfo(ItemData item) //아이템의 정보를 출력할 때 
    {
        if (item != null) //매개변수로 받은 아이템 데이터가 null이 아니라면
        {
            itemName.text = item.ItemName; //해당 아이템의 이름을 텍스트로 출력
            description.text = item.ItemDescription; //아이템 설명을 텍스트로 출력
        }
        else
        {
            itemName.text = ""; 
            description.text = "";
        }
    }
}
public class InventorySlotUI : MonoBehaviour
{
    public RawImage icon; //슬롯UI의 아이템 아이콘
    public Text amountText; //갯수 표시 텍스트

    private ItemData currentItem; //현재 아이템의 데이터
    private int currentAmount; //현재 아이템의 수량

    public event Action<ItemData> OnSlotClicked; //해당 슬롯을 클릭했을 때 이벤트

    private void Start()
    {
        ClearSlot(); //시작할 때 해당슬롯을 비움
    }

    private void SetIcon(Texture2D texture) //아이콘 설정 메서드?
    {
        if (icon != null && texture != null) 
        {
            icon.texture = texture; //해당UI의 아이콘에 매개변수로 받은 텍스쳐를 넣어준다.
            icon.enabled = true; //활성화
        }
        else
        {
            icon.texture = null; //여기서 오류 발생 nullReference
            icon.enabled = false; //비활성화
        }
    }

    public void SetItem(ItemData item, int amount)
    {
        currentItem = item; //받아온 매개변수의 아이템 데이터를 대입
        currentAmount = amount; //받아온 아이템 수량을 대입
        SetIcon(item.Icon); //아이콘 초기화 메서드인데 오류 확인예정
        amountText.text = amount > 1 ? amount.ToString() : ""; //수량 텍스트는 매개변수 amount가 1보다 클 때 표시되도록 되어 있음
    }

    public void ClearSlot()
    {
        currentItem = null; //슬롯의 아이템 데이터 초기화
        currentAmount = 0; //수량 초기화
        SetIcon(null); //오류 확인예정
        amountText.text = ""; //텍스트 초기화
    }

    public void OnClickButton() //버튼을 눌렀을 때 이벤트 실행
    {
        OnSlotClicked?.Invoke(currentItem); 
    }

    public void UseItem() //아이템 사용했을 때 UI 쪽 메서드
    {
        if (currentItem != null && currentItem.ItemType == ItemType.Consumable)  //
        {
            Debug.Log($"Using item: {currentItem.ItemName}");
            ConsumeItemServerRpc(currentItem.ItemName);
        }
    }

    [ServerRpc(RequireOwnership = false)]
    private void ConsumeItemServerRpc(string itemName)
    {
        Debug.Log($"Server: Item {itemName} consumed.");
        ConsumeItemClientRpc(NetworkManager.Singleton.LocalClientId, itemName);
    }

    [ClientRpc] //서버에서 클라이언트들에게 호출시켜주는 메서드
    private void ConsumeItemClientRpc(ulong clientId, string itemName)
    {
        if (NetworkManager.Singleton.LocalClientId == clientId)
        {
            Debug.Log($"Client: Item {itemName} consumed.");
            currentAmount--;
            if (currentAmount <= 0) ClearSlot();
        }
    }
}

 

 

분석하면서 알게된 내용 

 

 

넷코드의 NetworkObject  키워드:

ClientRpc : 해당 메서드가 호출 될 때 서버에서 모든 클라이언트에게 호출시켜준다. 

(클라이언트 개별적으로 적용하기 위해선 추가적인 개념이 필요한듯)

 

해야할 것

필요한 기능 여부 검사 

멀티플레이와 연계간의 상호보완을 위한 넷코드 이해

테스트신의 오류 수정 : 

 

'TIL' 카테고리의 다른 글

2024 12 11 TIL  (0) 2024.12.11
2024 12 10 TIL  (0) 2024.12.10
2024 12 06 TIL  (0) 2024.12.06
2024 12 05 TIL  (0) 2024.12.05
2024 12 04 TIL  (2) 2024.12.04