TIL 0207 게임개발 숙련 개인 과제 - 4 / UI 관리 / 제네릭 & 싱글톤

강성원·2024년 2월 7일
0

TIL 오늘 배운 것

목록 보기
32/69

개발한 내용

이번 과제는 일정, 컨디션 관리 대폭망이었다.

과제의 완성은 체념하고 "UI관리법 터득하기"를 나만의 목표로 세웠다.
결과는 필수구현만 개발한 뉴비의 깃허브..

전에 같은 조였던 분의 도움을 받아 UI를 프리팹으로 두고 필요할 떄 객체화 해서 사용하는 방법을 알게됐다.

프리팹 객체화 시키기

우선 이름이 동일한 스크립트와 프리팹을 준비한다.

public static class Util
{
    public static GameObject Instantiate<T>(string path = null, Transform parent = null) where T : UIBase
    {
        if (string.IsNullOrEmpty(path))
            path = "Prefabs";

        string fileName = typeof(T).Name; 

        GameObject prefab = Resources.Load<T>($"{path}/{fileName}").gameObject;

        GameObject go = Object.Instantiate(prefab, parent);
        go.name = fileName;

        return go;
    }
    
    ,,,생략,,,
}
  • 제네릭으로 받은 타입(스크립트 이름)을 가져올 파일 명으로 할당한다.
  • 스크립트의 이름(클래스 이름)으로 리소스 폴더에서 프리팹을 가져와 인스턴스화 시킨 후에 반환한다.

이름으로 자식 가져오기

이 함수는 UI에 있는 버튼이나 텍스트 등을 가져와 사용하기 위해 구현했다.

public static class Util
{
    ,,,생략,,,

    // 이름으로 자식 가져오기
    public static T FindChild<T>(GameObject go, string componentName, bool isRecur = false) where T : UnityEngine.Object
    {
        if (go == null) return null;

        if(!isRecur)
        {
            for (int i = 0; i < go.transform.childCount; ++i)
            {
                Transform transform = go.transform.GetChild(i);
                if (transform.name == componentName)
                {
                    T component = transform.GetComponent<T>();
                    if (component != null)
                        return component;
                }
            }
        }
        else
        {
            foreach(T component in go.GetComponentsInChildren<T>())
            {
                if(componentName == component.name)
                    return component;
            }
        }

        return null;
    }
}
  • T가 Button인 것으로 예를 들어본다.

  • 컴포넌트의 이름을 받고 부모 오브젝트의 자식들을 전부 순회하며 이름과 동일한지 판단한다.

  • 이름과 동일하면 Button을 반환한다.

  • 자식의 자식의 (그 이상 포함) 오브젝트까지 찾아봐야 할 경우에는 그냥 GetComponentsInChildren메서드를 사용한다.

사용

public class UIManager : MonoBehaviour
{
    GameObject UI_Title;
    ,,,비슷한 내용 생략,,,

    public GameObject[] Popups = new GameObject[(int)POPUP.INPUT_ERROR+1];

    // Start is called before the first frame update
    void Awake()
    {
        Init();
    }

    void Init()
    {
        UI_Title = Util.Instantiate<UI_Title>("Prefabs/UI", GameObject.Find("Canvas").transform);
        ,,,비슷한 내용 생략,,,
    }
    
    ,,,생략,,,
}

UtilInstantiate로 타입명 UI_Title와 이름이 동일한 프리팹을 객체화 시킨다.

public class UI_Deposit : UIBase
{
    private UIManager UI;
    private ATM ATM;

    [SerializeField] private Button ten;
    ,,,비슷한 내용 생략,,,

    private void Start()
    {
        UI = ATM.instance.UIManager;
        ATM = ATM.instance;
        
        // 가져오기
        ten = Util.FindChild<Button>(gameObject, "Ten");
        ,,,비슷한 내용 생략,,,

        // 이벤트 등록
        ten.onClick.AddListener(RequestDepositTen);
        ,,,비슷한 내용 생략,,,
    }
    
    ,,,생략,,,
}

현재UI_Deposit.cs스크립트는 UI_Deposit에 붙어있는 상태다.
UtilFindChildTen이라는 이름의 Button을 가져와서 사용한다.

공부한 내용

제네릭 & 싱글톤 특강

오늘 진행된 특강세션에서는 제네릭을 이용한 싱글톤에 대한 내용을 다루었다.

바로 코드.

public class SingletoneBase : MonoBehaviour
{
    public static SingletoneBase instance;
    
    void Awake()
    {
    	intance = this;
	}
}
============================================================
public class GameManager : SingletoneBase
{
    public void Something(){}
}
============================================================
public class AudioManager : SingletoneBase
{
    GameManager.instance.Something(); // 불가능!!!
}

싱글톤이 여러개가 필요할 때 이런 식으로 부모가 되는 싱글톤이 있으면 코드의 반복이 줄어든다.

하지만 위 코드에는 문제점이 있는데, 바로 AudioManager에서 싱글톤인 GameManager의 멤버에는 접근할 수 없다는 것.

간단하게 말하면 맨 위의 싱글톤 베이스에서 instance의 타입은 SingletoneBase이다.

싱글톤 베이스를 상속받은 GameManager부분은 접근하지 못하는 것이 당연하다.

이를 해결하기 위해서 제네릭을 사용한다.

public class SingletoneBase<T> : MonoBehaviour where T : MonoBehaviour
{
    // 프로퍼티
    private static T _instance;

    public static T Instance
    {
        get
        {
            if (_instance == null)
            {
                string typeName = typeof(T).FullName; 
                GameObject go = new GameObject(typeName);
                _instance = go.AddComponent<T>();
                
                DontDestroyOnLoad(go);
            }

            return _instance;
        }
    }


    void Awake()
    {
        Init();
    }

    public virtual void Init()
    {
        Debug.Log(transform.name + " is Init");
    }
}

============================================================
public class GameManager : SingletoneBase<GameManager>
{
    public void Something(){}
}
  • 제네릭을 사용해서 _instance의 타입을 지정해주면 SingletoneBase를 상속받은 자식들의 영역에도 접근할 수 있게된다.

  • 그리고 프로퍼티 부분은 싱글톤의 오류를 방지하기 위한 코드가 적혀있다.(디버그 찍어봐야겠다.)

profile
개발은삼순이발

0개의 댓글