TIL 트러블 슈팅-SpartaMetaVerse-2

rain cloud·2025년 7월 27일
0

TIL

목록 보기
8/11
post-thumbnail

트러블 슈팅

  • UI를 제작했고, UI의 작동방식을 작성하고 활성화와 비활성화를 설정해주기 위해서 UIManger.cs를 작성하였으나, SetActive가 제대로 작동하지 않는 문제가 발생하였음

  • 이를 해결하는 과정에 대해서 트러블 슈팅을 작성하였음.


배경

  • 상호작용이 가능한 NPC를 배치하는 것이 필수 기능 구현에 있어서, 이를 수행하던 도중, 상호작용 기능을 구현하기 위해서 F를 누르면 대화 UI가 나오도록 기능 구현을 시도하였음

  • 문제는 F를 눌러도 활성화가 안되거나, 아예 UI의 인식을 못하는 문제가 발생하였음.


발단

  • 강의에서 배운대로, enum을 활용해서 사용할 UI의 갯수만큼 작성하였음

  • BaseUI.cs를 작성하여 UIManger를 참조와 동시에 GetUIState() 메서드를 작성하여 enum UIState에 작성한 이름이 실제로 SetActive 기능을 수행할 수 있게 작성해 주었음

  • 그리고 BaseUI를 상속받는 각각의 UI 스크립트를 작성하였음

  • 문제는 BaseUI를 상속받은 UI스크립트인 ElfUI.cs의 스크립트를 Unity 에서 인식하지 못하는 에러가 발생하였음.

    NullReferenceException: Object reference not set to an instance of an object
    ElfNPC.Talk () (at Assets/Scripts/NPC/ElfNPC.cs:19)
    BaseController.Communicate () (at Assets/Scripts/StatusController/BaseController.cs:148)
    BaseController.FixedUpdate () (at Assets/Scripts/StatusController/BaseController.cs:76)
    • 일허게 NullReferencException이 발생하였고, 이를 해결하기 위해서 문제에 대한 이유를 확인하였음

전개

  • 문제를 해결하기 위해 우선 방어코드를 추가하였음

    private void Awake()
    {
        uiManager = FindObjectOfType<UIManager>();
        if (uiManager == null)
            Debug.LogError("UIManager를 찾을 수 없습니다!");
    }
    • 이렇게 추가하였는데, 이것 뿐만 아니라 각 의심되는 부분은 전부 Debug.LogError를 작성하여 어디서 문제가 생겼는지를 먼저 확인하였음
  • 문제를 확인한 결과 FindObjectOfType, GetComponentInChildren(), transform.Find() 등은 비활성화된 자식 오브젝트를 찾지 못한다는 것을 알게 되었음

  • 비활성화된 컴포넌트로 탐색할 때 기본 옵션상 무시되고, 이는 Awake나 Start에서 작동하지 않는 다는 것 또한 알게 되었음

  • 먼저 GetComponent처럼 Hierarchy를 직접 탐색해서 정보를 가져오지 않고 Inspector에서 직접 public 변수로 할당하는 방법을 사용하였음

    public class UIManager : MonoBehaviour
    {
        [SerializeField] private ElfUI elfUI;
        [SerializeField] private DwarfUI dwarfUI;
    }
    • 이런 식으로, 직접 스크립트를 넣어서 등록할 수 있게 작성하였음
  • ChangeState()메서드도,

    public void ChangeState(UIState state)
    {
        elfUI.gameObject.SetActive(state == UIState.Elf);
        dwarfUI.gameObject.SetActive(state == UIState.Dwarf);
    }
    
    • 이렇게 직접적이면서도 직관적으로 코드를 변경하였음.
    • 이렇게 하면 자연스럽게 비활성화되어 있던 UI를 활성화 할 수 있었음.
    • 문제는 다시 활성화에서 비활성화로 변경하는게 안되는 문제가 발생하였음
    • 그냥 단순히 SetActive(false)를 적용시켜주면 될 문제라고 생각했는데, 여러 스크립트로 코드들을 분산해 놓았기도하고, Input System을 활용하였기 때문에 어떻게 참조를 해서 false를 바꿔줘야 할지를 생각해내지 못하였음

절정

  • 그래서 떠올린 방법은

    public void SetElfCommunicate(bool isActive)
    {
        ChangeState(UIState.Elf, isActive);
    }
    
    public void SetDwarfCommunicate(bool isActive)
    {
        ChangeState(UIState.Dwarf, isActive);
    }
    
  • 이렇게 직접 bool값을 매개변수로 받아서, true와 false의 상태를 변경시킬 수 있도록 하는 것이었음

  • 이렇게 수정한 것과 맞춰서 ChangeState()메서드도 수정을 해주었음

    public void ChangeState(UIState state, bool isActive)
    {
        currentUIState = state;
    
        if (state == UIState.Elf)
        {
            elfUI.gameObject.SetActive(isActive);
            dwarfUI.gameObject.SetActive(false);
        }
        else if (state == UIState.Dwarf)
        {
            dwarfUI.gameObject.SetActive(isActive);
            elfUI.gameObject.SetActive(false);
        }
    }
  • 그런데 또 문제가 이 2개의 UI만 쓸것도 아닌데 이렇게 직접 하드코딩 방식으로 작성을 해버리면, 나중에 추가할 때마다 각각의 메서드들을 추가해야 한다는 생각이 들었음

  • 그래서 AI와 구글링을 통해서 해결법을 찾아보았음. 해결 방법으로는 Dictionary 기반 동적 UI 매니저를 활용하는 것이었음

  • Enum, Dictionary, List 등 자료구조로 모든 UI를 관리하고, UIState(enum) 값과 실제 UI 컴포넌트를 자동으로 매칭시킬 수 있어서, 상태의 전환과 UI 추가가 모두 반복문과 키값으로 일괄처리할 수 있는 방법이었음

    [SerializeField] private List<BaseUI> uiList;
    private Dictionary<UIState, BaseUI> uiDict;
    private UIState currentState = UIState.None;
    
    • 이렇게 List랑 Dictionary를 선언해 주었음. 제너릭으로 BaseUI를 사용한건, 등록할 UI들의 스크립트가 BaseUI를 상속받아서 BaseUI로 사용하였음
    protected void InitState()
    {
        currentState = UIState.None;
        foreach (var ui in uiDict.Values)
            ui.gameObject.SetActive(false); 
    }
    
    • 초기화 해주었음
    private void Awake()
    {
        uiDict = new Dictionary<UIState, BaseUI>(); 
        foreach (var ui in uiList)
            uiDict.Add(ui.GetUIState(), ui);
        InitState();
    }
    
    • uiDict를 new로 공간을 할당해주었고,
    • Inspector의 uiList에서 각 UI오브젝트를 꺼내와서 BaseUI의 GetUIState()를 키값으로, 해당 오브젝트를 value 값으로 넣어서 딕셔너리에 등록해주었음
    public void ChangeState(UIState state)
    {
        InitState();
    
        if (state != UIState.None && uiDict.ContainsKey(state))
            uiDict[state].gameObject.SetActive(true);
    
        currentState = state;
    }
    
    • 인자로온 state값에 해당하는 UI만 활성화되도록 설정하였음
    public void ToggleUI(UIState state)
    {
        if (currentState == state)
        {
            ChangeState(UIState.None); // 이미 켜져 있으면 눌렀을 때 꺼짐
        }
        else
        {
            ChangeState(state); // 켜져 있지 않으면 해당 UI 켜짐
        }
    }
    
    • 이미 켜져있으면 비활성화되도록 설정해주는 ToggleUI메서드도 작성하였음

결말

  • 이렇게 작성을 해주니까 Inspector창에서 등록할 UI들의 각 스크립트를 드래그엔 드롭으로 리스트에 추가하고,
  • enum에 사용할 이름을 추가해준다음, 각 BaseUI를 상속받는 앞서 추가한 스크립트에 GetUIState()를 오버라이딩해서 리턴값으로 해당 UI를 받아오게만 해주면 쉽게 등록이 가능하게 되었다.
  • ToggleUI, ChangeState()메서드 들을 활용해서 쉽게 다른 스크립트로 활성화, 비활성화를 다룰 수 있게 되었고, 문제를 해결할 수 있게 되었다.
profile
게임 개발, 기획

0개의 댓글