Unity制作自定义字体的两种方法

Unity支持自定义图片字体(CustomFont),网上有很多教程,细节不尽相同,当概括起来基本就是两种方式。一是使用BMFont,导出图集和.fnt文件,再使用图集在Unity中设置得到字体。二是不用BMFont,使用Unity自带的Sprite类似图集的功能。两种方式原理相同,只是手段有区别。基本原理都是先有一张贴图,比如:

需要知道的信息是贴图中每一个字符对应的ASCII码(例如0的ASCII码为48)与该字符在图集中对应的位置(0为x:0;y:0;w:55;h:76)。然后在Unity中创建材质和CustomFont并根据信息进行设置。

最后得到字体。

两种方式的区别仅在于第一步中如何得到图集的信息。具体的:

对于第一种使用BMFont的方式,目的是得到.fnt文件,实际上是xml格式文件。具体的信息为:

BMFont的使用方法不再详述。得到图集个fnt文件后,网上一般的方法是手动计算在Unity中的参数,有些繁琐,在这里写一个Editor脚本来自动完成这个过程。直接上代码:

using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using UnityEditor;
using UnityEngine;
 
public class CreateFontFromFnt : EditorWindow
{
    [MenuItem("Tools/创建字体(Fnt)")]
    static void DoIt()
    {
        GetWindow<CreateFontFromFnt>("创建字体");
    }
    private string fontName;
    private string fontPath;
    private Texture2D tex;
    private string fntFilePath;
 
    private void OnGUI()
    {
        GUILayout.BeginVertical();
 
        GUILayout.BeginHorizontal();
        GUILayout.Label("字体名称:");
        fontName = EditorGUILayout.TextField(fontName);
        GUILayout.EndHorizontal();
 
        GUILayout.BeginHorizontal();
        GUILayout.Label("字体图片:");
        tex = (Texture2D)EditorGUILayout.ObjectField(tex, typeof(Texture2D), true);
        GUILayout.EndHorizontal();
 
        GUILayout.BeginHorizontal();
        if (GUILayout.Button(string.IsNullOrEmpty(fontPath) ? "选择路径" : fontPath))
        {
            fontPath = EditorUtility.OpenFolderPanel("字体路径", Application.dataPath, "");
            if (string.IsNullOrEmpty(fontPath))
            {
                Debug.Log("取消选择路径");
            }
            else
            {
                fontPath = fontPath.Replace(Application.dataPath, "") + "/";
            }
        }
        GUILayout.EndHorizontal();
 
        GUILayout.BeginHorizontal();
        if (GUILayout.Button(string.IsNullOrEmpty(fntFilePath) ? "选择fnt文件" : fntFilePath))
        {
            fntFilePath = EditorUtility.OpenFilePanelWithFilters("选择fnt文件", Environment.GetFolderPath(Environment.SpecialFolder.Desktop), new string[] { "", "fnt" });
            if (string.IsNullOrEmpty(fntFilePath))
            {
                Debug.Log("取消选择路径");
            }
        }
        GUILayout.EndHorizontal();
 
        GUILayout.BeginHorizontal();
        if (GUILayout.Button("创建"))
        {
            Create();
        }
        GUILayout.EndHorizontal();
 
        GUILayout.EndVertical();
    }
 
    private void Create()
    {
        if (string.IsNullOrEmpty(fntFilePath))
        {
            Debug.LogError("fnt为空");
            return;
        }
        if (tex == null)
        {
            Debug.LogError("字体图片为空");
            return;
        }
 
        string fontSettingPath = fontPath + fontName + ".fontsettings";
        string matPath = fontPath + fontName + ".mat";
        if (File.Exists(Application.dataPath + fontSettingPath))
        {
            Debug.LogErrorFormat("已存在同名字体文件:{0}", fontSettingPath);
            return;
        }
        if (File.Exists(Application.dataPath + matPath))
        {
            Debug.LogErrorFormat("已存在同名字体材质:{0}", matPath);
            return;
        }
        var list = new List<CharacterInfo>();
        XmlDocument xmlDoc = new XmlDocument();
        var content = File.ReadAllText(fntFilePath, System.Text.Encoding.UTF8);
        xmlDoc.LoadXml(content);
        var nodelist = xmlDoc.SelectNodes("font/chars/char");
        foreach (XmlElement item in nodelist)
        {
            CharacterInfo info = new CharacterInfo();
            var id = int.Parse(item.GetAttribute("id"));
            var x = float.Parse(item.GetAttribute("x"));
            var y = float.Parse(item.GetAttribute("y"));
            var width = float.Parse(item.GetAttribute("width"));
            var height = float.Parse(item.GetAttribute("height"));
 
            info.index = id;
            //纹理映射,上下翻转
            info.uvBottomLeft = new Vector2(x / tex.width, 1 - (y + height) / tex.height);
            info.uvBottomRight = new Vector2((x + width) / tex.width, 1 - (y + height) / tex.height);
            info.uvTopLeft = new Vector2(x / tex.width, 1 - y / tex.height);
            info.uvTopRight = new Vector2((x + width) / tex.width, 1 - y / tex.height);
 
            info.minX = 0;
            info.maxX = (int)width;
            info.minY = -(int)height / 2;
            info.maxY = (int)height / 2;
            info.advance = (int)width;
 
            list.Add(info);
        }
 
        Material mat = new Material(Shader.Find("GUI/Text Shader"));
        mat.SetTexture("_MainTex", tex);
        Font m_myFont = new Font();
        m_myFont.material = mat;
        AssetDatabase.CreateAsset(mat, "Assets" + matPath);
        AssetDatabase.CreateAsset(m_myFont, "Assets" + fontSettingPath);
        m_myFont.characterInfo = list.ToArray();
        EditorUtility.SetDirty(m_myFont);
        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();
        Debug.Log("创建成功!");
    }
}
使用起来很简单:

代码也没什么可深究的,目的是代替手动计算,只是在纹理映射的时候有一个小坑。

第二种方式使用Unity中的Sprite。Unity支持把一个Sprite切割成多个。可以用这种方式代替BMFont导出的fnt文件。需要手动做的工作是将图集的TextureType设置为Sprite,然后把SpriteMode设为Multiple,打开SpriteEditor,对图片进行切割。Slice就基本可以完成这个工作,如果需要再手动微调一下。

一张图按照字符的位置分割成了10个Sprite。然后选中每一个Sprite把Name设置成字符对应的ASCII码。这样第一种方法里fnt文件包含的信息基本都包含在这个“图集”里了。同样写一个Editor脚本把这些信息写入到CustomFont里面,并不用手动去创建。

using UnityEngine;
using UnityEditor;
using System.IO;
 
public class CreateFont : EditorWindow
{
    [MenuItem("Tools/创建字体(sprite)")]
    public static void Open()
    {
        GetWindow<CreateFont>("创建字体");
    }
 
    private Texture2D tex;
    private string fontName;
    private string fontPath;
 
    private void OnGUI()
    {
        GUILayout.BeginVertical();
 
        GUILayout.BeginHorizontal();
        GUILayout.Label("字体图片:");
        tex = (Texture2D)EditorGUILayout.ObjectField(tex, typeof(Texture2D), true);
        GUILayout.EndHorizontal();
 
        GUILayout.BeginHorizontal();
        GUILayout.Label("字体名称:");
        fontName = EditorGUILayout.TextField(fontName);
        GUILayout.EndHorizontal();
 
        GUILayout.BeginHorizontal();
        if (GUILayout.Button(string.IsNullOrEmpty(fontPath) ? "选择路径" : fontPath))
        {
            fontPath = EditorUtility.OpenFolderPanel("字体路径", Application.dataPath, "");
            if (string.IsNullOrEmpty(fontPath))
            {
                Debug.Log("取消选择路径");
            }
            else
            {
                fontPath = fontPath.Replace(Application.dataPath, "") + "/";
            }
        }
        GUILayout.EndHorizontal();
 
        GUILayout.BeginHorizontal();
        if (GUILayout.Button("创建"))
        {
            Create();
        }
        GUILayout.EndHorizontal();
 
        GUILayout.EndVertical();
    }
 
    private void Create()
    {
        if (tex == null)
        {
            Debug.LogWarning("创建失败,图片为空!");
            return;
        }
 
        if (string.IsNullOrEmpty(fontPath))
        {
            Debug.LogWarning("字体路径为空!");
            return;
        }
        if (fontName == null)
        {
            Debug.LogWarning("创建失败,字体名称为空!");
            return;
        }
        else
        {
            if (File.Exists(Application.dataPath + fontPath + fontName + ".fontsettings"))
            {
                Debug.LogError("创建失败,已存在同名字体文件");
                return;
            }
            if (File.Exists(Application.dataPath + fontPath + fontName + ".mat"))
            {
                Debug.LogError("创建失败,已存在同名字体材质文件");
                return;
            }
        }
 
        string selectionPath = AssetDatabase.GetAssetPath(tex);
        if (selectionPath.Contains("/Resources/"))
        {
            string selectionExt = Path.GetExtension(selectionPath);
            if (selectionExt.Length == 0)
            {
                Debug.LogError("创建失败!");
                return;
            }
           
            string fontPathName = fontPath + fontName + ".fontsettings";
            string matPathName = fontPath + fontName + ".mat";
            float lineSpace = 0.1f;
            //string loadPath = selectionPath.Remove(selectionPath.Length - selectionExt.Length).Replace("Assets/Resources/", "");
            string loadPath = selectionPath.Replace(selectionExt, "").Substring(selectionPath.IndexOf("/Resources/") + "/Resources/".Length);
            Sprite[] sprites = Resources.LoadAll<Sprite>(loadPath);
            if (sprites.Length > 0)
            {
                Material mat = new Material(Shader.Find("GUI/Text Shader"));
                mat.SetTexture("_MainTex", tex);
                Font m_myFont = new Font();
                m_myFont.material = mat;
                CharacterInfo[] characterInfo = new CharacterInfo[sprites.Length];
                for (int i = 0; i < sprites.Length; i++)
                {
                    if (sprites[i].rect.height > lineSpace)
                    {
                        lineSpace = sprites[i].rect.height;
                    }
                }
                for (int i = 0; i < sprites.Length; i++)
                {
                    Sprite spr = sprites[i];
                    CharacterInfo info = new CharacterInfo();
                    try
                    {
                        info.index = System.Convert.ToInt32(spr.name);
                    }
                    catch
                    {
                        Debug.LogError("创建失败,Sprite名称错误!");
                        return;
                    }
                    Rect rect = spr.rect;
                    float pivot = spr.pivot.y / rect.height - 0.5f;
                    if (pivot > 0)
                    {
                        pivot = -lineSpace / 2 - spr.pivot.y;
                    }
                    else if (pivot < 0)
                    {
                        pivot = -lineSpace / 2 + rect.height - spr.pivot.y;
                    }
                    else
                    {
                        pivot = -lineSpace / 2;
                    }
                    int offsetY = (int)(pivot + (lineSpace - rect.height) / 2);
                    info.uvBottomLeft = new Vector2((float)rect.x / tex.width, (float)(rect.y) / tex.height);
                    info.uvBottomRight = new Vector2((float)(rect.x + rect.width) / tex.width, (float)(rect.y) / tex.height);
                    info.uvTopLeft = new Vector2((float)rect.x / tex.width, (float)(rect.y + rect.height) / tex.height);
                    info.uvTopRight = new Vector2((float)(rect.x + rect.width) / tex.width, (float)(rect.y + rect.height) / tex.height);
                    info.minX = 0;
                    info.minY = -(int)rect.height - offsetY;
                    info.maxX = (int)rect.width;
                    info.maxY = -offsetY;
                    info.advance = (int)rect.width;
                    characterInfo[i] = info;
                }
                AssetDatabase.CreateAsset(mat, "Assets" + matPathName);
                AssetDatabase.CreateAsset(m_myFont, "Assets" + fontPathName);
                m_myFont.characterInfo = characterInfo;
                EditorUtility.SetDirty(m_myFont);
                AssetDatabase.SaveAssets();
                AssetDatabase.Refresh();//刷新资源
                Debug.Log("创建字体成功");
 
            }
            else
            {
                Debug.LogError("图集错误!");
            }
        }
        else
        {
            Debug.LogError("创建失败,选择的图片不在Resources文件夹内!");
        }
    }
}

这个脚本参考了某一篇博文,时间长了实在是找不到了。

原理跟第一种方法一样,只是计算细节略有差异。使用起来还是很简单:

大同小异的两种方法,个人更喜欢用第二种。不需要使用额外的软件,一键搞定,基本上可以丢给美术童鞋来做了。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Unity制作图片字体的方法

    本文实例为大家分享了Unity制作图片字体的具体代码,供大家参考,具体内容如下 主要步骤: 1.新建一个字体. 2.新建一个材质. 3.把上一步新建的材质赋值给第一步创建的字体. 4.设置字体的Character Rects的大小来划分字体. 例如划分这个图片,因其要分为12个字体所以Character Rects大小为12: Character Rects中每个单元格的计算方式如图所标记(比较的复杂,了解怎么计算就行,后期通过编辑器可以计算出来.) 5.把设置好的字体直接赋值给Text中的Fo

  • unity将图片转换成字体的方法

    本文实例为大家分享了unity利用图片来生成字体的具体代码,供大家参考,具体内容如下 开发中,可能会用到需要将图片转换成字体的需求. BMFONT 插件 导入图片 然后生成 .fnt 和 .png 两个文件 (文件格式可以在设置中更改) 将这两个文件导入unity 将png 切割成精灵 创建材质.将贴图拖上去. 创建字体.将材质拖上去. 数据怎么算出来的公式百度上面有,此处略去.也可以利用代码来生成 using UnityEngine; using System.Collections; usi

  • Unity制作自定义字体的两种方法

    Unity支持自定义图片字体(CustomFont),网上有很多教程,细节不尽相同,当概括起来基本就是两种方式.一是使用BMFont,导出图集和.fnt文件,再使用图集在Unity中设置得到字体.二是不用BMFont,使用Unity自带的Sprite类似图集的功能.两种方式原理相同,只是手段有区别.基本原理都是先有一张贴图,比如: 需要知道的信息是贴图中每一个字符对应的ASCII码(例如0的ASCII码为48)与该字符在图集中对应的位置(0为x:0;y:0;w:55;h:76).然后在Unity

  • Matlab制作视频并转换成gif动态图的两种方法

    一.第一个方法比较简单,就是使用movie(f)直接取生成AVI视频文件. %% f(t)-->f(4*t+12) 并且验证%% function Signal_change() tic%记录程序运行时间 figure n = 0; t = -2*pi:0.01:2*pi; y = sin(t);%周期为2*pi y_result = sin(4*t); plot(t,y,'b'); xlabel('t'); ylabel('Amplitude'); n = n+1; F(n) = getfra

  • Python在图片中添加文字的两种方法

    本文主要介绍的是利用Python在图片中添加文字的两种方法,下面分享处理供大家参考学习,下来要看看吧 一.使用OpenCV 在图片中添加文字看上去很简单,但是如果是利用OpenCV来做却很麻烦.OpenCV中并没有使用自定义字体文件的函数,这不仅意味着我们不能使用自己的字体,而且意味着他无法显示中文字符.这还是非常要命的事情.而且他显示出来的文字位置也不太好控制.比如下面的代码,他想做的仅仅是显示数字3: 代码: #coding=utf-8 import cv2 import numpy as

  • Ajax中解析Json的两种方法对比分析

    eval();  //此方法不推荐 JSON.parse();  //推荐方法 一.两种方法的区别 我们先初始化一个json格式的对象: var jsonDate = '{ "name":"周星驰","age":23 }' var jsonObj = eval( '(' + jsonDate + ')' ); // eval();方法 var jsonObj = JSON.parse( jsonDate ); // JSON.parse(); 方

  • 关于Linux反空闲设置的两种方法总结

    为了增强Linux系统的安全性,我们需要在用户输入空闲一段时间后自动断开,这个操作可以由设置TMOUT值来实现.或者使用客户端工具,例如securecrt连接linux服务器,有的会出现过一段时间没有任何操作,客户端与服务器就断开了连接.造成这个的原因,主要是因为客户端与服务器之间存在路由器,防火墙以及为了本身的安全性,在超过特定的时间后就会把空闲连接断开.或者是服务器端设置了断开空闲连接.那么解决的方法有两种,一是从服务器着手,一是在客户端工具上下手. 方式一:设置服务器端 1.echo $T

  • iOS app 右滑返回操作的两种方法

    前提条件,存在A和B两个页面,A是主界面,A push 到 B 方法一:如果B页面的返回按钮要用自定义的按钮(在iOS7中,如果使用了UINavigationController,那么系统自带的附加了一个从屏幕左边缘开始滑动可以实现pop的手势.但是,如果自定义了navigationItem的leftBarButtonItem,那么这个手势就会失效.) 在A界面: - (void)viewDidAppear:(BOOL)animated { self.navigationController.i

  • 详解Swift model 解析的两种方法

    详解Swift model 解析的两种方法 1. 常规解析方法 //懒加载声明一个LJNewsModel为数据的数组 lazy var ljArray : [LJNewsModel] = [LJNewsModel]() //MARK:-- 数据获取和解析 extension NewsViewController{ func requestNetData(){ /* 打印json数据 */ LJDownLoadNetImage.request("GET", url: "http

  • JS获取地址栏参数的两种方法(简单实用)

    js获取地址栏参数的方法有两种:第一种,采用正则表达式获取地址栏参数,第二种,是比较传统的方法,在这小编给大家强烈推荐使用第一种方法,既方便有实用,具体实现过程请看下文详述. 方法一:采用正则表达式获取地址栏参数:( 强烈推荐,既实用又方便!) function GetQueryString(name) { var reg = new RegExp("(^|&)"+ name +"=([^&]*)(&|$)"); var r = window

  • C# web api返回类型设置为json的两种方法

    web api写api接口时默认返回的是把你的对象序列化后以XML形式返回,那么怎样才能让其返回为json呢,下面就介绍两种方法: 方法一:(改配置法) 找到Global.asax文件,在Application_Start()方法中添加一句: 复制代码 代码如下: GlobalConfiguration.Configuration.Formatters.XmlFormatter.SupportedMediaTypes.Clear(); 修改后: 复制代码 代码如下: protected void

随机推荐