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)
문제를 해결하기 위해 우선 방어코드를 추가하였음
private void Awake()
{
uiManager = FindObjectOfType<UIManager>();
if (uiManager == null)
Debug.LogError("UIManager를 찾을 수 없습니다!");
}
문제를 확인한 결과 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);
}
그래서 떠올린 방법은
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;
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();
}
public void ChangeState(UIState state)
{
InitState();
if (state != UIState.None && uiDict.ContainsKey(state))
uiDict[state].gameObject.SetActive(true);
currentState = state;
}
public void ToggleUI(UIState state)
{
if (currentState == state)
{
ChangeState(UIState.None); // 이미 켜져 있으면 눌렀을 때 꺼짐
}
else
{
ChangeState(state); // 켜져 있지 않으면 해당 UI 켜짐
}
}