關(guān)于低分辨率像素游戲下顯示非防鋸齒中文 / 漢字的研究

  • 聲明:轉(zhuǎn)載請鏈接出處郎逃。
  • 作者:來自 BITCA.CN 的 Retro Daddy
  • 郵箱:harrisyu@qq.com

面臨的問題

像素游戲是獨立游戲的一種常用表現(xiàn)方式谬墙,在制作中文游戲時我們要面臨顯示點陣漢字的問題。當(dāng)前各大游戲引擎中都會有顯示中文的功能苔咪,但顯示出來的中文字體效果一般都差強人意任意乏屯,在低分辨率的像素游戲畫面下會產(chǎn)生一些問題:

  1. 默認的防鋸齒使得字體跟游戲畫面的整體風(fēng)格不搭;
  2. 關(guān)掉防鋸齒后病梢,矢量的字庫渲染到低分辨率畫面上字型比較難看垛耳;
  3. 為了實現(xiàn)在各設(shè)備上的統(tǒng)一效果,可以將字體嵌入到游戲中飘千,但是一個中文字庫動輒十幾M的容量會消耗大量的資源空間,甚至超過游戲本體的容量大小栈雳,對于 HTML5 這樣的平臺更會增加下載時間护奈。
令我困擾的是像 Construct 這樣的 HTML5 引擎在使用原生字體渲染的時候,無法把字號調(diào)整到最懈缛摇(也許跟設(shè)備有關(guān))霉旗,這是我把字號設(shè)置為0.1pt的情況:

很多GBA漢化游戲都做得很好,這就是我要追求的效果

解決思路

  1. 使用貼圖的形式來顯示漢字,把用到的漢字當(dāng)作圖片存儲和顯示;
  2. 漢字庫只存放常用漢字厌秒,或者說只存使用到的漢字庫读拆;
  3. 使用點陣漢字庫,而不是矢量漢字庫鸵闪;

具體的實現(xiàn)

首先使用貼圖的形式會比直接渲染矢量的會更節(jié)省性能的消耗檐晕,大多數(shù)游戲引擎都提供了 Bitmap Font 或者 Sprite Font 這樣的使用貼圖來顯示字體的功能。實際上就是把所有可能用到的字符事先畫到一張貼圖上蚌讼,需要時再逐個渲染出來辟灰。當(dāng)然英文及類似的拼音文字系統(tǒng)所使用的字符數(shù)目比較少,所以在這方面比較省事篡石,一張貼圖就能搞定芥喇。可是中文可以在一張貼圖內(nèi)搞定么凰萨?讓我們來算一下继控,比如我們理想中的點陣漢字大小為 16x16 像素,那么在一張 1024x1024 的貼圖中胖眷,一共可以存放 4096 個漢字武通,太好了,因為我們所常用的漢字也就是 3500 個瘦材,你可以上網(wǎng)搜索到這3500個漢字的表厅须。有了這個常用漢字表,我們就可以用這張表來生成貼圖食棕。有些游戲引擎朗和,比如 game maker studio 是內(nèi)置了這樣的點陣字庫生成功能的。另外一些就需要借助一些工具簿晓。有不少可以生成字庫點陣貼圖眶拉,比較有名的是 BMFont(http://www.angelcode.com/products/bmfont/)。這些軟件可以讓你輸入要生成的字符表憔儿,選擇你想要的字體忆植,設(shè)定生成的字體大小,還可以設(shè)定顏色和描邊的效果以及是不是防鋸齒等谒臼。這些軟件生成貼圖的同時會生成一個數(shù)據(jù)文件朝刊,這個數(shù)據(jù)文件會保存有常用漢字對應(yīng)貼圖中的位置等信息,游戲在需要渲染點陣字體時可以使用這些數(shù)據(jù)來得到每個漢字對應(yīng)的貼圖區(qū)域蜈缤。這樣做還有一個額外的好處:可以預(yù)先疊加效果到字體上拾氓,比如描邊和漸變等,這樣也會省去處理這些效果時產(chǎn)生的性能消耗底哥。

Construct 中默認的像素字體咙鞍,西文的好處就是字符量很少房官,你甚至可以自己手寫設(shè)計,工作量不大

比較常見的位圖字體生成工具BMFont

BMFont 使用微軟雅黑輸出16像素非防鋸齒的漢字续滋,很不好看

字體的選擇

現(xiàn)在有了工具翰守,我們接下來要選擇使用什么樣的字體了。這個問題需要注意疲酌,因為大多數(shù)字體都不是免費的蜡峰,特別是你要用在商業(yè)用途上,所以在選擇字體時一定要注意看準字體的版權(quán)聲明徐勃。中文可免費商用的字體其實并不多事示,其中最有名的是 Google 和 Adobe 開發(fā)的思源系列字體。不過我測試了一些的這樣免費商用的矢量字體僻肖,都普遍存在一個問題:這些字體并不是為了點陣顯示而制作的肖爵,在選擇比較小的字號同時關(guān)掉防鋸齒時,出來的效果是機器不美觀的臀脏。因為我當(dāng)前追求的是低像素分辨率的畫面劝堪,所以這些字體并不能符合我的要求。

我要尋找在低字號大小無防鋸齒情況下都能表現(xiàn)良好的字體揉稚∶肜玻回想一下,在 DOS 時代搀玖,我們的漢字字體都是點陣的余境,如果你現(xiàn)在搜索 HZK16 時可以搜索到不少信息的,但是關(guān)于以前 DOS 時代的這些漢字字體的版權(quán)灌诅,能夠查到的信息并不多芳来。我們暫且把這個作為一個備選方案。另外猜拾,其實我們很多主機游戲的漢化都會涉及點陣漢字字體的問題即舌,我的印象中不少 GBA/3DS 漢化游戲的字體都是處理得不錯的,當(dāng)然因為是非商用挎袜,字體選擇可以很多顽聂。同時,雖然現(xiàn)在我們的大多數(shù)設(shè)備都可以渲染矢量字體盯仪,但還是有很多設(shè)備是需要顯示點陣的紊搪,比如各種 LCD 顯示屏。所以我覺得還是有針對點陣顯示設(shè)備設(shè)計的字體全景。我搜索到了“最像素”(https://github.com/SolidZORO/zpix-pixel-font)這個字體耀石,這個字體似乎是一個人開發(fā)的,而且是專門為極小分辨率點陣顯示準備的蚪燕。不過唯一的問題是娶牌,商業(yè)使用還是需要付費授權(quán)的。

DOS 時代馆纳,320x240 256 色是比較常見的分辨率诗良,當(dāng)時的中文處理是這樣的

UCDOS

WPS

最像素字體

當(dāng)我在嘗試各種可以免費商用的字體時,我發(fā)現(xiàn)了“文泉驛”(http://wenq.org)這個開源的字體系列鲁驶。里面竟然有一款專為點陣設(shè)計的宋體鉴裹,字號從9像素到12像素,顯示效果非常的不錯钥弯,那么決定就是它了>独蟆(后續(xù)補充,文泉驛為GPL協(xié)議脆霎,商用還是需要作者授權(quán)的总处,所以請大家使用時注意)

輸出的問題

在一般情況下,使用前面提到的 bmfont 這樣的軟件工具睛蛛,以及文泉驛點陣宋體鹦马,已經(jīng)可以解決大多數(shù)需求,只要你使用的游戲引擎支持使用的 bmfont 生成的數(shù)據(jù)文件就可以了忆肾。不過因為我用的是 Consturct 荸频,一個 HTML5 游戲制作軟件,它支持使用的點陣貼圖要求每個字符是同等大小的客冈,但是那些字體貼圖軟件大多數(shù)都不支持生成等寬的字體貼圖旭从,或者是支持生成等寬字體貼圖的軟件有各種缺陷,比如貼圖大小不可控场仲,無法關(guān)掉防鋸齒和悦,不支持太多字符集等。


BMFont 輸出的字體都是不等寬的燎窘,也就是說輸出時要經(jīng)過計算矯正
有人專門做了給 Construct 用的工具摹闽,原先的問題是不支持太大的字符集,現(xiàn)在已經(jīng)修正褐健,現(xiàn)在唯一的問題就是沒有去掉防鋸齒的功能

編寫工具

最后還是得自己動手做工具付鹿,既然在前面我們已經(jīng)研究了這么多,生成一張這樣的貼圖對于做游戲的我們來說就不是什么難事了蚜迅。我現(xiàn)在面臨的選擇就是用什么來做舵匾。本來我是很熟悉 Javascript 這一塊的,但是我所知道的 HTML5 相關(guān)的引擎都很難渲染出小字號的不帶防鋸齒的字體谁不。那么用 Lua 呢坐梯?我以前用過一段時間 Love2D 感覺處理這樣的 2D 像素是比較好的,以前我還用它來制作過處理像素畫的軟件刹帕。但是問題是我沒發(fā)現(xiàn)它能夠渲染沒有防鋸齒的字體吵血,可惜谎替,而且 Love2D 還有一個缺點就是處于安全性的考慮,它只能寫入文件到一個固定的 sandbox 文件夾中蹋辅,這樣做出來的工具使用上比較麻煩些钱贯。

最后,我開始考慮到我可以使用的另外一個腳本式語言:Python侦另。如果這個還不行秩命,我就只能考慮 Haxe 和 C 之類的了。說到 Python褒傅,我會熟悉 Python 主要是因為我使用 Blender 弃锐,使用 Python 可以讓我做一些插件擴展。所以以前我是考核過它的游戲制作能力的殿托。它的最出名的游戲庫就是 Pygame (https://pygame.org
)霹菊,不過這個 Pygame 的確很 Old School,它是個 2D 引擎碌尔,有很多跟像素相關(guān)的功能浇辜,而且很多概念還停留在 Blit 位圖的層面上。不過我仔細看了一下它的最新版的文檔唾戚,發(fā)現(xiàn)它的字體處理應(yīng)該可以滿足我的要求柳洋,因為我明確的看到了它可以關(guān)掉字體的防鋸齒渲染。所以決定就是它了叹坦!拿出 Python 書熊镣,臨時溫習(xí)一下,同時看下 Pygame 的文檔募书,很快我就做出了自己想要的工具绪囱,輸出了合適的位圖。

推薦使用 PyCharm ,用來寫 Python 體驗還是很好的

收尾

在收尾工作中莹捡,我需要處理一些問題:

  • 因為對話中也不免出現(xiàn)英文鬼吵。這個時候會遇到一點小麻煩,因為中文基本都是等寬的篮赢,而英文每個字符有可能是不等寬的齿椅,如果我們按照漢字的方式來顯示每一個英文字母的話,會出現(xiàn)英文字符之間間隔過寬的問題启泣,看起來就是不好看涣脚。不過 construct 是考慮到這個情況的,你只要輸出對應(yīng)需要調(diào)整寬度的字符列表及其寬度就可以了寥茫。


    沒有寬度矯正和有寬度矯正的西文字符的區(qū)別
  • 還有一個比較麻煩的問題就是英字其實是有基線的遣蚀,在我們單獨輸出某一個小寫的英文字母時會失去基線的對齊,幸好 Pygame 里面是可以取得基線的信息的,輸出字母時調(diào)整這個高度即可芭梯。


    所謂的基線就是紅線標的位置

    沒有考慮基線時輸出的西文小寫字符

    按照基線調(diào)整輸出的西文小寫字符

    如果不考慮基線輸出的話险耀,結(jié)果會是這樣
  • 最后,在使用過程中還是出現(xiàn)缺字的情況玖喘。這主要是因為我們使用的某個漢字不在常用漢字列表里面胰耗,這個時候我們只需要在常用漢字表中加入這個字就可以了。其實我碰到的這個字是“哦”字芒涡,顯然人們的漢語表達用語也在不斷的變化中,現(xiàn)在的一些常用口語有可能并不在這個常用漢字表中卖漫。也許以后根據(jù)游戲的結(jié)構(gòu)费尽,我會考慮做一個只按照使用過的漢字生成貼圖的功能。

Python 源碼(依賴 Pygame 模塊):

import pygame                   # Pygame 游戲模塊
from pygame import freetype     # 處理矢量字庫的 Pygame 模塊
import codecs                   # 處理 unicode 所需模塊
import json                     # 輸出 json 格式 所需模塊

# 等寬部分的字符表
fixWCharset = codecs.open( "hz3500.txt","r","utf-8" ).read()     # 讀取3500個常用漢字的表
fixWCharset = fixWCharset + "哦"                               # 加入常用字中沒有的字
# 需要記錄寬度信息的字符表
varWCharset = codecs.open( "ascii.txt","r","utf-8" ).read()      # 加入常用的 ascii 字符 表
varWCharset = varWCharset + "羊始,旱幼。;“”突委、:柏卤?《》"                 # 加入拳腳的漢字標點

gridW     = 14 # 每個字符輸出區(qū)域的寬度
gridH     = 14 # 每個字符輸出區(qū)域的高度
outColNum = 90 # 每行輸出的字符數(shù)
outRowNum = 42 # 一共輸出的行數(shù)
textureW  = gridW * outColNum # 最終輸出的貼圖寬度
textureH  = gridH * outRowNum # 最終輸出的貼圖高度

pygame.init()                                                # 初始化游戲引擎
pygame.display.set_caption("像素點陣漢字生成")                 # 窗口的標題
screen = pygame.display.set_mode( (textureW, textureH) )       # 打開的窗口大小
buffer = pygame.Surface( (textureW,textureH),pygame.SRCALPHA ) # 建立一個透明貼圖大小的緩沖區(qū),貼圖先

# 因為非等寬字體還要需要處理基線的問題匀油,所以同一個字體載入到兩個變量之中缘缚,可以進行不同的設(shè)置
fixWFont = pygame.freetype.Font( 'wenquanyi_9pt.pcf' ) # 等寬字符所用字體
varWFont = pygame.freetype.Font( 'wenquanyi_9pt.pcf' ) # 非等寬字體所用字體

# 關(guān)掉防鋸齒
fixWFont.antialiased = False
varWFont.antialiased = False

varWFont.origin = True # 使用基線方式渲染字體
varWFontSize    = 12   # 非等寬字體的固定輸出為 12 像素
baseLine        = 10   # 設(shè)定從頂部往下 10 個像素為基線

x = 0 # 字符輸出的行坐標
y = 0 # 字符輸出的列坐標
fontColor    = ( 255,255,255 )    # 字體顏色
outlineColor = ( 0,0,0 )          # 描邊顏色

for i in range( 0, len(fixWCharset) ): # 遍歷常用漢字表
    fx   = x * gridW # 字符輸出的像素坐標 x
    fy   = y * gridH # 字符輸出的像素坐標 y
    char = fixWCharset[i]
    # 渲染字符描邊
    fixWFont.render_to( buffer, (fx+1, fy+0), char, outlineColor )
    fixWFont.render_to( buffer, (fx+1, fy+2), char, outlineColor )
    fixWFont.render_to( buffer, (fx+0, fy+1), char, outlineColor )
    fixWFont.render_to( buffer, (fx+2, fy+1), char, outlineColor )
    # 渲染字符
    fixWFont.render_to( buffer, (fx+1, fy+1), char, fontColor )
    # 行列遞增
    x = x + 1
    if (x>=outColNum):
        x = 0
        y = y + 1

widthDict = {} # 記錄寬度的字典
for enIndex in range(0, len(varWCharset)):
    fx = x * gridW # 字符輸出的像素坐標 x
    fy = y * gridH # 字符輸出的像素坐標 y
    char = varWCharset[enIndex]
    # 渲染字符描邊
    varWFont.render_to( buffer, (fx+1, baseLine+fy+0), char, outlineColor, size=varWFontSize )
    varWFont.render_to( buffer, (fx+0, baseLine+fy+1), char, outlineColor, size=varWFontSize )
    varWFont.render_to( buffer, (fx+2, baseLine+fy+1), char, outlineColor, size=varWFontSize )
    varWFont.render_to( buffer, (fx+1, baseLine+fy+2), char, outlineColor, size=varWFontSize )
    # 渲染字符
    varWFont.render_to( buffer, (fx+1, baseLine + fy+1), char, fontColor, size=varWFontSize )
    # 記錄字符寬度
    m     =  varWFont.get_metrics( char, size=varWFontSize )
    lineX = fx + m[0][1]
    charW = m[0][1] + 3
    if not charW in widthDict : widthDict[charW] = []
    widthDict[charW].append( char )
    # 行列遞增
    x = x + 1
    if ( x >= outColNum ):
        x = 0
        y = y + 1

# 輸出 construct 3 所需的寬度 json 文件
outputList = []
for wKey in widthDict:
    charStr = ""
    for char in widthDict[wKey] : charStr = charStr + char
    outputList.append( [wKey,charStr] )
outJson = json.dumps( outputList )
print( "Json String For Construct : " )
print( outJson )
filename = "construct-spriteFont-spaceData.json"
file     = open( filename, "w" )
file.write( outJson )
file.close()
print( "saved to : " + filename )

# 輸出整體字符集文件
charset  = fixWCharset + varWCharset
filename = "charset.txt"
file     = codecs.open( filename, "w", "utf-8" )
file.write( charset )
file.close()
print( "charset saved to : " + filename )

# 保存貼圖文件
filename = "pixel-hz.png"
pygame.image.save( buffer, filename )
print( "texture saved to : " + filename )

# 主循環(huán)
running = True
while running:
    # 在窗口中顯示貼圖
    screen.blit( buffer, (0, 0) )
    pygame.display.update()
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

完成結(jié)果

最終生成的貼圖,我還加入了常用的全角中文標點符號

在 Aseprite 中檢查敌蚜,每個字都加上了黑色描邊

到此桥滨,對于在低分辨率像素游戲中使用點陣漢字的心得分享就這么多,希望對你有所幫助弛车。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末齐媒,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子纷跛,更是在濱河造成了極大的恐慌喻括,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贫奠,死亡現(xiàn)場離奇詭異唬血,居然都是意外死亡,警方通過查閱死者的電腦和手機叮阅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進店門刁品,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人浩姥,你說我怎么就攤上這事挑随。” “怎么了勒叠?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵兜挨,是天一觀的道長膏孟。 經(jīng)常有香客問我,道長拌汇,這世上最難降的妖魔是什么柒桑? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮噪舀,結(jié)果婚禮上魁淳,老公的妹妹穿的比我還像新娘。我一直安慰自己与倡,他們只是感情好界逛,可當(dāng)我...
    茶點故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著纺座,像睡著了一般息拜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上净响,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天少欺,我揣著相機與錄音,去河邊找鬼馋贤。 笑死赞别,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的配乓。 我是一名探鬼主播氯庆,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼扰付!你這毒婦竟也來了堤撵?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤羽莺,失蹤者是張志新(化名)和其女友劉穎实昨,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盐固,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡荒给,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了刁卜。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片志电。...
    茶點故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蛔趴,靈堂內(nèi)的尸體忽然破棺而出挑辆,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布鱼蝉,位于F島的核電站洒嗤,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏魁亦。R本人自食惡果不足惜渔隶,卻給世界環(huán)境...
    茶點故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望洁奈。 院中可真熱鬧间唉,春花似錦、人聲如沸利术。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽氯哮。三九已至,卻和暖如春商佛,著一層夾襖步出監(jiān)牢的瞬間喉钢,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工良姆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留肠虽,地道東北人。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓玛追,卻偏偏與公主長得像税课,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子痊剖,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,066評論 2 355

推薦閱讀更多精彩內(nèi)容

  • pygame圖形接口 使用pygame.image模塊陆馁,可以對圖像進行讀取和保存找颓。 使用pygame.image....
    sssally92閱讀 17,561評論 1 25
  • 概述 關(guān)于電子書的字體選擇,其實是一個很龐大的命題叮贩,講深了涉及字體設(shè)計击狮,這里不扯那么遠,主要是從電子書觀感的角度來...
    hyx108閱讀 9,957評論 10 9
  • 也許由于浪費了太多的時間益老,才會把最后幾天過得格外珍惜彪蓬。年度總結(jié)、年度計劃捺萌,小心翼翼地档冬、仔細斟酌地,不敢落下。 和那...
    Mint閱讀 325評論 0 1
  • 本文由幣乎社區(qū)(bihu.com)內(nèi)容支持計劃獎勵 今天從上海返回北京了捣郊,在高鐵上也聽到朋友說幣圈的一些動態(tài)了辽狈,似...
    七公愛吃雞閱讀 193評論 0 0
  • 文/小妖 下班了刮萌,走出寫字樓的時候才6點10幾分,可天都黑了娘扩,才發(fā)現(xiàn)原來夏天真的悄悄過去了着茸。路上的燈陸陸續(xù)續(xù)的亮起...
    小妖的幺閱讀 853評論 0 0