[利用 Python 快速制作游戏字库](一)基础篇+分割字库图片

来源:岁月联盟 编辑:exp 时间:2011-10-24

 

原始出处: Your life just a MOD

前言

其实早就打算写相关文章的,但因为抽不出时间还是一直没动笔。最近又想起来,然后翻开近半年前的草稿一看,居然一字未动。现在回想起来,当初定的目标似乎太遥远了,所以我打算把原来准备一次写完的内容拆成一小段一小段来写。这样有空就写一小段,似乎更实际一点。

 

在开始读这篇心得前,本人假定你对Python语言已有足够的了解,并已能写一些小程序;清楚游戏字库特别是图片类字库的原理、构造、作用,已做过或有充分准备可以制作一种字库。对于文章中出现的一些库和模块的功能,我会进行一定程度的讲解,更多需要你自行查阅。

 

准备

 

1、你需要安装python 2.7.x(目前我使用2.7.2),可以在这里找到:http://python.org/getit/

 

2、安装PIL(python的一个第三方图像处理库),当前最新版是1.1.7,点此下载,最新版本在此查看:http://www.pythonware.com/products/pil/

 

3、NVIDIA Texture Tools,可以将其他格式图片转换为dds格式,而且效率较高,还支持很多选项。但不单独提供,这是我提取出来的版本。点此下载

 

4、[可选]本人制作的字库生成器,你也可以使用其他类似软件代替,但本篇中提到字库生成器都以此为准。点此下载

 

5、urfFontReader(Urf字库生成器字体读取脚本)。很多例子都会用到,点此下载(记得点download)

 

转换bmp至png格式

 

字库生成的方法参考字库生成器的文章,这里不再详述。

 

 

 

由于字库生成器生成的bmp图片不方便接下来的操作,而且文件相对较大,所以推荐转换为png格式保存。png格式可以包含透明层,体积也很小,而且读取速度不慢。假设生成的图片为“1.bmp”,然后看下面的代码:  

 

import Image #导入PIL的基本处理模块

 

#打开bmp文件并转换为RGBA模式

bmp = Image.open('1.bmp').convert('RGBA')

#用一个列表保存像素数据

rgba = []

#将所有像素用简单公式转换为灰度作为透明层,底图使用白色

for pix in bmp.getdata():

    rgba.append((255, 255 ,255 ,int( pix[0]*0.299 + pix[1]*0.587 + pix[2]*0.114 )))

#保存修改

bmp.putdata(rgba)

bmp.save('1.png')

 

以上代码可以稍加修改,然后将此脚本作为一个命令行工具使用。

 

读取字体数据

 

我提供的urfFontReader可以很方便的读出保存在bin文件中的字体信息。这样就可以将相关数据转换为游戏需要的格式。下面通过分析该脚本的代码,初步了解二进制数据的处理方法。

 

#从struct模块引入必要函数

from struct import pack,unpack

 

def ReadUrfFontInfo(fontname):

    #以二进制模式打开文件并读取

    fl=open(fontname+'.bin','rb')

    #解开文件头中的数据

    img_w,img_h,nums,ch,stp1,stp2=unpack('6i',fl.read(24))

    #解开所有字符数据

    data = unpack('='+'H4i'*nums,fl.read(-1)) #+’='使之采用标准数据大小

    #用一个字典保存字符数据,方便使用

    db={}

    #转换成字典

    for i in xrange(nums):

        c,w,h,x,y = data[i*5:i*5+5]

        db[c]=(w,h,x,y)

    #返回数据

    return (img_w,img_h,nums,ch),db

 

 整个脚本主要使用了struct模块的upack函数来解构二进制数据。因为python并不能直接处理二进制数据(虽然可以当作字符串来处理,当其他数据类型就另当别论了)。upack函数的第一个参数用来描述数据结构,第二个参数是以字符串形式保存的数据。因为我这个字库当初设计得很简单,所以只用到二中类型,分别是:“H”无符号短整数;“i”有符号整数。与C程序中相应类型对应。

 

关于struct模块的更详细信息可以在网上搜索相关例程,或者在python控制台用help函数查看内部文档,之后不再详述。

 

下面是一个从其他脚本调用urfFontReader的实例。

 

#直接导入所需函数

from urfFontReader import ReadUrfFontInfo

#使用该函数读取字体bin文件,返回值分别存入等号前的变量

(img_w,img_h,nums,ch),font_data = ReadUrfFontInfo(r'mydir/myfont')

#打印图片的宽、高,包含的字符数和字符默认高度

print """image : %dx%d

number of chars : %d

general height : %d"""%(img_w,img_h,nums,ch)

#打印所有字符数据(unicode代码、宽、高、坐标x、y)

for char in font_data:

    w,h,x,y=font_data[char]

    print "X"%char,x,y,w,h

 

转换数据为游戏所需格式

 

字库生成器为了通用性,所以设计上是输出部分关键数据,然后根据具体游戏再转换为对应格式。

 

以下以相对较为简单的Chrome4引擎为例。该引擎的字库使用纯文本定义,每个字符需要6个相关参数。具体如下:

 

Char( unicode值, 实际宽度, 左顶点x坐标, 左顶点y坐标, 右下顶点x坐标, 右下顶点y坐标)

 

根据上面的定义,有些数据在读取后可以直接使用,而有些则需要进行计算。因此得到以下代码:

 

from urfFontReader import ReadUrfFontInfo

(img_w,img_h,nums,ch),font_data = ReadUrfFontInfo(r'1')

#创建一个文件用于写入输出文件

fm = open('mid_28.fm','w')

#先写入游戏字库需要的基本信息

fm.write('''Name("mid")

MapWidth(%d)

MapHeight(%d)

FontHeight(%d)

'''%(img_w,img_h,ch))

#遍历所有数据,写入每个字符的定义

for char,(w,h,x,y) in font_data.iteritems():

    fm.write("Char(%d, %d, %d, %d, %d, %d)/n"%(char, w, x, y, x+w, y+ch))

 

由于字典类型是没有顺序的,所以输出的文件会有点乱。但稍加改动就可以按unicode代码的顺序输出数据。

 

 #先将所有项的列表保存到一个变量

data = font_data.items()

#对这个列表排序

data.sort()

#再通过排序后的列表输出

for char,(w,h,x,y) in data:

    fm.write("Char(%d, %d, %d, %d, %d, %d)/n"%(char, w, x, y, x+w, y+ch))

 

以上介绍了一个相对简单的,保存为游戏可用格式的例子。在以后的文章中我会提供更多更复杂的例子。

 

分割字库图片

 

有很多情况需要分割字库图片。比如对于一些老显卡,可能不支持2048x2048以上的纹理大小,某些游戏只支持固定大小纹理等等。分割字库的思路很简单。先打开一个字库,然后将字符挨个复制到新大小的图片中,当装不下时就保存当前图片再新建另外一张图片。借助PIL库,很容易完成这个任务。

 

import Image

from urfFontReader import ReadUrfFontInfo

(img_w,img_h,nums,ch),font_data = ReadUrfFontInfo(r'1')

#设置分割成多大

max_w = 512 #目标宽度

max_h = 512 #目标高度

#打开我们的字库图片

img_in = Image.open('1.png')

 

img_idx = 0 #图片序号/计数器

cx = 0 #在目标图片中的x坐标

cy = 0 #在目标图片中的y坐标

#新建一张图像用于输出

img_out = Image.new("RGBA",(max_w,max_h))

#遍历所有数据进行分割

for char,(w,h,x,y) in font_data.iteritems():

    #如果下一个字符粘贴后超出目标宽度则切换到下一行

    if cx + w > max_w:

        cy += ch #换行

        #如果余下高度不足容纳一个字符则新建另一张图片

        if cy + ch > max_h:

            cy = 0 #y坐标归零

            img_out.save('s_d.png'%img_idx) #保存当前图片

            img_out = Image.new("RGBA",(max_w,max_h)) #新建另一张图片

            img_idx += 1 #图片序号+1

        cx = 0 #x坐标归零

    #从原图复制指定字符到新图片(这里特意分为两步以便看得更清楚,实际只需一步)

    box = img_in.crop((x,y,x+w,y+ch)) #复制原图的指定区域

    img_out.paste(box,(cx,cy)) #粘贴到新图的当前位置

    #这里可以插入保存字符信息的代码

    #…

    #位置向前移动一个字符

    cx += w

#结束后保存未保存的图片

img_out.save('s_d.png'%img_idx)

 

 

 

 

 

这里所用的crop 方法需要提供一个“box”参数,这个参数实际是一个列表,里面需要依序提供矩形的左上顶点和右下顶点的x、y坐标,然后该方法返回一个image对象,里面保存着截取的内容,相当于复制图片中的选定区域。然后再用paste 方法粘贴到指定位置。更多信息请参考PIL文档的imgae模块部分:http://www.pythonware.com/library/pil/handbook/image.htm

 

以上只是最基本的字库图片分割方法。有时实际要求要复杂得多,比如我要每个字符上下左右都留4像素的边要如何做呢?对此,我想我是没义务多做解释的是吧?

 

将图片转换为DDS格式

 

DDS是许多DX游戏常用的纹理格式。NVIDIA Texture Tools 可以将多数格式的图片转换为DDS格式。虽然python也有办法保存DDS格式,但目前的脚本压缩速度都实在太慢。

 

转换DDS主要用到的是nvcompress这个工具。用法如下:

 

$ nvcompress [options] infile [outfile]

 

详细参数表参考“NVIDIA_Texture_Tools_README.txt”。

 

这里介绍一下这个工具与脚本结合的方法。这是一个命令行工具,所以最简单的方法就是直接命令行调用。

 

 

以分割图片的代码为例:

 

import os #导入os库

……

#新建函数SaveDDS

def SaveDDS():

    img_out.save('tmp.png') #保存一个临时文件

    os.system(r'../nvtt/nvcompress.exe -nocuda -nomips -bc3 tmp.png s_d.dds'%img_idx) #通过命令行调用nvcompress,注意路径

    os.remove('tmp.png') #最后删除临时文件

……

#在需要保存图片的位置直接调用这个函数

SaveDDS()

 

这里保存的是一个普通的带透明层的dds图片。而不同游戏又有不同要求,可能需要你多加注意。

 

写在后面的话

 

之所以要使用脚本,其目的主要是为了快速实现我们的想法,方便地开发我们需要的工具。而执行效率并不是我们的主要目的,这点一定要记住。如果能兼顾效率的话当然最好,但如果为了追求效率而减慢了开发速度那就失去了使用脚本的意义了

图片内容