当前位置: 首页 > 工具软件 > Font Custom > 使用案例 >

Unity的UGUI中使用CustomFont(BMFont)

殳宸
2023-12-01

        Unity的UGUI的文字渲染效率应该是挺高的,一般来说用默认的Text控件,TTF的Font就满足需求了。不过有时候需要渲染艺术字体的时候还是需要用到BMFont。

一、BMFont的基础使用,创建fnt字体的步骤就不多说了。这里额外提一下就是BMFont是支持命令行的。这里贴一下我使用的脚本,更新字体比较方便。 

        bmfc文件是BMFont的配置文件,使用BMFont的GUI程序设置并保存好对应的字体配置(配置里面包含纹理大小、特殊字符对图片的映射关系等等)

        txt文件是一个纯文本,包含所需要生成字体的所有文字

# -*- coding: utf-8 -*-
import os,sys,shutil,subprocess,glob

global SOURCE_PATH
global TARGET_PATH

SOURCE_PATH = 'font/'
TARGET_PATH = '../Assets/Font'
COMMON_TEXT = "text.txt"
CMD = 'support/BMFont/bmfont.exe'

def genFont(configPath, txtPath):
    fntPath = configPath.replace('.bmfc', '.fnt');
    pngPathOld = configPath.replace('.bmfc', '_0.png');
    pngPathNew = configPath.replace('.bmfc', '.png');

    subprocess.call('"{0}" -c {1} -o {2} -t {3}'.format(CMD, configPath, fntPath, txtPath))
    if os.path.exists(pngPathNew):
        os.remove(pngPathNew);
    os.rename(pngPathOld, pngPathNew);

    fileData = []
    fp = open(fntPath, 'r');
    for line in fp:
        if line.find('file="') != -1:
            fileData.append(line.replace('_0.png', '.png'));
        else:
            fileData.append(line);
    fp.close();
    fpw = open(fntPath, 'w');
    fpw.writelines(fileData);


fileList = glob.glob(SOURCE_PATH + '*.bmfc')
for file in fileList:
    txtFile = file.replace(".bmfc", ".txt")
    if os.path.exists(txtFile):
        genFont(file,  txtFile)
    else:
        genFont(file, COMMON_TEXT)

os.system('PAUSE')



二、Unity中的特殊字体是使用CustomFont。 CustomFont是一个资源,跟ttf字体等价,里面包含了字体的字形信息等数据。之前NGUI有一套脚本可以通过fnt文件创建CustomFont。 我写了一个脚本用来做类似的事情。

using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;

// 创建bmfont
public class CreateFontEditor : Editor
{
    [MenuItem("Assets/CreateBMFont")]
    static void CreateFont()
    {
        Object obj = Selection.activeObject;
        string fntPath = AssetDatabase.GetAssetPath(obj);
        if (fntPath.IndexOf(".fnt") == -1) {
            // 不是字体文件
            return;
        }

        string customFontPath = fntPath.Replace(".fnt", ".fontsettings");
        if (!File.Exists(customFontPath)) {
            return;
        }

        Debug.Log(fntPath);
        StreamReader reader = new StreamReader(new FileStream(fntPath, FileMode.Open));

        List<CharacterInfo> charList = new List<CharacterInfo>();

        Regex reg = new Regex(@"char id=(?<id>\d+)\s+x=(?<x>\d+)\s+y=(?<y>\d+)\s+width=(?<width>\d+)\s+height=(?<height>\d+)\s+xoffset=(?<xoffset>\d+)\s+yoffset=(?<yoffset>\d+)\s+xadvance=(?<xadvance>\d+)\s+");
        string line = reader.ReadLine();
        int lineHeight = 0;
        int texWidth = 1;
        int texHeight = 1;

        while (line != null) {
            if (line.IndexOf("char id=") != -1) {
                Match match = reg.Match(line);
                if (match != Match.Empty) {
                    var id = System.Convert.ToInt32(match.Groups["id"].Value);
                    var x = System.Convert.ToInt32(match.Groups["x"].Value);
                    var y = System.Convert.ToInt32(match.Groups["y"].Value);
                    var width = System.Convert.ToInt32(match.Groups["width"].Value);
                    var height = System.Convert.ToInt32(match.Groups["height"].Value);
                    var xoffset = System.Convert.ToInt32(match.Groups["xoffset"].Value);
                    var yoffset = System.Convert.ToInt32(match.Groups["yoffset"].Value);
                    var xadvance = System.Convert.ToInt32(match.Groups["xadvance"].Value);

                    CharacterInfo info = new CharacterInfo();
                    info.index = id;
                    float uvx = 1f*x/texWidth;
                    float uvy = 1 - (1f*y/texHeight);
                    float uvw = 1f*width/texWidth;
                    float uvh = -1f*height/texHeight;

                    info.uvBottomLeft = new Vector2(uvx, uvy);
                    info.uvBottomRight = new Vector2(uvx + uvw, uvy);
                    info.uvTopLeft = new Vector2(uvx, uvy + uvh);
                    info.uvTopRight = new Vector2(uvx + uvw, uvy + uvh);

                    info.minX = xoffset;
                    info.minY = yoffset + height / 2;   // 这样调出来的效果是ok的,原理未知
                    info.glyphWidth = width;
                    info.glyphHeight = -height; // 同上,不知道为什么要用负的,可能跟unity纹理uv有关
                    info.advance = xadvance;

                    charList.Add(info);
                }
            } else if (line.IndexOf("scaleW=") != -1) {
                Regex reg2 = new Regex(@"common lineHeight=(?<lineHeight>\d+)\s+.*scaleW=(?<scaleW>\d+)\s+scaleH=(?<scaleH>\d+)");
                Match match = reg2.Match(line);
                if (match != Match.Empty) {
                    lineHeight = System.Convert.ToInt32(match.Groups["lineHeight"].Value);
                    texWidth = System.Convert.ToInt32(match.Groups["scaleW"].Value);
                    texHeight = System.Convert.ToInt32(match.Groups["scaleH"].Value);
                }
            }
            line = reader.ReadLine();
        }

        Font customFont = AssetDatabase.LoadAssetAtPath<Font>(customFontPath);
        customFont.characterInfo = charList.ToArray();
        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();
        Debug.Log(customFont);
    }
}

        这里稍微解释一下,最终的Font字体,我是先手动在Unity里面创建一个CustomFont文件,名字跟fnt文件相同,并给它附上相应的Material,Material是一个材质,shader选择 GUI/Text Shader,图片选择BMFont生成的纹理。 这里稍作修改可以支持自动创建字体文件。

        这个脚本干的事情就是读取并解析fnt文件,获取每个字形的坐标,创建CharactorInfo对象并给它附上计算后的uv坐标,最终结果保存早Font里面。需要留意的是unity的uv坐标是左下角为原点。

 类似资料: