TIL

2024 12 16 TIL

noc777 2024. 12. 16. 10:51

캔버스를 삭제하여야하는 상황이 발생하여 코드를 작성하다. 캔버스 컴포넌트만 삭제하여 이런 오류가 발생하였다.

(게임 오브젝트를 삭제해야하는데 캔버스 컴포넌트만 삭제해버린..)

 

캔버스에 달려있던 CanvasScaler 와 GraphicRaycaster가 캔버스가 제거되는 것을 막았다는 것인데

 

왜 이런 문제가 발생하나

그것은 두 컴포넌트가 Canvas 컴포넌트에 의존성을 가지고 있어서라고 한다.

 

CanvasScaler : 캔버스의 크기를 해상도에 맞게 조절하는 기능의 컴포넌트

GraphicRaycaster : UI 요소들이 상호작용(클릭이나 터치 등)되는 이벤트를 처리하는 컴포넌트

 

이 둘은 캔버스 없이는 사용이 불가능한 컴포넌트들이다.

따라서 유니티에서 두 컴포넌트가 삭제되기 전에 Canvas 컴포넌트가 삭제되는 것을 막아준 것이었다.

 

 

as 형변환 문제

 

제네릭을 통해 나누었던 함수들을 통합하려는 과정에서 형변환이 되지 않아 null이 반환된 문제가 있었다.

 public void DestroyUI<T>() where T : UIBase
 {
     string uiName = typeof(T).ToString();

     Dictionary<string, T> pool = ClassifyPool<T>();

     if (pool == null)
     {
         Debug.Log("pool is null");
         return;
     }

     if (pool.TryGetValue(uiName, out T value))
     {
         value.Destroy();
     }
 }

 

private Dictionary<string, T> ClassifyPool<T>() where T : UIBase
{
    if (typeof(UIPopup).IsAssignableFrom(typeof(T)))
    {
        return UIPopupPool as Dictionary<string, T>;
    }
    else if (typeof(UIScene).IsAssignableFrom(typeof(T)))
    {
        return UIScenePool as Dictionary<string, T>;
    }
    else
    {
        Debug.Log("Can't find curect Pool");
        return null;
    }
}

제네릭 타입에 맞는 딕셔너리를 반환해주는 것이 원래의 목적이었다.

조건문까지는 잘들어갔는데 딕셔너리를 반환해주는 과정에서 형변환이 실패하여 null을 반환한다.

이유는 객체를 저장하는 딕셔너리와 반환한 딕셔너리의 타입이 다르기 때문이다.

Dictionary<string,UIPopup> UIPopupPool != Dictionary<string,T> returnPool

제네릭을 사용하여서 T가 UIPopup을 상속받는 클래스라도 엄연히 딕셔너리의 타입은 다른 것이기에 오류가 발생하였다.

 

그렇다고 한 메서드에서 처리하여 반환할 필요가 없게 하기에 나중의 유지보수가 어렵기에 고민을 좀 했던것 같다.

 

 

해결

근본적으로 딕셔너리를 나누어서 발생한 문제임을 깨달았다. 

처음 생각으론 UIPopup과 UIScene 으로 UIBase 클래스의 갈래를 나누었기에 딕셔너리도 따로 관리하여야겠다고 생각하였으나

 

어차피 같은 UIBase를 상속받는 클래스들이기에 두 딕셔너리를 합치고 이후 메서드에서 타입을 구분하기로 하였다.

 

//딕셔너리 통합
private Dictionary<string,UIBase> UIPool = new Dictionary<string,UIBase>();

 

그로인해 이전에 타입별로 딕셔너리를 나누어 담는 코드도 수정하였다. 

//제네릭을 통해 해당하는 UI 생성
public T CreateUI<T>() where T : UIBase //UI의 생성 메서드
{
    string uiName = typeof(T).ToString();

    if(UIPool.TryGetValue(uiName,out var value)) //이제 타입별로 가져올 딕셔너리를 나누지 않고 키가 있는지 여부만 판단
    {
        return value as T;
    }

    T uiResource = ResourceManager.Instance.LoadAsset<T>(uiName, eAssetType.Prefab, eCategoryType.UI);

    if(uiResource == null) return null;
         
    Canvas parentCanvas = SetNewCanvas(uiName);
    T ui = Instantiate(uiResource, parentCanvas.transform);
    ui.canvas = parentCanvas;
    ui.name = ui.name.Replace("(Clone)", "");
    UIPool.Add(uiName, ui);

    return ui;
}

//캔버스 설정
private Canvas SetNewCanvas(string uiName)
{
    GameObject canvasObject = new GameObject(uiName + "Canvas");

    var canvas = canvasObject.AddComponent<Canvas>();
    canvas.renderMode = RenderMode.ScreenSpaceOverlay;

    var canvasScaler = canvasObject.AddComponent<CanvasScaler>();
    canvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
    canvasScaler.referenceResolution = new Vector2(ScreenSizeX, ScreenSizeY);

    canvasObject.AddComponent<GraphicRaycaster>();

    return canvas;
}

 

//UI 파괴 
public void DestroyUI<T>() where T : UIBase
{
    string uiName = typeof(T).ToString();

    if(UIPool.TryGetValue(uiName,out var value))
    {
        value.Destroy();
        UIPool.Remove(uiName);
    }
}

//UI 활성화
public void Show<T>() where T : UIBase
{
    string uiName = typeof(T).ToString();

    if (UIPool.TryGetValue(uiName, out var value))
    {
        value.Show();
    }
}

//UI 비활성화
public void Hide<T>() where T : UIBase
{
    string uiName = typeof(T).ToString();

    if (UIPool.TryGetValue(uiName, out var value))
    {
        value.Hide();
    }
}

//해당 타입의 UI 전체 파괴
public void DestroyAllUI<T>() where T : UIBase
{
    foreach(var ui in UIPool.Values)
    {
        if(ui is T)
        {
            ui.Destroy();
            UIPool.Remove(ui.name);
        }
    }
}

//해당 타입의 UI 전체 활성화
public void ShowAllUI<T>() where T : UIBase
{
    foreach (var ui in UIPool.Values)
    {
        if (ui is T)
        {
            ui.Show();
        }
    }
}

//해당 타입의 UI 전체 비활성화
public void HideAllUI<T>()  where T : UIBase
{
    foreach (var ui in UIPool.Values)
    {
        if (ui is T)
        {
            ui.Hide();
        }
    }
}

나머지 코드들도 단일 활성화 비활성화 파괴는 공용이 되었기에

그대로 딕셔너리에서 키만 받아와서 사용하면되고

 

UI타입에 따라 전체를 통제하는 메서드만 구분지어 놓았다. 

코드들이 다 반복되는 것 같아서 하나로 합치는 것도 좋겠지만

아직은 제네릭을 제대로 적용시키는 것이 미숙하여

나중에 리팩토링하는 과정으로 남겨두도록한다.

 

 

매니저를 만들고 수정하는 것은 여기까지하고 이제 미루었던 퍼즐 작업을 다시 하기로 하였다.

매니저도 없는 프로젝트 시작 가장 처음에 만든 것이라 그런지

다른 코드들과 동떨어진 느낌이라 다시 코드를 확인해보아야할 것 같다.

 

그리고 메인퍼즐의 기믹을 아직 작성하지 않아서 해당 부분도 신경을 써주어야 할 것 같다.

뭔가 다른 퍼즐 게임들의 예시가 있다면 보고 싶은 데 (퍼즐 데이터 관리나 , 리소스 관리 등)

개념들을 배워나가면서 활용하는 것에서 항상 막히는 것 같아 걱정이다.

 

맵관련해서도 동양풍의 맵이 많지 않아 일체화된 맵을 구하기가 어렵고 

프리팹들이 부품별로 나누어진 에셋을 쓰게되어서 이 부분도 좀 걱정이 많이된다.

 

또한 이후의 맵 최적화에 더티 플래그를 어떻게 구현해야하는지도 공부해봐야할 것 같다. 

 

'TIL' 카테고리의 다른 글

2024 12 18 TIL  (0) 2024.12.18
2024 12 17 TIL  (0) 2024.12.17
2024 12 15 TIL  (0) 2024.12.15
2024 12 13 TIL  (0) 2024.12.13
2024 12 12 TIL  (1) 2024.12.12