面临的问题

像素游戏是独立游戏的一种常用表现方式,在制作中文游戏时我们要面临显示点阵汉字的问题。当前各大游戏引擎中都会有显示中文的功能,但显示出来的中文字体效果一般都差强人意任意,在低分辨率的像素游戏画面下会产生一些问题:

  • 默认的防锯齿使得字体跟游戏画面的整体风格不搭;
  • 关掉防锯齿后,矢量的字库渲染到低分辨率画面上字型比较难看;
  • 为了实现在各设备上的统一效果,可以将字体嵌入到游戏中,但是一个中文字库动辄十几 M 的容量会消耗大量的资源空间,甚至超过游戏本体的容量大小,对于 HTML5 这样的平台更会增加下载时间。

令我困扰的是像 Construct 这样的 HTML5 引擎在使用原生字体渲染的时候,无法把字号调整到最小(也许跟设备有关),这是我把字号设置为0.1pt 的情况:

很多 GBA 汉化游戏都做得很好,这就是我要追求的效果:

解决思路

  • 使用贴图的形式来显示汉字,把用到的汉字当作图片存储和显示;
  • 汉字库只存放常用汉字,或者说只存使用到的汉字库;
  • 使用点阵汉字库,而不是矢量汉字库;

具体的实现

首先使用贴图的形式会比直接渲染矢量的会更节省性能的消耗,大多数游戏引擎都提供了 Bitmap Font 或者 Sprite Font 这样的使用贴图来显示字体的功能。实际上就是把所有可能用到的字符事先画到一张贴图上,需要时再逐个渲染出来。当然英文及类似的拼音文字系统所使用的字符数目比较少,所以在这方面比较省事,一张贴图就能搞定。可是中文可以在一张贴图内搞定么?让我们来算一下,比如我们理想中的点阵汉字大小为 16x16 像素,那么在一张 1024x1024 的贴图中,一共可以存放 4096 个汉字,太好了,因为我们所常用的汉字也就是 3500 个,你可以上网搜索到这3500个汉字的表。有了这个常用汉字表,我们就可以用这张表来生成贴图。有些游戏引擎,比如 game maker studio 是内置了这样的点阵字库生成功能的。另外一些就需要借助一些工具。有不少可以生成字库点阵贴图,比较有名的是 BMFont。这些软件可以让你输入要生成的字符表,选择你想要的字体,设定生成的字体大小,还可以设定颜色和描边的效果以及是不是防锯齿等。这些软件生成贴图的同时会生成一个数据文件,这个数据文件会保存有常用汉字对应贴图中的位置等信息,游戏在需要渲染点阵字体时可以使用这些数据来得到每个汉字对应的贴图区域。这样做还有一个额外的好处:可以预先叠加效果到字体上,比如描边和渐变等,这样也会省去处理这些效果时产生的性能消耗。

Construct 中默认的像素字体,西文的好处就是字符量很少,你甚至可以自己手写设计,工作量不大:

比较常见的位图字体生成工具 BMFont:

BMFont 使用微软雅黑输出16像素非防锯齿的汉字,很不好看:

字体的选择

现在有了工具,我们接下来要选择使用什么样的字体了。这个问题需要注意,因为大多数字体都不是免费的,特别是你要用在商业用途上,所以在选择字体时一定要注意看准字体的版权声明。中文可免费商用的字体其实并不多,其中最有名的是 Google 和 Adobe 开发的思源系列字体。不过我测试了一些的这样免费商用的矢量字体,都普遍存在一个问题:这些字体并不是为了点阵显示而制作的,在选择比较小的字号同时关掉防锯齿时,出来的效果是机器不美观的。因为我当前追求的是低像素分辨率的画面,所以这些字体并不能符合我的要求。

我要寻找在低字号大小无防锯齿情况下都能表现良好的字体。回想一下,在 DOS 时代,我们的汉字字体都是点阵的,如果你现在搜索 HZK16 时可以搜索到不少信息的,但是关于以前 DOS 时代的这些汉字字体的版权,能够查到的信息并不多。我们暂且把这个作为一个备选方案。另外,其实我们很多主机游戏的汉化都会涉及点阵汉字字体的问题,我的印象中不少 GBA/3DS 汉化游戏的字体都是处理得不错的,当然因为是非商用,字体选择可以很多。同时,虽然现在我们的大多数设备都可以渲染矢量字体,但还是有很多设备是需要显示点阵的,比如各种 LCD 显示屏。所以我觉得还是有针对点阵显示设备设计的字体。我搜索到了“最像素”这个字体,这个字体似乎是一个人开发的,而且是专门为极小分辨率点阵显示准备的。不过唯一的问题是,商业使用还是需要付费授权的。

DOS 时代,320x240 256 色是比较常见的分辨率,当时的中文处理是这样的:

UCDOS:

WPS:

最像素字体:

当我在尝试各种可以免费商用的字体时,我发现了“文泉驿”(http://wenq.org)这个开源的字体系列。里面竟然有一款专为点阵设计的宋体,字号从9像素到12像素,显示效果非常的不错,那么决定就是它了!

补充一下:文泉驿为 GPL 协议,商用需要作者授权,提醒大家注意~

输出的问题

在一般情况下,二手手游出售平台使用前面提到的 bmfont 这样的软件工具,以及文泉驿点阵宋体,已经可以解决大多数需求,只要你使用的游戏引擎支持使用的 bmfont 生成的数据文件就可以了。不过因为我用的是 Consturct ,一个 HTML5 游戏制作软件,它支持使用的点阵贴图要求每个字符是同等大小的,但是那些字体贴图软件大多数都不支持生成等宽的字体贴图,或者是支持生成等宽字体贴图的软件有各种缺陷,比如贴图大小不可控,无法关掉防锯齿,不支持太多字符集等。

BMFont 输出的字体都是不等宽的,也就是说输出时要经过计算矫正:

有人专门做了给 Construct 用的工具,原先的问题是不支持太大的字符集,现在已经修正,现在唯一的问题就是没有去掉防锯齿的功能:

编写工具

最后还是得自己动手做工具,既然在前面我们已经研究了这么多,生成一张这样的贴图对于做游戏的我们来说就不是什么难事了。我现在面临的选择就是用什么来做。本来我是很熟悉 Javascript 这一块的,但是我所知道的 HTML5 相关的引擎都很难渲染出小字号的不带防锯齿的字体。那么用 Lua 呢?我以前用过一段时间 Love2D 感觉处理这样的 2D 像素是比较好的,以前我还用它来制作过处理像素画的软件。但是问题是我没发现它能够渲染没有防锯齿的字体,可惜,而且 Love2D 还有一个缺点就是处于安全性的考虑,它只能写入文件到一个固定的 sandbox 文件夹中,这样做出来的工具使用上比较麻烦些。

最后,我开始考虑到我可以使用的另外一个脚本式语言:Python。如果这个还不行,我就只能考虑 Haxe 和 C 之类的了。说到 Python,我会熟悉 Python 主要是因为我使用 Blender ,使用 Python 可以让我做一些插件扩展。所以以前我是考核过它的游戏制作能力的。它的最出名的游戏库就是 Pygame(https://pygame.org),不过这个 Pygame 的确很 Old School,它是个 2D 引擎,有很多跟像素相关的功能,而且很多概念还停留在 Blit 位图的层面上。不过我仔细看了一下它的最新版的文档,发现它的字体处理应该可以满足我的要求,因为我明确的看到了它可以关掉字体的防锯齿渲染。所以决定就是它了!拿出 Python 书,临时温习一下,同时看下 Pygame 的文档,很快我就做出了自己想要的工具,输出了合适的位图。

推荐使用 PyCharm ,用来写 Python 体验还是很好的:

收尾

在收尾工作中,我需要处理一些问题:

- 因为对话中也不免出现英文。这个时候会遇到一点小麻烦,因为中文基本都是等宽的,而英文每个字符有可能是不等宽的,如果我们按照汉字的方式来显示每一个英文字母的话,会出现英文字符之间间隔过宽的问题,看起来就是不好看。不过 construct 是考虑到这个情况的,你只要输出对应需要调整宽度的字符列表及其宽度就可以了。

没有宽度矫正和有宽度矫正的西文字符的区别:

还有一个比较麻烦的问题就是英字其实是有基线的,在我们单独输出某一个小写的英文字母时会失去基线的对齐,幸好 Pygame 里面是可以取得基线的信息的,输出字母时调整这个高度即可。

所谓的基线就是红线标的位置:

没有考虑基线时输出的西文小写字符:

按照基线调整输出的西文小写字符:

如果不考虑基线输出的话,结果会是这样:

最后,在使用过程中还是出现缺字的情况。这主要是因为我们使用的某个汉字不在常用汉字列表里面,这个时候我们只需要在常用汉字表中加入这个字就可以了。其实我碰到的这个字是“哦”字,显然人们的汉语表达用语也在不断的变化中,现在的一些常用口语有可能并不在这个常用汉字表中。也许以后根据游戏的结构,我会考虑做一个只按照使用过的汉字生成贴图的功能。

Python 源码

  1. import pygame                   # Pygame 游戏模块
  2. from pygame import freetype     # 处理矢量字库的 Pygame 模块
  3. import codecs                   # 处理 unicode 所需模块
  4. import json                     # 输出 json 格式 所需模块
  5. # 等宽部分的字符表
  6. fixWCharset = codecs.open( "hz3500.txt","r","utf-8" ).read()     # 读取3500个常用汉字的表
  7. fixWCharset = fixWCharset + u"哦"                               # 加入常用字中没有的字
  8. # 需要记录宽度信息的字符表
  9. varWCharset = codecs.open( "ascii.txt","r","utf-8" ).read()      # 加入常用的 ascii 字符 表
  10. varWCharset = varWCharset + u",。;“”、:?《》"                 # 加入拳脚的汉字标点
  11. gridW     = 14 # 每个字符输出区域的宽度
  12. gridH     = 14 # 每个字符输出区域的高度
  13. outColNum = 90 # 每行输出的字符数
  14. outRowNum = 42 # 一共输出的行数
  15. textureW  = gridW * outColNum # 最终输出的贴图宽度
  16. textureH  = gridH * outRowNum # 最终输出的贴图高度
  17. pygame.init()                                                # 初始化游戏引擎
  18. pygame.display.set_caption("像素点阵汉字生成")                 # 窗口的标题
  19. screen = pygame.display.set_mode( (textureW, textureH) )       # 打开的窗口大小
  20. buffer = pygame.Surface( (textureW,textureH),pygame.SRCALPHA ) # 建立一个透明贴图大小的缓冲区,贴图先
  21. # 因为非等宽字体还要需要处理基线的问题,所以同一个字体载入到两个变量之中,可以进行不同的设置
  22. fixWFont = pygame.freetype.Font( 'wenquanyi_9pt.pcf' ) # 等宽字符所用字体
  23. varWFont = pygame.freetype.Font( 'wenquanyi_9pt.pcf' ) # 非等宽字体所用字体
  24. # 关掉防锯齿
  25. fixWFont.antialiased = False
  26. varWFont.antialiased = False
  27. varWFont.origin = True # 使用基线方式渲染字体
  28. varWFontSize    = 12   # 非等宽字体的固定输出为 12 像素
  29. baseLine        = 10   # 设定从顶部往下 10 个像素为基线
  30. x = 0 # 字符输出的行坐标
  31. y = 0 # 字符输出的列坐标
  32. fontColor    = ( 255,255,255 )    # 字体颜色
  33. outlineColor = ( 0,0,0 )          # 描边颜色
  34. for i in range( 0, len(fixWCharset) ): # 遍历常用汉字表
  35. fx   = x * gridW # 字符输出的像素坐标 x
  36. fy   = y * gridH # 字符输出的像素坐标 y
  37. char = fixWCharset[i]
  38. # 渲染字符描边
  39. fixWFont.render_to( buffer, (fx+1, fy+0), char, outlineColor )
  40. fixWFont.render_to( buffer, (fx+1, fy+2), char, outlineColor )
  41. fixWFont.render_to( buffer, (fx+0, fy+1), char, outlineColor )
  42. fixWFont.render_to( buffer, (fx+2, fy+1), char, outlineColor )
  43. # 渲染字符
  44. fixWFont.render_to( buffer, (fx+1, fy+1), char, fontColor )
  45. # 行列递增
  46. x = x + 1
  47. if (x>=outColNum):
  48. x = 0
  49. y = y + 1
  50. widthDict = {} # 记录宽度的字典
  51. for enIndex in range(0, len(varWCharset)):
  52. fx = x * gridW # 字符输出的像素坐标 x
  53. fy = y * gridH # 字符输出的像素坐标 y
  54. char = varWCharset[enIndex]
  55. # 渲染字符描边
  56. varWFont.render_to( buffer, (fx+1, baseLine+fy+0), char, outlineColor, size=varWFontSize )
  57. varWFont.render_to( buffer, (fx+0, baseLine+fy+1), char, outlineColor, size=varWFontSize )
  58. varWFont.render_to( buffer, (fx+2, baseLine+fy+1), char, outlineColor, size=varWFontSize )
  59. varWFont.render_to( buffer, (fx+1, baseLine+fy+2), char, outlineColor, size=varWFontSize )
  60. # 渲染字符
  61. varWFont.render_to( buffer, (fx+1, baseLine + fy+1), char, fontColor, size=varWFontSize )
  62. # 记录字符宽度
  63. m     =  varWFont.get_metrics( char, size=varWFontSize )
  64. lineX = fx + m[0][1]
  65. charW = m[0][1] + 3
  66. if not charW in widthDict : widthDict[charW] = []
  67. widthDict[charW].append( char )
  68. # 行列递增
  69. x = x + 1
  70. if ( x >= outColNum ):
  71. x = 0
  72. y = y + 1
  73. # 输出 construct 3 所需的宽度 json 文件
  74. outputList = []
  75. for wKey in widthDict:
  76. charStr = ""
  77. for char in widthDict[wKey] : charStr = charStr + char
  78. outputList.append( [wKey,charStr] )
  79. outJson = json.dumps( outputList )
  80. print( "Json String For Construct : " )
  81. print( outJson )
  82. filename = "construct-spriteFont-spaceData.json"
  83. file     = open( filename, "w" )
  84. file.write( outJson )
  85. file.close()
  86. print( "saved to : " + filename )
  87. # 输出整体字符集文件
  88. charset  = fixWCharset + varWCharset
  89. filename = "charset.txt"
  90. file     = codecs.open( filename, "w", "utf-8" )
  91. file.write( charset )
  92. file.close()
  93. print( "charset saved to : " + filename )
  94. # 保存贴图文件
  95. filename = "pixel-hz.png"
  96. pygame.image.save( buffer, filename )
  97. print( "texture saved to : " + filename )
  98. # 主循环
  99. running = True
  100. while running:
  101. # 在窗口中显示贴图
  102. screen.blit( buffer, (0, 0) )
  103. pygame.display.update()
  104. for event in pygame.event.get():
  105. if event.type == pygame.QUIT:
  106. running = False

复制代码

完成结果

最终生成的贴图,我还加入了常用的全角中文标点符号:

在 Aseprite 中检查,每个字都加上了黑色描边:

到此,对于在低分辨率像素游戏中使用点阵汉字的心得分享就这么多,希望对你有所帮助。

补充一下:文泉驿为 GPL 协议,商用需要作者授权,提醒大家注意~

关于低分辨率像素游戏下显示非防锯齿中文 / 汉字的研究相关推荐

  1. 从像素之间谈起:像素游戏的画面增强(下)

    其他可能的改进 投影增强 前面我们在进行扩散投影模拟的时候,是同时对周围八个点进行采样,但是事实上,有时为了控制投影的方向,可以只对一侧的点进行采样: 如图所示,只需要对右下角的五个格子采样,就可以模 ...

  2. 从像素之间谈起:像素游戏的画面增强

    转自:http://fushigi-hako.site/2017/07/02/from_pixel_to_screen_1/ 无所不在的像素画 分类 随着分辨率的普遍提高,我们已经告别了依赖于简陋像素 ...

  3. WPF 绘制对齐像素的清晰显示的线条

    WPF 绘制对齐像素的清晰显示的线条 原文:WPF 绘制对齐像素的清晰显示的线条 版权声明:本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可.欢迎转载.使用.重新发布, ...

  4. 像素工厂如何显示服务器列表,萌新如何快速解锁像素工厂炮台科技

    最近有很多新人入坑了像素工厂,这是一款塔防类的游戏,鼻祖是War3的里的TD小偷,在手机端则是植物大战僵尸,就是在地图上造满防御塔消灭敌人,随着关卡上升敌人会越来越强,击败敌人后获得金钱和科技也能升级 ...

  5. 连接服务器显示英文,cf游戏服务器显示英文

    cf游戏服务器显示英文 内容精选 换一换 修改裸金属服务器名称.裸金属服务器名称取值范围:只能由中文字符.英文字母(a~z,A~Z).数字(0~9).下划线(_).中划线(-).点(.)组成,且长度为 ...

  6. 新手教程:用像素游戏制作大师MV开发游戏(一)

    第一步 本指南,是面向第一次接触本系列的用户为对象制作的. 首先,从最初级的用自己制作的角色打到敌人这一部分开始介绍吧. 第一步"设置角色并让其动起来" 用像素游戏制作大师MV制作 ...

  7. unity3d实现像素游戏的精确碰撞判定

    -- 检测碰撞物,如果发生碰撞则进行位移 function LColliderBDY:BDYFixedUpdate(velocity)local isGround = falselocal isWal ...

  8. windows下的中文文件名共享在linux下显示乱码的问题

    1.现象描述 在windos环境下有一个含有中文的文件名,比如dataset_省调.scd. 在linux(虚拟机)下通过挂载系统将该文件挂载在/mnt/hgfs目录下,显示为乱码.但是通过ftp将文 ...

  9. linux网卡断流测试,Windows XP SP2操作系统下网络非完全断流的再分析(转)

    Windows XP SP2操作系统下网络非完全断流的再分析(转)[@more@] 在升级到WindowsSP2系统后,本人的电脑经常出现这种怪毛病,具体情况为:在上网时网关能够PING通,用Bitc ...

最新文章

  1. linux date
  2. (三)Amazon Lightsail 部署LAMP应用程序之连接到Lightsail数据库
  3. 在html中如何使用span,如何在HTML和CSS中使用DIV和span?
  4. android+p+华为手机,给1.9亿用户32款老机型进行安卓P升级 华为值吗?
  5. DCMTK:DcmFloatingPointDouble类的测试程序
  6. int 取值范围_一定范围内的随机数
  7. MySQL工作笔记-编写存储过程批量添加数据
  8. 十八、数据容器、数据访问宽度、端口(计算机对数据处理方式:读取、写入、运算;数据可存放三个地方:CPU内部、内存、端口)
  9. MFC中Doc,View,MainFrmae,App各指针的互相获取
  10. Android 6.0动态权限(转)
  11. 2021年游戏项目的十大编程语言:C++、Java、C#均上榜
  12. 如何解密网易ncm/qq音乐的qmcflac/酷狗kgm等加密格式转换成MP3
  13. 前嗅ForeSpider教程:采集新浪新闻
  14. 【题解】HNOI-2015落忆枫音
  15. 机器学习、数据挖掘、神经网络、人工智能和模式识别之间,主要是什么关系
  16. 学习笔记——STM32摄像头OV7725(一)
  17. 解决【react-native init awesomeproject没反应】
  18. 在matlab中ln10,ln函数(ln在函数中等于多少)
  19. Python爬取豆瓣电影短评
  20. Android进阶三部曲 第三部《Android进阶指北》已完稿

热门文章

  1. python islice_python文件操作细节
  2. sqlmap自动扫描注入点_SQLmap JSON 格式的数据注入
  3. 最长公共子序列和追踪解
  4. go 多线程并发 queue demo
  5. Java:清空文件内容
  6. 作业调度方案(codevs 1156)
  7. SQL2000中因为选定的用户拥有对象,所以无法除去该用户.
  8. zz Microsoft Chart Controls for Microsoft .NET Framework 3.5
  9. 2009 年 5 月 忙碌的一个月
  10. u-boot2013.10引导linux3.10.30记录