- 聲明:轉(zhuǎn)載請鏈接出處郎逃。
- 作者:來自 BITCA.CN 的 Retro Daddy
- 郵箱:harrisyu@qq.com
面臨的問題
像素游戲是獨立游戲的一種常用表現(xiàn)方式谬墙,在制作中文游戲時我們要面臨顯示點陣漢字的問題。當(dāng)前各大游戲引擎中都會有顯示中文的功能苔咪,但顯示出來的中文字體效果一般都差強人意任意乏屯,在低分辨率的像素游戲畫面下會產(chǎn)生一些問題:
- 默認的防鋸齒使得字體跟游戲畫面的整體風(fēng)格不搭;
- 關(guān)掉防鋸齒后病梢,矢量的字庫渲染到低分辨率畫面上字型比較難看垛耳;
- 為了實現(xiàn)在各設(shè)備上的統(tǒng)一效果,可以將字體嵌入到游戲中飘千,但是一個中文字庫動輒十幾M的容量會消耗大量的資源空間,甚至超過游戲本體的容量大小栈雳,對于 HTML5 這樣的平臺更會增加下載時間护奈。
解決思路
- 使用貼圖的形式來顯示漢字,把用到的漢字當(dāng)作圖片存儲和顯示;
- 漢字庫只存放常用漢字厌秒,或者說只存使用到的漢字庫读拆;
- 使用點陣漢字庫,而不是矢量漢字庫鸵闪;
具體的實現(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)生的性能消耗底哥。
字體的選擇
現(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)的。
當(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)掉防鋸齒和悦,不支持太多字符集等。
有人專門做了給 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 的文檔募书,很快我就做出了自己想要的工具绪囱,輸出了合適的位圖。
收尾
在收尾工作中莹捡,我需要處理一些問題:
-
因為對話中也不免出現(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é)果
到此桥滨,對于在低分辨率像素游戲中使用點陣漢字的心得分享就這么多,希望對你有所幫助弛车。