Unity UGUI汉化方案:Unity UI Translation (UITL & UITI)

Unity UI Translation Loader(简称UITL)是由HongFire和Anime-Sharing论坛的akyryz为了英化黄油(不要问我英化黄油是什么黄油,好吃吗)而制作的一个Unity游戏本地化方案,由一个类库UnityEngine.UI.Tranlation.dll和修改过的Unity引擎库UnityEngine.dll、UnityEngine.UI.dll组成。其核心思想是让Unity在执行到引擎库里的某些方法(比如一个Text或者Texture加载时)时调用到UnityEngine.UI.Tranlation,而这个库会在本地查找这些对象有没有翻译版本的对应素材,如果有则替换成翻译版本,再返回到引擎库的正常执行过程(这个过程类似于Hook);或者(翻译模式)将这些文本、图片(名称)导出到文件等待翻译。他已经将这一方案用在SBPR以及最近很火的HS上。

此方案的优点:对翻译者非常方便。只需运行游戏就能获得文本,文本的格式统一,用文本编辑器即可修改,修改完再运行游戏即可查看效果。此外还能支持图片和声音的替换,甚至对声音添加字幕。一个没有本地化考虑的游戏仿佛就此具有了本地化功能。

此方案的缺点:

  • 除了文本之外不能直接导出其他素材(如图片),只能通过名字手动导出对应的素材并修改(但可以直接加载修改后的素材而不需手动替换回游戏文件)。今后对此应当进行改进。
  • 获取到的文本是显示文本,而不是程序文本。有些游戏采用打字机式的对话输出,就会导致文本大量冗余。此外对于程序诸如“你的名字是:{0}”这样的参数字符串可能难以处理。(不过另一方面来说,此方案特别适合处理保存在资源中的或是加密过的文本。)
  • 需要修改Unity的引擎DLL。
  • UITL实现时为了读取INI文件,使用了Windows API。这是不科学的,必须得改。
  • 喂喂,你怎么能在UnityEngine.dll里引用UnityEngine.UI.Tranlation.dll,又在UnityEngine.UI.Tranlation.dll里引用UnityEngine.dll,这样的代码不是辣鸡吗?(笑)——按照软件工程的标准来看确实不应如此,但此处只有这样才能实现。

文本问题:比如游戏中有这样一个逐字显示的文本

う、うぅ……、ここは? = 唔、呜呜……这里是?

 在UITL中就会Dump出以下文本:

う =
う、 =
う、う =
う、うぅ =
う、うぅ… =
う、うぅ…… =
う、うぅ……、 =
う、うぅ……、こ =
う、うぅ……、ここ =
う、うぅ……、ここは =
う、うぅ……、ここは? = 

 

此方案需要修改Unity引擎的DLL,包括UnityEngine.dll和UnityEngine.UI.dll。但是akyryz只提供了几个黄油版本的DLL,并没有给出通用的方案。这么有趣的方案只用于黄油,还是挺可惜的。于是我从其实现(UnityEngine.UI.Translation)出发,制作了UITI:Unity UI Translation Injector。项目见github:https://github.com/UlyssesWu/UnityEngine.UI.Translation

UITI的作用是修改Unity游戏的引擎DLL,使其能与UITL协同工作。UITL的一些实现细节让我感到很有意思,比如为了修改UnityEngine.AudioSource(位于UnityEngine.dll)的Play方法(此方法被标记为外部实现,即没有方法体,在运行时由mono绑定到native方法),竟然构造了一个同名的UnityEngine.AudioSource(位于UnityEngine.UI.Translation.dll)作为前者的基类,在其中构造Play方法,以完全相同的名字(UnityEngine.AudioSource.Play)欺骗mono,使mono把这个基类的Play方法绑定到了native方法,而原来的Play方法就可以随意修改了。不过这也引出了编程时的问题:引入的两个不同的DLL中具有【同样的命名空间名】和【同样的类型名】的类型该怎么区分?答案是使用extern alias,详见代码。

效果图:

 

这里其实还有一个问题,看截图可能也发现了:原游戏中使用的日语字体没有简体汉字。为了使其显示简体汉字,我改了游戏的字体。目前UITL还没有这个功能(英化一般不考虑修改字体,汉化却经常需要考虑),这也是接下来可以考虑增加的一个功能。为了修改本游戏的字体,我为另一款黄油工具SB3U写了一个简单的插件使其支持导出和修改字体,这个留待以后讨论。

评论 (12) -

  • RMB
    U酱果然好强啊..
  • 在网上找了很久找到博主的文章有提及汉化字体的事,结果看到结尾说留待以后讨论,想着有希望了,但是看了看这文时间和博主之后再无更新.....
    • 这个……因为之前异常地闲所以有空搞些别的东西,但是最近变得忙起来了,而且以后工作可能会更加的忙了。

      首先说明一点,这里针对的是ttf/otf的字体(而不是贴图字体,那种通常需要同时替换贴图和字体定义文本)。因为SB3U这个工具是可以替换素材的,但不能替换ttf/otf,于是我顺手把这个功能加上了(同时也感受到SB3U、UnityStudio等这些工具的代码架构真是太混乱啦,本想开个坑但是忙起来了)。如果你正是需要这个【SB3U下的ttf替换功能】,可以留个邮箱,我发给你。
  • 感觉对字库的支持不是很好现在的游戏变动太多,艺术字体图集字库越来越多干脆就没有ttf类型的字体了==如果可以类型的图集字库配置文件就好了,
    • 作为程序,我认为多数情况下使用ttf/otf比起位图字体会更好,“游戏变动太多”的情况用ttf反而是更好的——比如更新版本的时候要把某条文本换掉,如果这时候你的字体贴图没有新文本里的某个字,那就尴尬了。字体文件多出来的那点空间占用对游戏影响不大。(艺术字完全可以通过在ttf渲染时加特效解决。)
      当然,本文提及的方案重点是程序内文本的汉化,至于换字体那是属于资源处理方面的问题。(毕竟这个方案的原作者的目的是英化,不存在换字体的考虑。)当然,你完全可以像换ttf一样换掉位图字体定义文件和字体贴图(由于用到的中文字可能会比较多,极有可能要添加贴图),这都是可以做到的。目前我没有参与这样的项目,也就没有去做。
  • 最近在尝试汉化一些用NGUI的Unity游戏,有些文本是在Assembly-CSharp.dll里面的,每次都手动提取和替换,有些在UIRoot里面的,这些不知道怎么提取和替换,好像要用到UnityEditer来获取路径,还有些ADV文本不知道保存在哪里,又没unity3d的本地文件,根本就不知道怎么汉化,所以问一下有没有Unity NGUI汉化方案。

    另外,UnityEngine和UnityEngine.UI怎么插入代码,有PC版插入工具吗,我都是用Reflector手动插入,UnityEngine里的Spirite不能手动插入,Unity UI Translation改的UnityEngine.dll里新加的get_texture()方法和texture-get(自动变成get_texture())同名冲突,没法手动插入。
    • 我没有研究过NGUI,无法为您解答。资源里的文本,请结合常见的Unity资源提取工具去研究吧。至于程序集中的文本,当然可以用工具手动或自动修改代码/IL。我的UniTranslator也实现了这样的功能(原理也是一样的)。
      你所使用的“手动插入”程序并不是Reflector,而是(不仅仅能用在Reflector上的)插件Reflexil。这个插件已经过时了,没有必要再用了:)——这是我(作为dnSpy的主要译者)的主张。
      “没法手动插入”也是错误的,正确的说法是“(可能)没法用Replace Code功能直接替换代码插入”:)因为你可以通过正确地修改IL指令去插入,不同于C#代码可能会在编译时遇到问题,IL指令不会造成歧义。
      当然,你也可以向我一样,通过IL编辑库(如cecil、dnlib)写程序去直接修改,这才是最自由的,毕竟,我的UIT Injector就是这么做的。
    • 另外,请不要在填写昵称的地方填写评论标题啊 = =
  • 用UIT Injector插入错误,如何解决?
    • 嗯,尽管这个工具(事实上是PoC)最初的目标是支持5.x,但是Unity变化确实太多了。至于错误原因已经在提示中指明了,是因为[UnityEngine.WrapperlessIcall]这个内部使用的标签被后续的Unity版本取消了(据观测5.6.x取消了这个Attribute),而这个标签在UnityEngine.AudioClip中被使用了。
      一种可能的解决方案是,你需要用你目标游戏的UnityEngine.dll和UnityEngine.UI.dll替换文件夹内的*.ref.dll。如果仍有错误,需要在UnityEngine.UI.Translation项目中把引用的dll替换成游戏版本,然后重新编译出UnityEngine.UI.Translation.dll。
      当然,这个工具目前肯定是无法用在2017以上的版本的,因为在2017中UnityEngine的所有功能已经被拆分到其他dll中了,所以需要进行较多修改才行。由于受众较小以及其他个人原因,我目前没有维护这个项目的计划。
      • 用目标游戏的UnityEngine.dll和UnityEngine.UI.dll替换文件夹内的*.ref.dll的确不会出错,但是好像没有插入导入UnityEngine.UI.Translation.dll插件的IL代码,用UIT Injector注入前的dll和注入后的dll反编译出来的代码几乎是一样的,对比了一下只是将某个string=“”替换成string.empty而已。

        原来的*.ref.dll已经有导入UnityEngine.UI.Translation.dll插件的IL代码,拿目标游戏的UnityEngine.dll和UnityEngine.UI.dll去替换*.ref.dll,替换过后的*.ref.dll就不含导入UnityEngine.UI.Translation.dll插件的IL代码了,所以用UIT Injector注入就什么都没注入是这样的吗?
        • 额,抱歉,确实是这样的(时间比较久了我有点忘了)。
          那么我想其中一个workaround是手动把ref里的相关类里的那个标签去掉吧。(其实在UIT Injector里做改动会更容易,只要指定忽略这个标签就可以了。但是目前我暂时没有时间,抱歉。估计只有到过年才会有时间了。)

添加评论

Loading