유니티에서 다른 언어를 지원하기 위해서 로컬라이제이션이 필요하다.
유니티에서 제공하는 로컬라이제이션 튜토리얼을 보고 로컬라이제이션을 숙지하게 되었다.
생각보다 어려운 내용이라서 소화하기 편하도록 자세하게 준비했다.
추가적으로 엑셀을 통하여 로컬라이제이션을 할 수 있는 방법까지 고려했다.
크... 배려심 오졌고
목차
1. 실제 프로젝트 시연
2. 로컬라이제이션 이미지 도식화
3. 로컬라이제이션 스크립트 핵심 부분 설명
4. 샘플 프로젝트 및 참고 링크
1. 실제 프로젝트 시연
토글 형식으로 언어를 바꾸면 즉시 모든 텍스트 요소들이 언어에 맞추어 바뀐다.
한글 로컬라이제이션 데이터
영어 로컬라이제이션 데이터
각 데이터는 키와 밸류로 관리되고 있다.
눈여겨볼 점은 key값은 통일되어야 한다는 점이다.
입력은 엑셀로 하고 유니티에서 csv 파일을 json파일로 저장해서 json파일을 (정확히는 json형식의 txt 파일) 게임에서 불러와서 로컬라이제이션을 한다.
나중에 다시 설명을 할 것이지만 로컬라이제이션 세팅을 하고 나면 text 컴포넌트에 Localized Text 컴포넌트를 붙이고 텍스트에 해당하는 key값을 적어주면 알아서 언어 값에 따라서 바뀐다.
2. 로컬라이제이션 이미지 도식화
오늘따라 친절이 포텐터져서 이해하기 쉽도록 도식화까지 해놨다.
먼저 csv파일로 로컬라이제이션 데이터를 저장한다.
1. 유니티 에디터(LocalizedTextEditor)를 통하여 csv 파일을 json 형태의 txt 파일로 저장한다.
2. LocalizationManager에서 로컬라이제이션 json(txt) 데이터를 불러온다.
3. 텍스트 컴포넌트에 부착된 LocalizedText 컴포넌트가 키값에 해당하는 데이터를 불러와서 텍스트 컴포넌트의 텍스트를 바꿔준다.
4. 인게임에서 바뀐 텍스트가 정상적으로 출력된다.
3. 로컬라이제이션 스크립트 핵심 부분 설명
아래 4개의 스크립트에서 핵심적인 부분만 설명을 하겠다.
사용 방법은 4번 샘플 프로젝트에서 설명하기 때문에 스크립트에 대한 이해가 필요 없어도 괜찮으니 넘어가도 좋다.
LocalizedTextEditor
유니티 GUI 에디터에 대한 부분은 생략하도록 하겠다. (사실 잘 모르기도 함)
private void LoadCSVFile()
{
string filePath = EditorUtility.OpenFilePanel ("Select localization data file", Application.dataPath, "csv");
if (!string.IsNullOrEmpty (filePath))
{
string dataAsJson = File.ReadAllText (filePath, Encoding.UTF8);
string[] stringBigList = dataAsJson.Split('\n');
localizationData = new LocalizationData();
localizationData.items = new LocalizationItem[stringBigList.Length];
for (var i = 1; i < stringBigList.Length; i++)
{
string[] stringList = stringBigList[i].Split(',');
for (var j = 0; j < stringList.Length; j++)
{
localizationData.items[i - 1] = new LocalizationItem(stringList[1], stringList[2]);
}
}
}
}
csv파일을 불러오는 함수다.
윈도 창에서 Application.dataPath 경로에 해당하는 부분으로 열어준 다음에 csv 파일 확장자를 불러온다.
만약에 파일이 공백이 아니라면 UTF8로 불러와서 \n 개행 문자열로 스플릿 해서 string 배열로 가져온다.
하지만 문제점이 있는데 이상하게 csv 파일에는 항상 마지막 부분에 개행문자가 들어가도록 되어있는지 추가적으로 공백 데이터가 인식돼버린다. 조금 거슬려서 처리해보려고 노력했으나 쉽지 않아서 나중에 예외처리가 가능하니까 내버려두었다.
읽어낸 데이터를 다시 쉼표(,)로 스플릿 하여 키와 밸류로 분리한 뒤 Serializable 클래스 LocalizationData의 Items에 LocalizationItem으로 삽입을 한다.
stringList에서 0열은 인덱스라서 사용하지 않는다.
private void LoadGameData()
{
string filePath = EditorUtility.OpenFilePanel ("Select localization data file", Application.dataPath, "txt");
if (!string.IsNullOrEmpty (filePath))
{
string dataAsJson = File.ReadAllText (filePath);
localizationData = JsonUtility.FromJson<LocalizationData> (dataAsJson);
}
}
private void SaveGameData()
{
string filePath = EditorUtility.SaveFilePanel ("Save localization data file", Application.dataPath, "", "txt");
if (!string.IsNullOrEmpty(filePath))
{
string dataAsJson = JsonUtility.ToJson(localizationData);
File.WriteAllText (filePath, dataAsJson);
}
}
SaveGameData는 csv파일로 데이터를 불러오면 해당 데이터를 json 형태로 txt 확장자로 저장을 한다. (json 파일을 읽어 올 수 있다면 json으로 저장해도 되지만 Resources.Load<TextAsset>("Texts/"+fileName);로 텍스트 데이터를 불러올 때 json으로 잘 안 불러지길래 txt로 저장하였다.)
LoadGameData는 저장된 json 형태의 txt 파일을 조회한다.
LocalizationData
텍스트 데이터를 들고 다니기 쉽도록 포장처리하는 데이터 형태라고 보면 된다.
[System.Serializable]
public class LocalizationData
{
public LocalizationItem[] items;
}
[System.Serializable]
public class LocalizationItem
{
public LocalizationItem(string key, string value)
{
this.key = key;
this.value = value;
}
public string key;
public string value;
}
너무 간단하다.
json으로 데이터 읽고 쓰기 가능하도록 Serializable 선언
하나의 LocalizationItem의 배열 형태 items에 현재 언어에 해당하는 LocalizationItem 데이터를 전부 다 넣고 나중에 key로 조회를 한다.
LocalizationManager
로컬라이제이션을 총괄하는 스크립트다.
게임의 곳곳에서 참조를 해야 하기 때문에 싱글턴 패턴으로 선언한다. 싱글턴 패턴 설명에 대해서는 생략한다.
missingTextString는 키값이 없어서 불러오지 못한 텍스트에 대한 기본값이다.
fileName은 불러와야 하는 파일명으로 eng.txt면 eng를 넣으면 된다. 자세한 설명은 나중에 하겠다.
public void LoadLocalizedText(string fileName)
{
localizedText = new Dictionary<string, string> ();
TextAsset mytxtData= Resources.Load<TextAsset>("Texts/"+fileName);
//파일 정상적으로 읽어오는지 확인
// Debug.Log(Resources.Load<TextAsset>("Texts/"+fileName));
// Debug.Log(fileName);
// Debug.Log(mytxtData);
string txt=mytxtData.text;
if (txt!="" && txt!= null) {
string dataAsJson = txt;
LocalizationData loadedData = JsonUtility.FromJson<LocalizationData> (dataAsJson);
for (int i = 0; i < loadedData.items.Length; i++)
{
//불러오는 데이터 확인
//Debug.Log(loadedData.items[i].key + ":" + loadedData.items[i].value);
//공백데이터가 여러개 들어가면 오류발생
if (!localizedText.ContainsKey(loadedData.items[i].key))
localizedText.Add(loadedData.items[i].key, loadedData.items[i].value);
}
Debug.Log ("Data loaded, dictionary contains: " + localizedText.Count + " entries");
} else
{
Debug.LogError ("Cannot find file!");
}
isReady = true;
}
Debug.Log는 참고용으로 주석 처리해놨는데 필요시 주석 해제하면 무엇이 문제인지 확인이 쉽다.
해당 함수는 fileName에 해당하는 즉 언어에 해당하는 txt 파일을 불러와서 딕셔너리 형태의 localizedText에 언제든 꺼내 쓸 수 있도록 전부 다 넣어준다.
여기서 아까 csv파일의 특성으로 인해 생기는 공백 데이터가 문제를 일으키는데 지금 넣어놓은
if (!localizedText.ContainsKey(loadedData.items[i].key)) 이 부분으로 예외처리를 해서 문제는 발생하지 않을 것이다.
만약 저렇게 예외처리를 하지 않는다면 공백 데이터가 2개 들어가 버려서 txt 저장할 때는 문제없는데 게임 실행하면 저렇게 오류가 발생한다.
public string GetLocalizedValue(string key)
{
string result = missingTextString;
if (localizedText.ContainsKey (key))
{
result = localizedText [key].Replace("\\n","\n");
}
return result;
}
나중에 텍스트 컴포넌트에 부착된 LocalizedText에서 key로 value값을 조회할 때 사용된다. \n 치환은 개인적으로 저 부분이 자꾸 문제를 일으켜서 넣어놨는데 없애도 괜찮다. (왜있는거지 ?? 나란 괴물...)
public void ReloadTexts(string fileName)
{
LoadLocalizedText(fileName);
foreach (LocalizedText text in FindObjectsOfType<LocalizedText>())
{
text.ReloadText();
}
}
나중에 버튼과 연동시켜서 언어를 바꿀 때 쓰는 기능이다.
유의할 점은 FindObjects 함수는 게임 오브젝트가 enabled 된 컴포넌트만 가져오기 때문에 꺼져있는 텍스트는 바뀌지 않는다.
저렇게 Reload를 한다고 전부 다 바뀌는 것이 아님을 알아둬야 한다.
컴포넌트에 OnEnable을 사용하여 별도로 새로고침 하는 과정이 필요하다.
LocalizedText
Text 및 TextMeshPro에 붙어서 로컬라이제이션 값을 가져와 교체해주는 컴포넌트
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class LocalizedText : MonoBehaviour {
public string key;
private Text Text;
private TextMeshProUGUI TextPro;
// Use this for initialization
void Start ()
{
}
private void OnEnable()
{
Text = GetComponent<Text> ();
TextPro = GetComponent<TextMeshProUGUI> ();
ReloadText();
}
public void ReloadText()
{
if (Text != null)
{
Text.text = LocalizationManager.Instance.GetLocalizedValue (key);
}
if (TextPro != null)
{
TextPro.text = LocalizationManager.Instance.GetLocalizedValue (key);
}
}
}
아까 말한 대로 OnEnable을 통하여 활성화되었을 때 새로고침을 하도록 한다.
ReloadText 함수는 텍스트 컴포넌트가 존재하면 로컬라이제이션 매니저에다가 값을 질의하여 텍스트를 교체해준다.
여기까지 핵심 스크립트에 대한 설명은 끝났다. 자세한 사용 방법을 알아보도록 하자.
4. 샘플 프로젝트 및 참고 링크
먼저 영상을 보고 무슨 샘플 프로젝트인지 보자. 음식 이름에 대해서 고찰해보는 프로젝트다.
먼저 csv 파일을 2개 준비한다.
각각 영어 데이터와 한글 데이터를 넣어둔다.
key값은 동일해야 한다.
Window > Localized Text Editor > LoadCSVFile
아까 만들었던 csv 파일을 불러온다.
한글 데이터부터 먼저 가져온다.
정상적으로 가져온 것 같지만 2가지 문제가 있다.
한글 데이터 인코딩 문제와 4, 5번 엘리먼트에는 공백이 담겨있다.
공백 데이터에 대해서는 우리는 예외처리를 했으니까 별 상관은 없지만 엘리먼트 숫자를 줄여서 없애도 되고 마음대로 하자
인코딩 문제는 다음과 같이 불러온 데이터가 깨져서 나온다.
웹 쪽 일을 조금 해봤다면 이럴 때 어떻게 해야 하는지 알 것이다.
그렇다 있는 척 좀 해봤다.
C# 내에서 인코딩 건드려서 불러와도 되고 나는 메모장에서 인코딩을 utf-8로 바꿨다.
요렇게 해서 덮어쓰기로 저장한다.
그럼 이제 잘 나온다.
그러면 Save data를 눌러서 위의 경로에 맞추어 저장하도록 하자
파일명은 kor eng로 하겠다.
영어도 똑같이 한다.
그리고 텍스트 컴포넌트가 붙어있는 텍스트 오브젝트에 Localized Text의 Key에 교체되길 원하는 key값을 적어 넣는다. 우리의 경우 food1이나 food2다.
Localization Manager 오브젝트도 넣고 인스펙터의 File Name에는 최초로 활성화하고 싶은 언어 파일명을 넣는다.
한글 버튼에다가 다음과 같이 온 클릭 이벤트를 걸어놓는다.
영어 버튼에다가는 eng를 넣으면 되겠다.
요런 식으로 사용하면 된다.
이하 참고 링크와 샘플 프로젝트를 올리며 마무리하겠다. (ㅃㅇ~)
사실 해당 소스는 유니티에서 친절하게 제공하는 튜토리얼을 보고 따라 만든 것이다.
아래 링크를 보면 자세하게 설명이 나와있다.
https://unity3d.com/kr/learn/tutorials/topics/scripting/localization-manager
김치와 불고기의 로컬라이제이션에 대하여 고찰하는 샘플 프로젝트다.
'Unity > 강좌' 카테고리의 다른 글
유니티 ragdoll 발사하기 - 1.목차 (0) | 2019.08.16 |
---|---|
유니티 게임 개발 따라하기 JJTAN - 2. 공 발사하는 캐논 설정 (0) | 2019.06.22 |
유니티 게임 개발 따라하기 JJTAN - 1.프로토타입 수준의 오브젝트 및 환경 설정 (0) | 2019.06.20 |
유니티 음성 인식 음량 측정 (4) | 2019.06.09 |
유니티 게임 개발 따라하기 JJTAN (0) | 2019.05.24 |