
7일차는 못다한 UI 작업을 마무리하는 날이었다. 이 날의 주요 쟁점은 ESC키의 역할이 중복되어 잘못하면 꼬일 수도 있지 않을까? 였다.
ESC키를 누르면 열려있는 UI를 닫을 수 있도록 하는 기능이 일시정지가 되는 기능이 충돌하게 된 것인데, 이 부분에 대해서는 조금 더 세심하게 구조를 짤 필요가 있지 않을까 싶다. 작업 내역은 다음과 같다.
아이콘이나 Scene 배경에 활용된 이미지는 원하는 이미지, 에셋이 없어 AI 이미지 생성을 활용했다. 나름 나쁘지 않게 나와서 만족스럽게 사용한 것 같다.
이번에 소개할 코드는 CSV 파일을 활용하여 대사, PopUp 문구 관리하는 방법이다.
TextLoaderTextLoader는 CSV 파일을 읽어오기 위한 Class로 TextAsset csvFile = Resources.Load<TextAsset>("popup_texts") 로 Resources폴더에 저장된 popup_texts 이름의 CSV 파일을 불러오는 것이 가능하다.
public class TextLoader : MonoBehaviour
{
// CSV 파일에서 읽어온 데이터를 저장하기 위한 Dictionary
private Dictionary<string, string> _popupTexts;
private void Awake()
{
Init();
}
private void Init()
{
// 데이터 저장을 위한 Dictionary 초기화
_popupTexts = new Dictionary<string, string>();
// TextAsset : CSV 파일을 담는 변수
// Resources.Load<TextAsset>() : Resources 폴더에 있는 CSV 파일을 읽어오기 위한 메서드
// popup_texts : 파일 이름
TextAsset csvFile = Resources.Load<TextAsset>("popup_texts");
// 해당 파일이 없으면 돌아가기
if (csvFile == null) return;
// CSV 파일에 저장된 데이터를 개행(줄바꿈)으로 분리하여 개별 문장으로 저장
string[] lines = csvFile.text.Split('\n');
// lines에 저장된 각 문장마다 해당 루틴 실행
for (int i = 1; i < lines.Length; i++)
{
// 문장의 앞,뒤 공백 제거
string line = lines[i].Trim();
// 공백을 제거했을 때 아무 것도 없는 경우 스킵
if (string.IsNullOrEmpty(line)) continue;
// 문장을 ,(쉼표)로 구분
string[] parts = line.Split(',');
// 쉼표로 구분 했을 때 생기는 문장이 2개 이상인 경우(쉼표가 1개 이상인 경우)
if (parts.Length >= 2)
{
// 첫 번째는 'ID'
string id = parts[0];
// 두 번째는 'Text'
string text = parts[1];
// 세 번째 그 이상부터는 CSV 파일에 저장된 데이터에 따라 다르지만
// 현재 파일에는 id, text 밖에 없기 때문에 세 번째 이상은 모두 text로 해석
if (parts.Length > 2)
{
// 다음 문장들을 text에 추가
for (int j = 2; j < parts.Length; j++)
{
text += "," + parts[j];
}
}
// id를 key, text를 data로 묶어 Dictionary에 저장
_popupTexts[id] = text;
}
}
}
// 외부에서 데이터를 가져오기 위한 메서드
// id : CSV 파일의 ID, Dictionary의 Key
public string GetPopupText(string id)
{
return _popupTexts[id];
}
}
TextManagerTextManager는 텍스트 출력을 담당하는 매니저로 게임 시작부터 끝까지 사용되기 때문에 Singleton패턴과 함께 DontDestroyOnLoad로 유지시켰다.
public class TextManager : Singleton<TextManager>
{
// 팝업 문구를 출력하기 위한 UI
private PopUpUI _popUpUI;
// CSV파일의 데이터를 가지고 있는 TextLoader를 참조
private TextLoader _textLoader;
// Singleton 기본 세팅과 DontDestroyOnLoad, TextLoader 초기화
protected override void Awake()
{
base.Awake();
DontDestroyOnLoad(gameObject);
Init();
}
// 초기화 순서를 위해 외부 참조(PopUp UI)의 경우 Start()에서 처리
private void Start()
{
ConfigUI();
}
// 초기화 순서가 상관 없는 내부 참조(TextLoader)의 경우 Awake()에서 처리
private void Init()
{
_textLoader = transform.GetOrAddComponent<TextLoader>();
}
// PopUp UI 초기화, UI Binder를 활용
private void ConfigUI()
{
if (_popUpUI == null)
{
_popUpUI = UIBinder.Instance.GetPopUpUI();
}
}
// CSV 파일에 저장된 text를 불러오는 메서드
private void PopupText(string id)
{
// 외부에서 해당 메서드가 호출되어 TextManager가 생성되는 경우
// Start보다 먼저 수행되기 때문에 ConfigUI를 호출해야한다.
// 첫 씬에서 Manager 빈 오브젝트에 묶어 생성할 경우 호출하지 않아도 무관
ConfigUI();
// CSV 파일에 저장되어있는 데이터 불러오기
string popupText = _textLoader.GetPopupText(id);
// PopUp UI 활성화 및 출력
_popUpUI.gameObject.SetActive(true);
_popUpUI.PopupText(popupText);
}
// PopUp UI를 닫는 메서드
private void HideText()
{
// 문구 초기화 및 UI 비활성화
_popUpUI.ResetText();
_popUpUI.gameObject.SetActive(false);
}
// 일정 시간(time)이 지나면 출력된 PopUp UI창이 닫히도록 구현한 Coroutine
private IEnumerator PopupTextRoutine(string id, float time)
{
PopupText(id);
yield return new WaitForSeconds(time);
HideText();
}
// 외부에서 호출하여 실제로 사용되는 메서드
public void PopupTextForSecond(string id, float time)
{
StartCoroutine(PopupTextRoutine(id, time));
}
}
PopUpUI팝업 창의 기능을 담당하는 Class로 하위에 있는 TextMeshPro의 Text를 관리한다.
public class PopUpUI : MonoBehaviour
{
[Header("Drag&Drop")]
[SerializeField] private TMP_Text _popUpText;
private void Awake()
{
Init();
}
public void PopupText(string text)
{
_popUpText.text = text;
}
public void ResetText()
{
_popUpText.text = "";
}
private void Init()
{
if (_popUpText == null)
{
_popUpText = GetComponentInChildren<TMP_Text>();
}
}
}