本地化第一需要考虑的元素自然就是语言,转换到游戏内容的话就是文本处理。绝大多数的多语言相关内容都只需要客户端关心,然而为了日后的更新便利,在一定程度上服务端和运营也参与了多语言文本的处理,这主要包括以下几个方面:策划配置表,服务器错误码提示,UI拼接时候的预置标题文本,敏感屏蔽词,以及相关的语言推送等。
1 配置表
配置表作为客户端最常用的数据展示,是需要第一个进行改造的。而大部分时候,这些文本会分散在各个表格之中,比如副本表的关卡标题,关卡描述。道具表的道具标题,道具描述。英雄表的英雄背景介绍,技能表的技能说明等等。太过于分散的语言表无疑会让策划或者运营翻译时变的更加复杂,同时还会产生遗漏,再者如果委托第三方进行翻译的话,还可能导致数据泄露的风险。
所以大部分做多语言的时候,都会将策划表格所使用的文本描述类的文字提取到一个单独的language表格中,用key来区分,同时在其他表格需要用到文本的时候配置成为语言表当众的key值。 比如上一章我们介绍提供游戏内语言支持的表格中,关键行配置的就是key值(红框内的是策划描述,配置为4的时候导表工具不会导出该列,只是策划自己用来做提示用的)。 同样,我们会在language表中配置该key所对应的多国语言。
策划这边表格约定好了之后,接着就是程序处理。其实流程很简单,只是程序在获取显示的时候一定要记得,凡是涉及到文本描述的字段一定要调用共用接口去配置表读取即可。
这里稍微插入讲一下程序方面针对多语言的设计。
一般来说这里有两种设计,一种是以语言为主体,所有的同语言的文本使用一张单独的表,这样每次在加载语言的时候就只需要加载一张表格,减少内存大家的同时也可以增加游戏的启动速度。坏处就是策划在维护表格的时候每个语言表文件都需要去修改,会产生遗漏。
第二种就是我们现在的方式,所有的语言都在一张大表格中,每一个key都配置了多个支持的语言,好处就是策划维护方便,当然坏处就是加载时候会产生额外的内存开销。
之所以我们使用第二种方式,是因为还有一个影响因素,我们的多语言是可以通过服务器下发配置进行增改的,也就是给线上纠错留下一个可操作空间。由于服务器不可能针对客户端做定制化的数据下推,所以服务器推送的数据也是以key为基础,推送所有跟该key相关的多语言文本给客户端进行处理。
那可能会有同学会问,服务器推送下来的数据,客户端肯定是知道自己需要哪种语言的,只保留处理自己目前用到的不就好了吗?
这里又要引入另外一个游戏特性,就是我们的语言是支持动态切换的,即在游戏内通过多语言的界面选择之后,不用退出游戏,立刻就会改变所有的文本显示。也就是说服务器下发的这些增补数据客户端必须都要缓存下来以应对该种情况。同时,服务器下发的增补数据也是策划使用工具导出维护的,所以从各个角度来说,我们都需要一份统一的数据格式方便策划维护,也方便程序处理。
所以客户端这边的语言获取代码就可以这样去表示: 设计语言的存储结构。
语言表的字典结构,其中key就是LanguageItem中的key
获取文本的时候根据当前偏好的语言,返回对应的文本(注:这里的枚举还没有重构为国际化标准):
插曲讲完之后再继续说说策划的表结构,因为语言表也不是同一个策划去维护的,所以在大表中有分为很多个页签。
因为客户端存储是以字段方式进行的,所以页签再多,只要key不重复,都可以随时将它们组合在运行时的存储结构中。
不过这里还有一个需要注意的地方就是base_text这张表。这里涉及的是一个更新问题。因为表格都属于资源也就是都可以在热更或者补丁中进行更新,但一个悖论就是,热更新模块怎么更新它自己呢?
在执行热更新的界面本身也是需要显示文本的,所以这个表的作用就是定义了这些相对固定,启动就需要使用的文本情况。
但也不是说这张表不能更新,只是更新了之后只有下次启动的时候才能看到更新后的文本效果了。
2 服务器下发
客户端更新和服务器更新比较大的区别就是客户端更新需要倒腾玩家。而玩家应该越少折腾越好。从更新流程而言,繁琐程度都差不多,都需要进行一整套的补丁流程,但从产品而言如果能不麻烦玩家或者用户的就尽量不要叨扰。
从设计上说,服务器拥有对客户的更大的控制权也是好事,毕竟如果100万用户你想达到100%的更新覆盖率要很久,但是如果是由服务器发布的,客户端只要连接上来就能体现最新的内容,并且几乎是无感知的。
关于服务器的详细下发技术我们会在下个小节讨论。在日常的操作过程中,我们会在策划的表格目录下放置一个hotfix的目录。策划可以将需要由服务器下发更新的数据表放置在该目录下,然后导表工具会识别该目录内容,将数据导入到服务器指定的hotfix目录下。
服务器启动的时候会读取该目录下的数据缓存,每次客户端登陆成功会通过指定协议下发增改数据,客户端收到增改数据进行缓存处理,客户端能在运行时不重启就修复显示内容。
同时服务器也提供了工具给运营后台,可以手动控制服务器进行Horfix全局下发,也就是说服务器不必是客户端新链接上才可以下发,它同样支持针对所有在线用户,在任何时候进行不停机修复内容。
当然这么好用的功能可不能仅用在语言文本更新上,实际上它是支持任意策划表格的修复。比如我们再版署过审时候就用来更新屏蔽词库。
3 UI预置
文本另外一个常用的地方就是UI界面了。大部分时候在编辑UI的时候都需要将文本直接直接放在在界面上以查看UI效果。或者有一些静态的Label等描述控件都是需要持久显示在界面上的。所以这里我们既要能够让UI编辑人员能够直观的编辑和处理Prefab,又要确保游戏在运行过程当中不要产生遗漏,都能正常替换为指定语言的文本。
前面其实我们有提过,在字体和插件这块我们使用的是TextMeshPro。虽然在实现方式上有差异,但和UGUI的Text组件相比较而言,接口差异并不是特别大。UI层面使用的Component是TextMeshProUGUI,它也是简单的赋值text字段即可。
要实现这个也很简单。多语言的管理和数据都是在游戏初始化的时候进行的,也就是说它会先于所有的UI逻辑完成。等到游戏内开始加载界面的时候,所有的组件和数据都已经可用。所以只需要在Text的相关组件上绑一个自定义的多语言组件,先填好语言文本的Key值,并在Awake的时候进行文本查找和赋值即可。
代码层面也非常简单: 仅此而已。
另外我们前面有提到,游戏是支持运行时动态切换语言的,这也得益于该脚本。当在语言选择界面切换了某个语言之后,会触发一个SwitchLanguage的函数。 而它只是找出当前所有的组件进行一次重新赋值即可。
4 屏蔽词
屏蔽词是国内做游戏,永远逃不开的、必须处理的一个功能点。随着时间推移,词库的词条数量只增不减的情况下已经变得越来越庞大,甚至在我们申请版号的过程中还在不停的增加。我们现在用的字库已经有大约55万条之多。这些屏蔽词不仅仅是占据了大量的内存空间,也为业务开发带来了不同程度的阻碍。比如聊天,比如邮件私信,比如联盟公告、社交留言等等,但凡玩家能够自由输入的地方都需要判定屏蔽词。
关于屏蔽词的实现可以查看我的另外两篇文章:
1、版署送审,一共要踩多少坑?实名、防沉迷和屏蔽词的完善方案
https://zhuanlan.zhihu.com/p/142116058
2、Unity手游实战:从0开始SLG——独立功能扩展(二)使用DFA处理屏蔽字 https://zhuanlan.zhihu.com/p/84685657
除了上述所描述的内存和业务处理之外,还有一个特别为版署审核定制的任务:增量下发屏蔽词库。关于Trie树的相关介绍直接看上面的文章即可。
在游戏内我们的屏蔽词库甚至达到了55多万条内容。这庞大的字库会严重拖慢游戏初始化的时间,并且在处理的过程中产生大量的GC,拉高Mono的峰值。
这里有两个应对措施,第一将屏蔽词的格式进行离线处理,处理为可以直接进行序列化的格式,以减少运行时的解析时长和mono内存。第二由服务器下发的屏蔽词也事先处理为对应格式,服务器下发解析之后扩充trie树,以完成屏蔽词库的扩充。
但不管怎么优化,庞大的基数还是会对游戏启动和内存峰值造成了较大的影响,并且似乎难以避免。下面是开发过程中缩减后的词库数量(防止每次启动耗费大量时间)。
5 推送
最后一个相关的内容是移动平台上的通知推送。为了让运营后台能够针对不同地区或者语言系统进行对应语言的消息推送,需要能够识别出对应玩家所使用的语言标签。
这个相对来说可能和多语言关系不大。我们接入的是TPNS系统。系统的后台有一个特性可以给指定标签的用户推送消息,我们的想法就是给不同语言系统的玩家在初始化的时候做个标签,然后运营针对不同语言系统的标签组使用对应的语言进行推送。
所以这里大概也就是拓展了一下TPNS的SDK功能,在玩家登陆后上报一个标签而已。
以上便是本次本地化元素相关的一些内容,下一章节讲一下服务器下发的具体流程。
最后,恭喜TLOU2拿下今年7个奖项,心中最美的画面永远停留在这里了