相关文章推荐
纯真的葡萄  ·  将C代码中的warning当做错误处理 ...·  2 年前    · 
卖萌的煎鸡蛋  ·  C语言实现函数重载-阿里云开发者社区·  2 年前    · 
威武的茶叶  ·  MySQL笔记4--事务&视图&存储过程&锁 ...·  2 年前    · 
文雅的牛肉面  ·  Node.js API (API) - ...·  2 年前    · 
读研的葫芦  ·  一个Wpf控件库(Wpf客户端框架使用) ...·  2 年前    · 
Code  ›  【游戏开发解答】Unity发布微信小游戏,中文字无法显示的问题(自制字体库 | Font | Custom set | 动态字体 | 静态字体)_unity 静态字体_林新发的博客
https://blog.csdn.net/linxinfa/article/details/123681974
力能扛鼎的抽屉
1 年前
      • 一、前言
      • 二、Unity默认的字体:Arial
      • 三、查看动态字体的动态纹理
      • 四、用动态字体还是静态字体
        • 1、用不用Arial动态字体
        • 2、用Dynamic还是Custom set
      • 五、字体文件资源瘦身
        • 1、办法一:字体文件本身做裁剪
        • 2、办法二:制作静态字体
      • 六、扫描工程中用到的字,自动设置Custom Chars
        • 1、配置表、代码文本扫描
        • 2、预设文本扫描
        • 3、通用标点符号、数字、字母
        • 4、字符去重
        • 5、执行扫描,自动设置customCharacters
      • 七、微信小游戏中文显示问题

      嗨,大家好,我是林新发。
      我使用 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的人,共勉~

      之前编写过WebGL平台转微信小游戏的教程,以为顺风顺水的开始了开发。结果在将一定的功能做完后转换的工程用微信开发者工具打开后,发现所有的中文字都丢失了。如图: 查看了log也没有找到相关的报错或者提示。 在官网上也没查到方法,最后在在github上下载了它的官方案例,在排行榜\Demo\Ranking中找到了答案,它使用了一个“字魂59号-创粗黑”字体,而不是Unity内置的Arial字体。 这里我觉得能找到答案:字体文件需要放入工程中。 不过验证后,发现只是”我觉得”了。 我将C:\W Unity3D中支持动态字体和静态字体两种格式字体,动态字体即使用TTF格式字体库,静态字体则需要自己打包字体图集。动态字体和静态字体区别在于,动态字体如果出现字体库中不存在的字体,会使用系统字体,而静态字体则不会,而且静态字体是图片,字体大小通过缩放来改变。Unity3D也有自带的字体,Windows下自带字体为Arial。如果使用Unity3D的自带字体Arial字体,在某些机型上可能显示不全... 4.webgl做纹理内存优化的时候,需要注意 DXT5或者ETC2压缩格式对图片分辨率是有要求的,4的倍数 or 2的幂次方,如果不是,则会压缩失败,会转成(RGBA32)。6.微信小游戏的虚拟支付接入需要注意 mchID 和 appID的绑定关系,我中间因为有一次公司转移的操作,导致这两个ID不匹配,支付拉不起来。新号的话,注意一下就行。5.SDK带了一个资源优化工具,使用的时候注意MaxSize的勾选可以去掉(感觉没什么用,会导致一些图片不能正常显示,自己根据内存占用手动调整比较好)。 最近,小编在Unity WebGL平台上制作一个有趣的网页版的音乐播放器,在项目发布时,发现歌曲中含有中文的字样都不显示,表示有些着急。其实在打包项目时,默认选择Unity 自带的Arial字体,所以第一步考虑应该是字体类型不正确,所以选择替换其他字体的方案。其实在网上也有很多解决的方案,在网上看到有一种通用的字体,DroidSansFallback字体,但是实际活用以后,效果并不好,还是存在中文... Unity 推荐使用 TextMesh Pro 来代替内置的现有文本组件如 Text Mesh 及 UI Text,因为 TextMesh Pro 可以渲染出非常清晰的文本。在使用过程中发现,发现有以下问题: TextMesh Pro 需要先生成一张静态字体图集,每当静态文字增加时,都需要打开自带的生成工具,一个参数一个参数的设置,非常麻烦,也不便于其他人员使用。 每一个字体资产都会...
 
推荐文章
纯真的葡萄  ·  将C代码中的warning当做错误处理 -Wall -Werror_warning: unused variable 'q8035_valid' [-wunused-v_不留你的名字的博客-CSDN博客
2 年前
卖萌的煎鸡蛋  ·  C语言实现函数重载-阿里云开发者社区
2 年前
威武的茶叶  ·  MySQL笔记4--事务&视图&存储过程&锁 - 知乎
2 年前
文雅的牛肉面  ·  Node.js API (API) - Webpack 中文开发手册 - 开发者手册 - 腾讯云开发者社区-腾讯云
2 年前
读研的葫芦  ·  一个Wpf控件库(Wpf客户端框架使用) - 竹天笑 - 博客园
2 年前
今天看啥   ·   Py中国   ·   codingpro   ·   小百科   ·   link之家   ·   卧龙AI搜索
删除内容请联系邮箱 2879853325@qq.com
Code - 代码工具平台
© 2024 ~ 沪ICP备11025650号