游戏开发解答Unity发布微信小游戏,中文字无法显示的问题(自制字体库 | Font | Custom set | 动态字体 | 静态字体)
Posted 林新发
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了游戏开发解答Unity发布微信小游戏,中文字无法显示的问题(自制字体库 | Font | Custom set | 动态字体 | 静态字体)相关的知识,希望对你有一定的参考价值。
文章目录
一、前言
嗨,大家好,我是林新发。
我使用Unity
开发项目,然后发布成微信小游戏的时候遇到了一个问题:有部分中文字无法显示。
发现是因为个别界面预设中的Text
使用了默认的Arial
字体,为什么会出现中文字无法显示的问题呢?
我觉得有必要写篇文章讲讲。
注:可能有同学会问:
Unity
开发的项目可以直接发布微信小游戏吗?答案是可以滴,不过需要安装一个发布工具插件,插件GitHub
地址:https://github.com/wechat-miniprogram/minigame-unity-webgl-transform
详细参见GitHub
中的文档即可,如果有需要的话我再另外写文章讲解操作流程。
二、Unity默认的字体:Arial
Unity
默认使用的字体是Arial
,比如你用UGUI
创建一个Text
,你就会看到它使用的Font
为Arial
,
Arial
是Windows
的系统字体之一,Unity
直接访问它,并以动态字体(Dynamic
)的形式渲染。
Arial
本身并不包含中文字库,为了证明Arial
字体不包含中文字库,我使用FontCreator
这个软件打开arial.ttf
字体,然后预览窗口中输入Hello,我叫林新发
,可以看到,中文字显示不了:
注:
FontCreator
这个软件可以从我的GitCode
上免费下载:https://gitcode.net/linxinfa/fontmaker/-/blob/master/FontCreator.zip?from_codechina=yes
另外,我之前写过一篇关于字体裁剪的文章,里面有介绍这个软件的使用:《字体裁剪,精简字体,字体瘦身:FontSubsetGUI,FontCreator,FontPruner》
那Unity
是怎么把中文显示出来的呢?我贴一段Unity
官方手册的说明吧~
Unity官方手册:
当Unity
尝试使用 动态字体 渲染文本但无法找到字体时(因为未选择Include Font Data__
,并且用户计算机上未安装该字体),或者字体不包含请求的字形时(例如尝试使用拉丁字体在东亚脚本中渲染文本时,或者使用粗体/斜体字形文本时),它将尝试Font Names__
字段中列出的每种字体,从而查看是否可以找到与项目中的字体名称匹配的字体(包含字体数据)或者用户计算机上安装的字体是否具有请求的字形。如果找不到列出的后备字体或者这些字体不具有所请求的字形,Unity
将回退到硬编码的全局后备字体列表,其中包含当前运行时平台上通常安装的各种国际字体。
手册文档中提到的Include Font Data__
和Font Names__
字段,我们在Unity
中选择字体文件,在Inspector
面包中进行设置:
所以我们的中文并不是通过Arial
这个字体来渲染的,而是通过其他系统字体来渲染的。
三、查看动态字体的动态纹理
跟3D
模型的渲染类似,文字的渲染过程也是GPU
通过网格、纹理、材质等信息计算绘制出来的,
动态字体 是在运行时动态创建字的纹理,并且当出现字体库中不存在的字时,会从系统的默认字体库中查找对应的文字。
注:如果系统默认的字库中也没有这个字,就会造成字体不显示的问题。
我们创建一个Text
,使用默认的Arial
动态字体,如下
我们可以看到英文和中文都能正常显示,
接着,我们打开Frame Debugger
,
可以看到渲染文字时它动态生成了一张Font Texture
,不过在Frame Debugger
中看不清,
没关系,我们可以通过代码把这张纹理图取出来显示到界面中。我们先创建一个RawImage
,用于显示字体纹理,
然后创建一个Main.cs
脚本,代码如下:
using UnityEngine;
using UnityEngine.UI;
public class Main : MonoBehaviour
public Text text;
public RawImage img;
void Start()
img.texture = text.font.material.mainTexture;
把Main.cs
脚本挂到Canvas
节点上,并赋值Text
和RawImage
对象,如下,
运行Unity
,现在可以看清字体的纹理啦,
现在我们来玩个好玩的,我动态调整Text
的字号,可以看到它动态生成了不同字号的纹理,如下
这就是为什么我们使用动态字体时,不同字号的字清晰度不同的原因,它会根据你字号所在的段位查找匹配的纹理进行渲染。
四、用动态字体还是静态字体
用动态字体还是静态字体,这个问题要具体情况具体分析。
1、用不用Arial动态字体
如果你的项目是纯英文的项目,你可以使用默认的Arial
动态字体,否则不要使用默认的Arial
动态字体,原因如下:
不同平台的系统默认字体不同,比如在
android
系统中Unity3D
默认会去查找名为DroidSansFallback
的字体,但是因为Android
系统的可定制性,很多手机厂商会去修改默认字体。Android
系统是根据字体的文件名称来找字体的,但Unity3D
识别字体却是通过字体内部的设置来识别字体的,所以即使在Android
手机中有DroidSansFallback.ttf
字体,也有可能导致Unity3D
找不到这个字体。由于上面的原因,尽量不要使用Unity3D
的默认字体Arial
,除非你做的游戏是纯英文的。
2、用Dynamic还是Custom set
自己导入一个TTF
字体,是用Dynamic
还是用Custom set
呢?
我们先来做个实验,以这个这个Dengb.ttf
字体为例,它有15M
这么大(实际项目不会用这么大的字体,会做一些处理,下文会讲解决办法),
假设我们使用Dynamic
,并且勾选的了Incl. Font Data
,如下,
现在我们使用Addressables
系统,把它单独放在一个Group
中,如下
注:关于
Addressables
系统,我之前写过一篇教程,可以参见我之前的这篇文章:《【游戏开发探究】Unity Addressables资源管理方式用起来太爽了,资源打包、加载、热更变得如此轻松(Addressable Asset System | 简称AA)》
现在们打包资源,看生成的.bundle
文件大小,有11.1M
那么大,
我们使用AssetStudio
逆向这个.bundle
文件,可以看到它包含了完整的字体文件数据和一张空纹理,
如果字体放在包内,你的包体就会变大,如果你是动态下载,那么就要下载十几兆的字体bundle
,这对于存储空间和资源下载都不是很友好,当然,如果你不在乎这点存储空间和下载时间,可以不用管~
五、字体文件资源瘦身
有没有缩小字体文件的办法呢?有两种解决办法。
1、办法一:字体文件本身做裁剪
对ttf
字体本身做裁剪瘦身,我之前写过一篇教程,可以参见我这篇文章:《字体裁剪,精简字体,字体瘦身:FontSubsetGUI,FontCreator,FontPruner》
字体设置上依然使用Dynamic
。
2、办法二:制作静态字体
制作静态字体,也就是使用Custom set
。下面我着重讲下这种办法。
注意,如果使用静态字体,当出现不存在的字形时,Unity
并不会像动态字体那样去帮我们查询后备字体和系统字体,所以会导致文字无法正常渲染。
所以这里就涉及到一个问题,我们使用Custom set
,要填写的Custom Chars
该填多少个字呢?
不管三七二十一,把所有的汉字都填进去,那是不科学的,咱们就拿1994
年出版的《中华字海》来说,它收字有85,568
个,全放进去,生成的纹理该有多大啊。
一般我们只会放常用的8000汉字
、英文字母、数字、标点符号等,设置Font Size
为60
,即使这样,生成的纹理尺寸已经达到极限的 4096 x 4096
,有16M
这么大,
可以酌情得把常用汉字减少一些,比如减到5000字
,另外,如果你非常确定所需要显示的字量,比如我非常明确只用到了Hello,林新发
这几个字,Custom Chars
中我就只需要填Hello,林新发
这几个字,
生成的纹理只有32KB
,
我们运行时动态修改Text
的字号,可以发现它始终都是引用静态字体的纹理,不会像动态字体那样动态生成纹理,字号的调整仅仅只是做纹理的缩放,当缩放过大时就会显得模糊,
另外,由于纹理是静态的,Text
的Font Style
只能是Normal
,不能设置斜体、粗体等,
现在,我们使用Addressables
系统重新打包资源,可以看到生成的.bundle
只有8.13KB
,
我们使用Asset Studio
逆向bundle
文件,可以看到里面存放这我们的32KB
纹理和470B
的字体信息,这样就大大减少了字体文件的资源大小了,
那么问题又来了,我们如何确定工程中到底用到了哪些字呢?我们可以写工具去扫描整个工程。
六、扫描工程中用到的字,自动设置Custom Chars
一般我们工程中使用到的文字,会散落在以下一些地方:配置表、代码写死的字符串、预设中摆放的Text
、预设中挂的MonoBehaviour
脚本的string
类型的成员变量等。
以下示例代码需引入的命名空间:
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;
using UnityEngine.UI;
using System.Text;
using System.Reflection;
1、配置表、代码文本扫描
配置表和代码的扫描,可以使用正则表达式匹配,如果懒的话,直接全部字符文本都读出来,以扫描json
配置表为例:
// 配置文本扫描
public string ScanJsonCfg()
StringBuilder sbr = new StringBuilder();
string[] fs = Directory.GetFiles(Application.dataPath + "/Config", "*.json", SearchOption.AllDirectories);
for (int i = 0, flen = fs.Length; i < flen; ++i)
var f = fs[i];
EditorUtility.DisplayProgressBar("扫描中", f, (float)i / flen);
var path = f.Replace(Application.dataPath, "Assets");
var cfg_text = AssetDatabase.LoadAssetAtPath<TextAsset>(path).text;
// TODO 可自行做正则表达式匹配
// ...
sbr.AppendLine(cfg_text);
EditorUtility.ClearProgressBar();
return sbr.ToString();
2、预设文本扫描
预设中文本的扫描,示例:
// 预设文本扫描
public string ScanPrefabText()
StringBuilder sbr = new StringBuilder();
string[] fs = Directory.GetFiles(Application.dataPath + "/Prefabs", "*.prefab", SearchOption.AllDirectories);
for (int i = 0, flen = fs.Length; i < flen; ++i)
var f = fs[i];
EditorUtility.DisplayProgressBar("扫描中", f, (float)i / flen);
var path = f.Replace(Application.dataPath, "Assets");
var go = AssetDatabase.LoadAssetAtPath<GameObject>(path);
var texts = go.GetComponentsInChildren<Text>(true);
for (int j = 0, len = texts.Length; j < len; ++j)
sbr.Append(texts[j].text);
// 通过反射获取所有脚本的public string的成员----------------------------------------
var triggers = go.GetComponentsInChildren<MonoBehaviour>(true);
for (int j = 0, len = triggers.Length; j < len; ++j)
var fields = triggers[j].GetType().GetFields(BindingFlags.Public | BindingFlags.Instance);
foreach (var field in fields)
if (field.FieldType == typeof(string))
var txt = (string)field.GetValue(triggers[j]);
sbr.Append(txt);
EditorUtility.ClearProgressBar();
return sbr.ToString();
3、通用标点符号、数字、字母
再补充一些标点符号、数字、字母啥的,
// 通用标点符号、数字、字母等
public string GetCommonString()
return @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
, 、 。 . ? ! ~ $ % @ & # * ? ; ︰ … ‥ ﹐ ﹒ ˙ ? ‘ ’ “ ” 〝 〞 ‵ ′ 〃 ↑ ↓ ← → ↖ ↗ ↙ ↘
㊣ ◎ ○ ● ⊕ ⊙ ○ ● △ ▲ ☆ ★ ◇ ◆ □ ■ ▽ ▼ § ¥ 〒 ¢ £ ※ ♀ ♂
ΑΒΓΔΕΖΗΘΙΚ∧ΜΝΞΟ∏Ρ∑ΤΥΦΧΨΩ
α β γ δ ε ζ η θ ι κ λ μ ν ξ ο π ρ σ τ υ φ χ ψ ω
АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ
а б в г д е ё ж з и й к л м н о п р с т у ф х ц ч ш щ ъ ы ь э ю я
ā á ǎ à、ō ó ǒ ò、ê ē é ě è、ī í ǐ ì、ū ú ǔ ù、ǖ ǘ ǚ ǜ ü
ˉˇ¨‘’々~‖∶”’‘|〃〔〕《》「」『』.〖〗【【】()〔〕}[]~||¶µ©®ßΛΣΠ€♯♪♫
ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ①②③④⑤⑥⑦⑧⑨⑩
≈≡≠=≤≥<>≮≯∷±+-×÷/∫∮∝∞∧∨∑∏∪∩∈∵∴⊥‖∠⌒⊙≌∽√
§№☆★○●◎◇◆□■△▲※→←↑↓〓#&@\\^_':;`:¡¢£¦«»¯´·ˊˋƒ‒―‚„†‡•″‹›℅ℓΩ℮↔↕∂─│╒▀▐▪▫◊◦♠♣♥♦⃝⃞‧℧∅∝∞()!*
◢◣◤◥☉♀♂°′〃$£¥‰%℃¤¢⊙●○①⊕◎Θ⊙¤㊣▂ ▃ ▄ ▅ ▆ ▇ █ █ ■ 回 □ 〓≡ ╝╚╔ ╗╬ ═ ╓ ╩ ┠ ┨┯ ┷┏ ┓┗ ┛┳⊥『』┌♀◆◇◣◢◥▲▼△▽⊿
";
4、字符去重
再封装一个字符去重的方法,
// 字符去重
public string DeRepeat(string str)
StringBuilder sbr = new StringBuilder();
Dictionary<char, object> charDic = new Dictionary<char, object>();
for (int i = 0, len = str.Length; i < len; ++i)
if (!charDic.ContainsKey(str[i]))
charDic.Add(str[i], null);
sbr.Append(str[i]);
GameLogger.Log("总字数: " + charDic.Count);
return sbr.ToString();
5、执行扫描,自动设置customCharacters
最后,我们调用上面的方法执行扫描,自动设置到字体的customCharacters
字段中,如下
StringBuilder sbr = new StringBuilder();
sbr.Append(ScanPrefabText());
sbr.Append(ScanJsonCfg());
sbr.Append(GetCommonString());
var characters = DeRepeat(sbr.ToString());
var imp = AssetImporter.GetAtPath("Assets/Font/Dengb.ttf") as TrueTypeFontImporter;
imp.customCharacters = characters;
AssetDatabase.ImportAsset("Assets/Font/Dengb.ttf");
七、微信小游戏中文显示问题
好了,啰嗦了那么多,现在回归开头的问题,为什么微信小游戏显示中文的时候出了问题呢?
因为微信小游戏跑的是WebGL
平台,而在WebGL
平台Unity
不能访问系统字库,它忽略Include Font Data
设置,并会始终包含字体数据。所有要用作后备字体的字体都必须包含在项目中,所以如果你用了Arial
动态字体,它在WebGL
平台就只能渲染英文,无法渲染中文字了。
解决办法就是导入一个带中文字库TTF
字体,你可以使用Dynamic
也可以使用Custom Set
,鉴于微信小游戏的内存条件,我建议使用Custom Set
。
另外,关于字体的玩法,我之前写过一篇文章:【游戏开发创新】Unity狗屁不通文章生成器阐述点赞的意义,可生成文字长图保存到本地(Unity | 附源码 | Text转Texture长图 | 详细教程)
里面我讲了如何通过Text
来获取字体信息并把Text
渲染成图片的方法,感兴趣的同学可以看下。
好了,就啰嗦这么多吧~
我是林新发,https://blog.csdn.net/linxinfa
一个在小公司默默奋斗的Unity
开发者,希望可以帮助更多想学Unity
的人,共勉~
以上是关于游戏开发解答Unity发布微信小游戏,中文字无法显示的问题(自制字体库 | Font | Custom set | 动态字体 | 静态字体)的主要内容,如果未能解决你的问题,请参考以下文章
游戏开发解答Unity发布微信小游戏,中文字无法显示的问题(自制字体库 | Font | Custom set | 动态字体 | 静态字体)
[Unity转小游戏]微信开发者工具/微信小游戏中找不到unityInstance.(unityInstance is not defined)