查看: 2446|回复: 3
打印 上一主题 下一主题

读取xml数据,Reading Strings out of an XML file using C# in Unity 3D

[复制链接]

2508

主题

2

听众

3万

积分

资深设计师

Rank: 7Rank: 7Rank: 7

纳金币
32806
精华
12

最佳新人 活跃会员 热心会员 灌水之王 突出贡献

跳转到指定楼层
楼主
发表于 2012-8-3 17:44:46 |只看该作者 |倒序浏览
What is it supposed to do?
Basically, I could simply hard-code most of my strings used in the game directly into the code – no one would notice the difference anyway. But obviously, this is not a very good idea, both because editing strings and later translating them becomes a pain.
Creating some data that would allow me to get strings out of an XML file would solve this problem – and, if the code is good enough, be reusable in later games.
It would allow me to edit text independently of the game code and add translations on a later date.
Preliminary format of the XML
The XML file has the following (as of now experimental) format:
<?xml version="1.0" encoding="UTF-8" ?> <localizableStrings> <meta> <gameName>***st in Me</gameName> <version>1.0</version> <author>Kaspar Manz</author> <language>multilingual</language> <grouping>multi</grouping> </meta> <group id="1"> <string id="1"> <text lang="de">Das ist der Text auf Deutsch.</text> <text lang="en">This is the same string in English.</text> <audio lang="de">audio/de/1/1.ogg</audio> <audio lang="en">audio/en/1/1.ogg</audio> </string> <string id="2"> <text lang="de">Willkommen zu ***st in Me.</text> <text lang="en">Welcome to ***st in Me.</text> </string> </group> </localizableStrings>
In order to allow for different distribution formats, parts of this file can change, as reflected within the <meta> tag.
The file above could contain all strings in all languages for all levels and screens. The format allows for splitting these files up, i.e. into single level files (one file for each level) or single language files (one file for each available language) or a combination thereof.
A a mono-grouped, monolingual file would then look like this:
<?xml version="1.0" encoding="UTF-8" ?> <localizableStrings> <meta> <gameName>***st in Me</gameName> <version>1.0</version> <author>Kaspar Manz</author> <language>en</language> <grouping>menu</grouping> </meta> <group> <string id="button-load"> <text>Load Game</text> </string> <string id="button-save"> <text>Save Game</text> </string> <string id="button-delete"> <text>Delete Game</text> </string> </group> </localizableStrings>
Comparing both examples, you can see certain features:

    The XML format does no refer to "levels", but to "groups", in order to take into account that also menu and option screens can have their own groups.
    Whenever either language or grouping have been defined within the meta tag, the attributes aren't necessary anymore later on.
    All ids are strings, in order to allow for more understandable coding and to reduce human error while matching the ID and the actual string in the code.
    The format should also allow for audio files to be matched with the strings. This function is not yet implemented, though.

The format has yet to be formalised in a RelaxNG schema or a DTD.
How to get the strings out of the file
Again, first I thought I could do stuff with a XMLReader – before I realised that I didn't want to program a glorified typewriter. An anonymous commenter pointed me to LINQ, which sounded great, but isn't available in Unity, since it uses an older version of the framework.
What's available is XmlDocument, though – and how comfortable it is. Using simple XPath queries, the document can be easily traversed and the necessary strings can be pulled from it.
The following code prepares the necessary variables:
using System; using UnityEngine; using System.IO; using System.Xml;     public class XMLStringReader : MonoBehaviour { public string localizedStringsFile; string language; string grouping; XmlDocument root;   void Start () { // Get the file into the document. root = new XmlDocument (); root.Load (localizedStringsFile);   language = root.SelectSingleNode ("localizableStrings/meta/language").InnerText; grouping = root.SelectSingleNode ("localizableStrings/meta/grouping").InnerText;   }
The following code is used to retrieve the strings.
/**

* <summary>Retrieves the string by its ID. Only works when the referenced file

* is both monolingual and monogrouped. Will throw a NullReference Exception

* when the file doesn't meet the requirements.</summary>

* <param name="id">The ID of the requested string.</param>

* <returns>A string with the required text.</returns>

*/ public string GetText (string id) { try { if (language == "multilingual" || grouping == "multi") { throw new NullReferenceException ("The referenced file is" + "multilangual and/or multigrouped."); } else { string n = root.SelectSingleNode ("localizableStrings" + "/group/string[@id='" + id + "']/text").InnerText; return n; }   } catch (NullReferenceException ex) { string s = "Missing string (" + ex.ToString () + ")"; return s; } }   /**

* <summary>Retrieves the string by its ID and its level. Assumes the second

* parameter to be the level string, but uses it as a language string if it

* fails the first time.</summary>

* <param name="id">The ID of the requested string.</param>

* <param name="level">The name of the group in which the string is placed.</param>

* <returns>A string with the required text.</returns>

*/ public string GetText (string id, string level) { try { /*TODO Write a routine that silently ignores the level string

* when it's the same of the grouping.

*/ string n = root.SelectSingleNode ("localizableStrings" + "/group[@id='" + level + "']" + "/string[@id='" + id + "']" + "/text").InnerText; return n;   } catch (NullReferenceException exa) { // Apparently, there was nothing there ... well, so // we simply try and see whether we can use the last string as // a language code.   try { string n = root.SelectSingleNode ("localizableStrings" + "/group/string[@id='" + id + "']" + "/text[@lang='" + level + "']").InnerText; return n; } catch (NullReferenceException exb) { string s = "Missing string (" + exb.ToString () + ")"; return s; } } }   /**

* <summary>Retrieves the string by its ID, its level and its language.</summary>

* <param name="id">The ID of the requested string.</param>

* <param name="level">The name of the group in which the string is placed.</param>

* <param name="lang">The language the string is required in.</param>

* <returns>A string with the required text.</returns>

*/ public string GetText (string id, string level, string lang) { try { string xPath = "localizableStrings"; xPath += "/group[@id='" + level + "']"; xPath += "/string[@id='" + id + "']"; xPath += "/text[@lang='" + lang + "']"; string n = root.SelectSingleNode (xPath).InnerText; return n; } catch (NullReferenceException ex) { string s = "[This string has either not been implemented or needs " + "to be translated.]"; return s; } }
The code may not be really beautiful right now and could be written more clearly and compact. It was more an***rcise in overloading methods and using try/catch s***ctures ...
The method overloading allows for coding, since only the necessary parameters have to be given (at least, that's the idea).
Ideas that got trashed along the way
For a brief moment I considered changing that all to a ***pal-style translation system, in which the actual strings in the original language would be string IDs, in order to allow coding along the lines of Button(rect, t(Load Game)); where the ID string could be used as a fallback when the language wasn't available.
I decided against it, because of the following reasons:

    Text is, again, distributed over actual game code and an XML file: whenever the wording of the original has to be changed, it has to be changed in the code. This defeats the original purpose of the code.
    Whenever the original wording is changed in the game code, the translations break (this is also a problem in ***pal, even though strings are handled in a database).
    Unsanitised input – depending on the text, it could wreak havoc both in the code as well as the XML.

What still has to be done

    The strings aren't sanitised – bad things could happen if someone tried to sneak in some strings on his own, I guess.
    The method using two parameters has to be rewritten – it seems to be too clumsy right now.
分享到: QQ好友和群QQ好友和群 腾讯微博腾讯微博 腾讯朋友腾讯朋友 微信微信
转播转播0 分享淘帖0 收藏收藏0 支持支持0 反对反对0
回复

使用道具 举报

2508

主题

2

听众

3万

积分

资深设计师

Rank: 7Rank: 7Rank: 7

纳金币
32806
精华
12

最佳新人 活跃会员 热心会员 灌水之王 突出贡献

沙发
发表于 2012-8-3 17:54:31 |只看该作者
我爱纳金网~www.narkii.com
回复

使用道具 举报

.    

3797

主题

11

听众

5万

积分

首席设计师

Rank: 8Rank: 8

纳金币
32328
精华
41

活跃会员 优秀版主 荣誉管理 论坛元老

板凳
发表于 2012-11-8 21:13:34 |只看该作者
这是一个好帖子,大家快来围观!!
回复

使用道具 举报

0

主题

1

听众

-18

积分

限制会员

纳金币
-15
精华
0
地板
发表于 2013-4-16 17:33:53 |只看该作者
顶!!!!!!!!!!!!!!!!!!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

手机版|纳金网 ( 闽ICP备08008928号

GMT+8, 2024-6-16 16:59 , Processed in 0.135209 second(s), 27 queries .

Powered by Discuz!-创意设计 X2.5

© 2008-2019 Narkii Inc.

回顶部