Development/유니티

[섹션7] 인벤토리 실습 & 코드 정리

mine__ral 2021. 8. 1. 19:41

<인벤토리 실습 #1>

1. Prefab의 UI_Button에서 Itemicon 부분을 삭제하고 PlayerController에서 UI_Button을 부르는 부분 삭제

 

굉장히 다양한 UI가 있는데 딱 보면 알 수 있다.  -> 그때 그때 찾아보면서 코드를 짜면 된다. 

 

  • 다양한 UI

asset store에서 제공하는 무료 UI 이용하기 

 

1. Unity Asset store에서 원하는 UI를 다운받아서 사용한다. 

2. 다음과 같이 UI 인벤토리를 만든다. 

전체 인벤토리를 감싸주는 Panel을 생성하고 이름을 UI_Inven으로 한다. 

item을 UI -> Panel을 통해서 만들어주고(UI_Inven_Item) 그 아래에 item 이미지를 자식으로 둔다. Panel의 Image는 1에서 다운받은 UI의 UIButtonDefault를 mapping 한다. 

3. 위에서 만든 UI_Inven_Item을 Panel 아래의 여러개의 자식을 두어 표현하는데 이때 Panel에 Grid Layout Group component를 붙여주면 위의 그림과 같이 UI_Inven_Itme이 Grid로 표현할 수 있다. 

(원하는 Grid 모양으로 아래의 Cell Size, Spacing 등등을 바꾸어 )

4. 위에서 만든 UI_Inven_Item과 UI_Inven을 Prefabs 폴더 아래에 Scene 폴더에 두고 다음과 같이 Prefab UI_Inven_Item을 다음과 같이 Text를 두어 수정한다. 

 

  • Binding하기

1. UI_Inven.cs와 UI_Inven_Item.cs를 Sripts -> UI -> Scene 폴더 아래에 만든다. 

 

2. UI_Inven.cs 파일을 열어서 이전에 UI 자동화에서 배웠던대로 Binding을 하도록 한다. 

*UI_Inven.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UI_Inven : UI_Scene
{
    enum GameObjects
    {
        GridPanel
    }
    void Start()
    {
        
    }
    void Update()
    {
        
    }

    public override void Init()
    {
        base.Init();

        // Binding 하기 
        Bind<GameObject>(typeof(GameObjects));

2. 그 후 기존에 있던 UI_Inven_Item을 없애고 다시 Prefab에서 원하는 만큼 UI_Inven_Item을 들고 와서 mapping을 해줘야한다. 

*UI_Inven.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UI_Inven : UI_Scene
{
    enum GameObjects
    {
        GridPanel
    }
    void Start()
    {
        
    }
    void Update()
    {
        
    }

    public override void Init()
    {
        base.Init();

        // Binding 하기 
        Bind<GameObject>(typeof(GameObjects));

        // 우선 기존에 있던 UI_Inven_Item을 날려야한다. 
        GameObject gridPanel = GET<GameObject>((int)GameObjects.GridPanel);
        // gridPanel에 있는 child를 순회하면서 찾음
        foreach (Transform child in gridPanel.transform)
            Managers.Resource.Destroy(child.gameObject);

        // 실제 인벤토리 정보를 참고해서 해야하지만 현재는 강제적으로
        for (int i = 0; i < 8; i++)
        {
            GameObject item = Managers.Resource.Instantiate("UI/Scene/UI_Inven_Item");
            item.transform.SetParent(gridPanel.transform); // 강제로 Parent를 정해줌
        }
    }
}

 

3. PlayerController.cs 에서 위의 UI_Inven이 나타나는지 Test해본다.  Play해보면 인벤토리 창이 잘 뜨는 것을 확인할 수 있다. 

*PlayerController.cs

void Start()
    {
        /*
        Managers.Input.KeyAction -= OnKeyboard;
        Managers.Input.KeyAction += OnKeyboard;
        */

        Managers.Input.MouseAction -= OnMouseClicked;
        Managers.Input.MouseAction += OnMouseClicked;

        // TEMP
        Managers.UI.ShowSceneUI<UI_Inven>();
    }

 

 

<Item 기능 추가>

 

Item 버튼의 이름을 각각 바꾸고 클릭 Event를 추가한다. -> UI_Inven_Item.cs를 통해서 

 

  • Item 이름 바꾸기

1. 해당 Item에 관한 것은 UI_Popup과 UI_Scene의 상속이 아닌 (어느 곳에서도 속하지 않기 때문에) UI_Base의 상속을 받아야한다. 이때 위에서 처럼 overrid init()을 하면 UI_Base에 init함수가 없기 때문에 error가 난다. 

=> init() 함수를 UI_Base로 옮기는 것이 설계적으로 더 좋다

 

2. 다음과 같이 UI_Base.cs에서 Init() 함수를 abstract으로 추상 클래스, 추상 메서드로 구현을 한다. 이렇게 바꾸면 UI_Popup.cs와 UI_Scene.cs에서 ovrride로 바꿔줘야한다. 

*UI_Base.cs

public abstract class UI_Base : MonoBehaviour
{
    // 딕셔너리를 이용하여 Buttons를 받으면 Unity Object의 Button을 반환하고,
    // Texts를 받으면 Unity Object의 Text를 반환하도록 
    Dictionary<Type, UnityEngine.Object[]> _objects = new Dictionary<Type, UnityEngine.Object[]>();

    public abstract void Init();

*UI_Base.cs & UI_Scene.cs

public override void Init()
    {
        Managers.UI.SetCanvas(gameObject, true);
    }

=> UI_Inven_Item.cs에서 init()함수를 override를 하여 구현한다.

 

◆ abstract 메소드는 인터페이스의 메소드와 마찬가지로 몸통이 없다. 즉 abstract 클래스를 상속하는 클래스에서 해당 abstract 메소드를 구현해야만 하는 것이다.

 

3. 위에서 Binding한대로 enum으로 GameObject를 정의해야하는데 현재 Image와 Text UI component 두 개 밖에 없기 때문에 GaemObjects로 한번에 enum을 정의한다. 

Bind를 해주는데 이때 GameObject로 가져온 다음 해당 Object의 Text component를 가져온다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class UI_Inven_Item : UI_Base
{
    enum GameObjects
    {
        Itemicon,
        ItemNameText,
    }
    void Start()
    {
        Init();
    }
    public override void Init()
    {
        Bind<GameObject>(typeof(GameObjects)); // 각각의 GameObject를 Binding 해줌
        // 해당 GameObject의 Text component를 가져온다.
        GET<GameObject>((int)GameObjects.ItemNameText).GetComponent<Text>().text = "bind test"; 
    }
    void Update()
    {
        
    }
}

4. 이때 Play를 하면 text가 바뀌지않는 것을 확인할 수 있다. 

=> 기존의 UI_Popup과 UI_Scene에서는 UI_Manager.cs 파일을 통해 Util.GetOrAddComponent를 통해 component를 붙여줬었다. 이 부분이 없기 때문에 실행이 되지 않았다. 

 

5. 해결 방안은 UI_Inven.cs에서 다음과 같이 UI_Inven_Item을 추가해주거나 Unity tool을 통해 UI_Inven_Item을 직접적으로 추가해주는 것이다. 

* UI_Inven.cs

// 실제 인벤토리 정보를 참고해서 해야하지만 현재는 강제적으로
        for (int i = 0; i < 8; i++)
        {
            GameObject item = Managers.Resource.Instantiate("UI/Scene/UI_Inven_Item");
            item.transform.SetParent(gridPanel.transform); // 강제로 Parent를 정해줌

            // component 추가
            Util.GetOrAddComponent<UI_Inven_Item>(item);
        }

 

6. 이 상태로 실행하면 component가 추가되고 다음과 같이 Text가 바뀐것을 확인할 수 있다. 

 

7. 사실 실제 인벤토리 정보를 가져와서 해당 item 각각에 name을 정해줘야한다. 

다음과 같이 UI_Inven_Item.cs 파일에서 name을 지정해주는 함수를 만들어서 Text를 바꿔주고 

UI_Inven.cs 에서 해당 name을 setting 해준다. 

*UI_Inven_Item.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class UI_Inven_Item : UI_Base
{
    enum GameObjects
    {
        Itemicon,
        ItemNameText,
    }
    string _name;
    void Start()
    {
        Init();
    }
    public override void Init()
    {
        Bind<GameObject>(typeof(GameObjects)); // 각각의 GameObject를 Binding 해줌
        // 해당 GameObject의 Text component를 가져온다.
        GET<GameObject>((int)GameObjects.ItemNameText).GetComponent<Text>().text = _name; 
    }
    
    public void SetInfo(string name)
    {
        _name = name;
    }
}

*UI_Inven.cs

// 실제 인벤토리 정보를 참고해서 해야하지만 현재는 강제적으로
        for (int i = 0; i < 8; i++)
        {
            GameObject item = Managers.Resource.Instantiate("UI/Scene/UI_Inven_Item");
            item.transform.SetParent(gridPanel.transform); // 강제로 Parent를 정해줌

            // component 추가
            UI_Inven_Item invenItem = Util.GetOrAddComponent<UI_Inven_Item>(item);
            invenItem.SetInfo($"집행검{i}번");
        }

8. 실행하면 다음과 같이 번호가 나오는 것을 확인할 수 있다. 

 

  • Item Log 보여주기

1. 다음과 같이 UI_Inven_Item.cs에서 Event를 추가하여 해당 item의 Name을 출력하는 event를 추가한다.

* UI_Inven_Item.cs

public override void Init()
    {
        Bind<GameObject>(typeof(GameObjects)); // 각각의 GameObject를 Binding 해줌
        // 해당 GameObject의 Text component를 가져온다.
        GET<GameObject>((int)GameObjects.ItemNameText).GetComponent<Text>().text = _name;

        GET<GameObject>((int)GameObjects.Itemicon).AddUIEvent((PointerEventData) => { Debug.Log($"아이템 클릭! {_name}"); });
    }

 

2. play를 하면 다음과 같이 console 창에 출력되는 것을 볼 수 있다. 

 

 

<코드 정리>

 

1. UI_Inven.cs에서 Util.GetOrAddComponent으로 component를 추가하는 것이 아니라 바로 gameobject.GetOrAddComponent으로 접근하기 위해 Util의 해당 함수를 Extension.cs에 추가한다. 

*Extension.cs

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

public static class Extension 
{
    public static T GetOrAddComponent<T>(this GameObject go) where T : UnityEngine.Component
    {
        return Util.GetOrAddComponent<T>(go);
    }
    public static void AddUIEvent(this GameObject go, Action<PointerEventData> action, Define.UIEvent type = Define.UIEvent.Click)
    {
        UI_Base.AddUIEvent(go, action, type);
    }


}

*UI_Inven.cs

// 실제 인벤토리 정보를 참고해서 해야하지만 현재는 강제적으로
        for (int i = 0; i < 8; i++)
        {
            GameObject item = Managers.Resource.Instantiate("UI/Scene/UI_Inven_Item");
            item.transform.SetParent(gridPanel.transform); // 강제로 Parent를 정해줌

            // component 추가
            UI_Inven_Item invenItem = item.GetOrAddComponent<UI_Inven_Item>();
            invenItem.SetInfo($"집행검{i}번");
        }

 

2. Unity를 Play하면 다음과 같이 Prefab에 "Clone" 문자열이 붙는다. 

 

=> ResourceManager.cs에서 Instantiate 함수를 수정한다. 

*ResourceManager.cs

public GameObject Instantiate(string path, Transform parent = null)
    {
        // 위에서 정의한 Load를 통해 가져옴 
        GameObject prefab = Load<GameObject>($"Prefabs/{path}");

        // prefab 가져오기 실패 -> 문제가 있는 것
        if(prefab == null)
        {
            // Log를 남김
            Debug.Log($"Failed to load prefab : {path}");
            return null;
        }

        GameObject go = Object.Instantiate(prefab, parent);
        int index = go.name.IndexOf("(Clone)"); // "Clone" 문자열의 인덱스를 반환한다. 

        if (index > 0)
            go.name = go.name.Substring(0, index); // Substring으로 0부터 index까지 문자열을 자른다. 

        return go;
    }

실행 후

3. UI_Base.cs에서 자주사용하는 함수 중 GameObject를 반환하는 함수가 없으므로 추가

*UI_Base.cs

protected GameObject GetObject(int idx) { return GET<GameObject>(idx); }

 

4. UI_Base.cs에서 추가한 AddUIEvent함수가 이름이 안붙어서 BindEvent로 바꾸고자한다.

ctrl + shift + F를 눌러서 AddUIEvent -> BindEvent로 바꾼다.

 

5. UI_Inven.cs에서 Prefab을 Instantiate한 부분을 UIManager을 통해 만들도록 한다. 사실 UI_Inven_Item은 Popup도 아니고 Scene도 아닌 UI_Inven에 기생하는 것이기 때문에 별도의 파일로 뽑아서 관리하도록 한다. 마찬가지로 Script도 SubItem 폴더를 만들어주어 옮긴다. 

 

=> 이렇게 한 후 UIManager.cs에서 Popup과 Scene에서 했던 것과 같이 함수를 만들어 준다. 

-> UIManager에서 자동으로 Prefab을 가져오고 Script를 연결해준다. 

* UIManager.cs

public T MakeSubItem<T>(string name = null) where T: UI_Base
    {
        // 이름을 받지않으면 T의 이름으로 
        if (string.IsNullOrEmpty(name))
            name = typeof(T).Name;

        // UI Prefab을 가져옴
        GameObject go = Managers.Resource.Instantiate($"UI/SubItem/{name}");

        return go.GetOrAddComponent<T>();
    }

-> 부모 연결도 한번에 해주기위해 UIManager.cs를 다음과 같이 바꾼다. 

* UIManager.cs

public T MakeSubItem<T>(Transform parent = null, string name = null) where T: UI_Base
    {
        // 이름을 받지않으면 T의 이름으로 
        if (string.IsNullOrEmpty(name))
            name = typeof(T).Name;

        // UI Prefab을 가져옴
        GameObject go = Managers.Resource.Instantiate($"UI/SubItem/{name}");

        if (parent != null)
            go.transform.SetParent(parent);

        return go.GetOrAddComponent<T>();
    }

* UI_Inven.cs

// 실제 인벤토리 정보를 참고해서 해야하지만 현재는 강제적으로
        for (int i = 0; i < 8; i++)
        {
            GameObject item = Managers.UI.MakeSubItem<UI_Inven_Item>(gridPanel.transform).gameObject; // UIManager의 함수를 이용
            // component 추가
            UI_Inven_Item invenItem = item.GetOrAddComponent<UI_Inven_Item>();
            invenItem.SetInfo($"집행검{i}번");
        }
댓글수0