有關遊戲本地化實現的一些分享
引言
[本文主要面向國內遊戲開發者和對本地化工作有興趣的玩家,並非漢化遊戲指南]本文產自遊戲古登堡計劃,首發於這裡,我們是致力於弭除遊戲中語言障礙的志願者組織,目前在進行遊戲漢化、相關領域文檔資料翻譯,視頻字幕等各種遊戲本地化相關工作,歡迎有興趣的同學加入我們。
遊戲本地化
所謂遊戲本地化,是指為適應目標國家的玩家群體而對遊戲進行調整修改的過程。狹義上講,主要指遊戲內文本圖片等資源的翻譯;廣義上講,也包括為了目標國家的遊戲市場環境和法律法規而做出的內容與功能調整。
對國內玩家來說,將遊戲本地化簡單理解成漢化也沒有什麼問題。由於眾所周知的原因,國內的主機/單機遊戲市場剛剛才從黑暗低迷的時期走出,許多方面百廢待興,許多遊戲並沒有完善的官方中文支持。很多人可能不太理解遊戲開發商為了支持完善的本地化支持,究竟需要哪些工作,這也是筆者寫作本文的主要目的。遊戲古登堡的主要使命之一,也正是幫助更多的優秀遊戲加入官方中文支持,讓國內的玩家能夠更好地享受遊戲內容。
對本地化相關功能提供相對完善的官方支持應當做到下面幾點:
- 實現遊戲內容層和邏輯層的分離,存放為單獨的本地化文件不要將涉及到遊戲內容的代碼與遊戲邏輯相關的代碼混淆在一起。並且將需要本地化為多國語言的遊戲文本和遊戲素材單獨保存為易於編輯修改的專有格式。專有格式一般會是一個保存著 key-value 形式數據的文件,key 為文本索引,value 為翻譯後的目標語言文本,遊戲會從指定路徑讀取這個專有格式的文件,並通過檢索 key 來確定某個特定位置顯示什麼樣的文本。很多遊戲直接使用 XML 格式;有的則簡單使用每行一句 key = value 語句的形式。這個專門文件應當保存在指定路徑下,並以特殊規則命名,類似地,對圖片,語音文件等的本地化也應以相同方式處理。專門的本地化文件之於遊戲有點像網頁編程中控制格式的 css 文件之於 html 文件,好處在於,不必將遊戲邏輯暴露給負責文本翻譯的人(他們很可能毫無技術背景),這樣可以大大降低後期維護的難度:只需要文本編輯器就能修改遊戲的語言,交給第三方的翻譯公司或社區進行本地化工作變成相當簡單的工作。
- 充分考慮語言差異造成的本地化問題開發商需要充分考慮語言差異造成的本地化問題。遊戲中的各自 UI 元素,諸如下拉列表、徑向菜單等在展開或收縮都會用到必要的空間,為了取得最佳的本地化效果,需要協同遊戲文本的翻譯者,來根據顯示效果進行適配,這樣才能徹底免除圖像覆蓋等形式的顯示差錯。如果以英文為標準長度,多數亞洲文字會比英文稍短,而歐洲國家的語言一般會比英文更長:阿拉伯語通常會比英語長上25%,法語和德語通常會比英語長 20% 左右,義大利語會長 15%,德語長 20%,西班牙語長 25%。中日文字一般會更短,但由於語言體系存在根本差異,也會有一些其他問題,比如,字母文字擁有大量可用的像素字體,而漢字在 8px 以下就很難找到合適字體。還有很多細微問題:阿拉伯文字從右往左顯示的問題;換行處理問題(有些遊戲會使用空格來判斷是否應當自動換行,而中文並不使用空格來分詞)等等。對 cjk 字元,由於巨大的字元使用量,還需要單獨製作專門的字型檔。
- 根據文化差異和政策規定進行內容調整筆者並不贊成為了迎合受眾市場而對遊戲內容進行大幅度修改,但這種做法其實並不鮮見。例如,大家所熟悉的魂斗羅中主角形象是以第一滴血主角來設計的,但在歐版中,卻變成了令人啼笑皆非的機甲形象。隨著遊戲文化價值方面屬性的上升,大家會更傾向於享受原汁原味的遊戲內容,但為了適應不同國家的文化和政策,仍然會有許多需要注意的地方:例如,對一些血腥暴力元素的修改,對一些具有不同含義的圖標進行修改(比如,在大部分國家和地區,「大拇指」意味著讚許。但在有些地方,這卻是種挑釁的手勢。)等等。
很多遊戲並非以常規渠道同玩家見面,其中文版也多由民間漢化組或盜商提供,其製作流程是有別於官方的本地化流程的。民間漢化組需要以非常規手段解包和重新打包遊戲,需要翻譯和修改的文本需要自己提取和重新封包,字型檔問題也要獨立解決。因此,對民間漢化組來說,是否能夠進行漢化,一方面取決於遊戲本身的框架對中文的支持程度,另外一方面也取決於漢化組本身的技術實力。大家可以閱讀下這篇文章感受民間漢化的艱難與不易。
官方中文支持所面臨的困難
對開發商來說,遊戲本地化有助於開拓非母語市場,商業上的意義自不必贅言。以國內情況為例,只消去觀察遊戲商城和各大遊戲社區的用戶評論區看看有多少「求漢化」發發言就能多少感受到需求之旺盛。
素質平平的仿dqb遊戲《傳送門騎士》在更新中推出漢化後在國內大賣但推進官方中文支持的推進其實也還面臨著相當多的困難:
CJK(中日韓文)字元的獨特性
純粹從技術角度來講,本地化系統非常容易實現。但是,如果不是在開發階段就考慮到相關的問題,尤其對包含大量文本的遊戲來說,後期才進行本地化工作會變成一個相當繁瑣的工作。幸運的是,隨著全球化趨勢,本地化工作已經日益成為一個常規考慮事項,但不幸的是,由於中文與西方字母文字的天然差異,中文支持會需要考慮一些無中文背景的西方開發者可能難以考慮到的額外的情況:諸如擴大遊戲字型檔,不同的分行處理演算法,預留更多的顯示範圍等等。日韓文字其實也面臨相似的問題,但中文問題尤其嚴峻:以像素字體為例,日文有大量的可用免費字體供開發者選擇,但中文幾乎找不到特別合適的全字型大小解決方案(一般是不消除鋸齒使用小字型大小宋體)。
開發商對國內市場的信任與評估問題過去這些年,海外遊戲作品缺少正常途徑引入國內,即便目前環境逐步好轉,但進入中國市場的困難和複雜程度已經給他們留下了非常深刻的印象。中國市場的確潛力巨大,但其活躍的正版玩家用戶數量仍然與其巨大的人口總量十分不匹配,儘管市場在逐漸回暖,但當下而言,國內的盜版玩家數量還是遠遠多於正版玩家用戶。根據 steamspy 的統計,中國的 steam 用戶已經逼近千萬,但平均擁有遊戲數不到10款;作為對比,美國玩家平均擁有遊戲數為40款左右。正式進入國內市場需要經過許多內容上的審核,並做出相應調整,因此也需要找到熟悉中國市場的國內合作夥伴,對開發商而言,本地化絕非僅僅翻譯遊戲內容那樣簡單,翻譯遊戲內容只是其中最主要的必不可少的一個環節,其他事項過於複雜也會動搖他們執行本地化的興趣和信心。
高昂的翻譯費用成本取決於文本的內容規模,也取決於文本質量。
根據國外遊戲翻譯公司 lingo24 的一篇文章,他們的報價大抵為:
- 英文到法文,義大利文,德文和西班牙文:每千詞 $160-$275(有賴於文本類型和對翻譯者的專業性要求)
- 英文到丹麥文,荷蘭文,芬蘭文,冰島文,日文,韓文,挪威文,瑞士文:每千詞 $190-$325(有賴於文本類型和對翻譯者的專業性要求)
505 發行遊戲目前一般都支持官方中文,質量中等偏上(例如前段時間的 Abzu 就是通過這個渠道製作的官方中文,翻譯質量尚可,但存在少量缺漏),主要通過 localizedirect 翻譯,也可以參考他們首頁的報價單,如英文翻譯為中文,每千字報價為 120 歐元,約合 134 美元。
一些涉及大量文字的遊戲在翻譯方面會支持一筆相當高的開支,例如,以劇情見長的永恆之柱如果使用商業翻譯公司來製作中文版本,假使按 50 萬單詞計算,僅僅製作中文翻譯文本就需要花費超過 5 萬美元。
(根據筆者最近一段時間的了解,中翻英在400~600人民幣/千字的價格區間內能找到質量基本可靠的。如果對文筆有進一步的要求價格會更高。當然,非劇情類,使用辭彙比較固定的遊戲找千字百元價位的翻譯應該也不會有太大問題,雖說只是差強人意而已。)
如果擁有大量的玩家受眾,也考慮承受社區翻譯良莠不齊的風險,也可以通過向玩家社區開發本地化文件介面的形式來獲取翻譯方面的支持,但多數時候,情況會更加複雜。
民間漢化的轉正也困難重重很多來自民間漢化組和盜商的中文版本,質量上存在良莠不齊的問題,加之缺少與官方的溝通渠道,難以被採用為正式的中文版,同廣大玩家見面。在進行遊戲古登堡計劃中,我們有機會同很多開發者交流。當提及國內有大量的民間漢化組在製作他們作品的中文版時,他們多數人樂見這樣的情況,對中國有如此多的愛好者能肯定他們的遊戲作品表示開心,但正式的官中支持會是個更複雜的問題,需要更多的交流溝通,需要對翻譯質量更審慎的評估和認證,這些都是民間漢化組可能難以做到的。另外,正如前文提到的,需要開發方做一些額外的並非零成本的準備工作(字體授權的申請/購買,字型檔製作,市場調研與政策問題解決等等),很多工作會和漢化組的傳統操作流程相悖。盜商抱著以漢化來吸引人氣的動機,更難積極去推進官方中文支持的進程。
接下來的部分主要面向遊戲開發者,我將簡單介紹如何在 unity 和 gms 這兩款引擎中實現本地化系統。
Unity 的本地化系統
本教程主要面向對 unity 和 C# 有一定使用經驗的用戶。
正如前文所述,我們首先要指定一種用來保存 key-value 形式數據的本地化文件格式。既可以使用簡單的 .ini 格式,也可以使用 json 或者 xml(自定義格式也可以,但你可能需要花更多時間來專門編寫解析它的函數)。不同格式都有各自的優勢劣勢,我們主要從三個方面考慮:是否便於人類編輯修改;是否方便程序來讀取解析;功能上是否有拓展空間,是否有足夠的彈性適應千奇百怪的需求。.ini 雖然易於閱讀修改,但缺乏彈性;json 計算機解析起來很容易,但不利於人類閱讀。本教程中使用 .xml 格式。它解析起來很方便,而且雖然初看上去不是那麼便於閱讀,但卻很容易轉換成其他更容易手動編輯的格式。此外,我們這裡使用的引擎是 unity,而 C# 對 xml 的支持非常良好,解析起來很容易。
XML format
我們來定義下用於本地化的 xml 文件的結構。我會盡量讓例子保持簡明。將下面的文件命名為ENGLISH.xml 並保存到遊戲項目assets路徑下的某個指定位置:
<!--?xml version="1.0" encoding="utf-8"?--><language lang="english" id="0"> <!-- Main Menu --> <text key="MAIN_TITLE">My Game Title</text> <text key="CONTINUE">Continue</text> <text key="START_NEW_GAME">Start New Game</text> <text key="OPTIONS">Options</text> <text key="QUIT">Quit</text></language>
目前的文件結構看起來非常簡單。解析和手動編輯都不難。xml 的根節點定義了語言的種類和 id 屬性,會用於在遊戲中設置語言類型。不同的 id 對應不同的語言文件。而擁有 key 屬性的 text 節點則用於保存遊戲中實際可見的文本。這個文件會在 Asset 路徑下保存為 ENGLISH.xml,而如果你製作了一個用於中文的文件,也可以將其保存為 CHINESE.xml。
接下來我們來實現在遊戲中利用 key-value 對來讀取語言文件。
編寫本地化的類
下面提供了一個簡單的類,用於讀取保存在 xml 中的文本。你只需要將其作為腳本綁定到到場景中一個不可見的 GameObject 上就可以了。將下面的代碼保存為 LocalizationManager.cs:
using UnityEngine;using System.Collections;using System.Collections.Generic;using System.Xml.Linq; public class LocalizationManager : MonoBehaviour{ public static LocalizationManager Instance { get { return instance; } } public int currentLanguageID = 0; [SerializeField] public List languageFiles = new List(); public List languages = new List(); private static LocalizationManager instance; // GameSystem local instance void Awake() { instance = this; DontDestroyOnLoad(this); // This will read each XML file from the languageFiles list<> and populate the languages list with the data foreach (TextAsset languageFile in languageFiles) { XDocument languageXMLData = XDocument.Parse(languageFile.text); Language language = new Language(); language.languageID = System.Int32.Parse(languageXMLData.Element("Language").Attribute("ID").Value); language.languageString = languageXMLData.Element("Language").Attribute("LANG").Value; foreach (XElement textx in languageXMLData.Element("Language").Elements()) { TextKeyValue textKeyValue = new TextKeyValue(); textKeyValue.key = textx.Attribute("key").Value; textKeyValue.value = textx.Value; language.textKeyValueList.Add(textKeyValue); } languages.Add(language); } } // GetText will go through each language in the languages list and return a string matching the key provided public string GetText(string key) { foreach(Language language in languages) { if (language.languageID == currentLanguageID) { foreach(TextKeyValue textKeyValue in language.textKeyValueList) { if (textKeyValue.key == key) { return textKeyValue.value; } } } } return "Undefined"; }}// Simple Class to hold the language metadata[System.Serializable]public class Language{ public string languageString; public int languageID; public List textKeyValueList = new List();}// Simple class to hold the key/value pair data[System.Serializable]public class TextKeyValue{ public string key; public string value;}
腳本的一些細節解釋如下:
這個類使用了 DontDestroyOnLoad(this);,因此不必擔心載入新場景會讓腳本失效。你可以將用於本地化的遊戲對象放在第一個遊戲場景中,或者配合某個場景管理工具來方便地使用。
使用 [SerializeField] 比 languageFile List<> 更方便,因此你可以在場景編輯中自由拖拽 xml 文本。這個腳本使用了 [System.Serializable] 將語言的子類放入 Unity 的監視器中,這樣就能可視化地查看數據,有助於調試修改。
變數 currentLanguageID 用來指定使用哪種語言文件。你可以按照自己的需求來設置這個數值,但它必須和 xml 中你使用的 id 數值相匹配。我將默認值設為0,此時它會使用英文文件,而 English.xml 中的 id 即為 0。你既可以通過偵測系統語言的方法來設置語言類型,也可以在菜單中加入一個選項,允許玩家自己更改語言設置。
本地化 UI 文本的組建
在向遊戲對象 GameObject 添加 UI 文本組建時你也需要一個腳本,保存下面代碼為LocalizationUIText.cs:
using UnityEngine;using UnityEngine.UI; [RequireComponent(typeof (Text))]public class LocalizationUIText : MonoBehaviour{ public string key; void Start() { // Get the string value from localization manager from key & set text component text value to the returned string value GetComponent().text = LocalizationManager.Instance.GetText(key); }}
無論是包含文本組件的按鈕,標籤還是別的什麼,凡是包含文本的遊戲對象,都可以向其添加該腳本。只需要在腳本中調用 LocalizationManager 中的 GetText() 就可以將標記該文本組件,並向其返回本地化之後的文本。
如果某個你添加過腳本的遊戲對象還沒有綁定文本組件,那麼 [RequireComponent(typeof (Text))] 這行代碼則會自動添加一個。
將腳本添加到綁定了 UI 文本組件的遊戲對象上,並設置 key 為你在 xml 文件中使用的值你可以為每個特別的文本組建使用單獨的key,在遊戲中,顯示的內容會自動替換為本地化後的文本。
在其他腳本中也可以使用 LocalizationManager,凡是涉及文本顯示的地方,只需要調用 GetText() 並適應你想要在 xml 中使用的 key 即可。
string localizedString = LocalizationManager.Instance.GetText("START_NEW_GAME");
它會通過 LocalizationManager 讀取當前遊戲文本id currentLanguageID 指定的文本(假設是英文),並返回字元串"Start New Game"。
GMS 遊戲的本地化實現
思路大體類似 unity 中的實現,我們這裡簡單地使用 .ini 格式來作為本地化文件。
下面在遊戲中讀取文本的腳本由來自 reddit 的 DragonCoke 提供:
在需要本地化的地方調用的 text_local() 實現:
///text_local("section","identifier",[massimport])if (argument_count != 3 || argument[2] = 0) { ini_open("localization/"+global.language); }var var_importedtext = ini_read_string(argument[0],argument[1],"-1errormessage");if (var_importedtext = "-1errormessage") //try to reimport from english if missing { ini_close(); ini_open("localization/english.txt") var_importedtext = ini_read_string(argument[0],argument[1],"MISSINGNO#CHECK FILE INTEGRITY"); ini_close(); if (argument_count == 3 && argument[2] == true) {ini_open("localization/"+global.language);} }else if (argument_count != 3) {ini_close();}return var_importedtext;
///text_local_massimport()ini_open("localization/"+global.language);
///text_local_massimport_end()ini_close();
DragonCoke 也提供了將遊戲文本從 .ini 中導出為 .csv 格式的工具,這樣就能很方便地用電子表格進行協同編輯了(雖然並非最佳的解決方案,例如遊戲古登堡計劃就使用了專門的翻譯協作工具)。
工具下載見這裡。
來源與擴展閱讀
- 對民間漢化組的流程與組織參考了這篇文章
- 遊戲本地化的介紹與報價
- Unity 中本地化系統的實現
- GMS 中遊戲本地化系統的實現
推薦閱讀:
※如果 十二兵團由胡璉當司令官 那 么徐蚌會戰 會贏嘛 最起碼是個平手?
※為什麼微軟 Windows 中所有的「賬戶」都寫成「帳戶」,並且現在還不改?
※Windows 7 的簡體中文界面有哪些翻譯 bug?
※OS X Lion 的簡體中文界面有哪些翻譯 bug?