Unity像素中文字體和描邊效果制作

一脯倚、字體制作工具下載

下載安裝windows下免費的位圖字體制作工具Bitmap Font Generator
下載地址 http://www.angelcode.com/products/bmfont/

二、創(chuàng)建字體文件

清空字符
Edit->Clear all chars in font

載入新字符
Edit->Selecting text from file...
要提前準(zhǔn)備字體文件围肥,需注意txt文件的格式要對應(yīng),如果使用的Unicode編碼就Font設(shè)置的時候就要選中Unicode著淆。


字體文件

載入字符

打開軟件->Options -> Font Settings

Size可以設(shè)定字體大小末荐,需要多大就設(shè)定多大,這里是12
Height可以設(shè)定字體的拉伸高度嘉栓,保持默認(rèn)100%就可以了

Font Settings

打開軟件->Options -> Export Options

image.png

Padding:文字的內(nèi)邊框,或者理解為文字的周邊留空要多大 做后期樣式時這個屬性很重要拓诸,需要預(yù)留空間來給描邊侵佃、發(fā)光等特效使用 比如我預(yù)計我的樣式要加一個2px的邊框,然后加一個右下角2px的投影效果奠支,所以我設(shè)定了padding:2px 4px 4px 2px
BitDepth:必須32位馋辈,否則沒有透明層
Presets:字體初始化的預(yù)設(shè)的顏色通道設(shè)定,也就是說字體的初始顏色設(shè)定是什么樣的倍谜,建議都用白色字首有,可以直接設(shè)定為White text with alpha,即白色字透明底枢劝。
Font descript:字體描述文件,可以使用text或者xml 也就是fnt文件格式
Textures:紋理圖片格式卜壕,一般選png您旁。

最后設(shè)置好之后保存,會生成一個.png和一個.fnt兩個文件
option->Save bitmap font as

三轴捎、Unity中生成字體

生成的png和fnt文件導(dǎo)入Untiy中鹤盒,創(chuàng)建一個工具:

namespace src.test
{
    using UnityEngine;
    using UnityEditor;
    using System.IO;
    using System.Xml;
    using System;

    public class BitmapFontExporter : ScriptableWizard
    {
        [MenuItem("BitmapFontExporter/Create")]
        private static void CreateFont()
        {
            ScriptableWizard.DisplayWizard<BitmapFontExporter>("Create Font");
        }


        public TextAsset fontFile;
        public Texture2D textureFile;

        private void OnWizardCreate()
        {
            if (fontFile == null || textureFile == null)
            {
                return;
            }

            string path = EditorUtility.SaveFilePanelInProject("Save Font", fontFile.name, "", "");

            if (!string.IsNullOrEmpty(path))
            {
                ResolveFont(path);
            }
        }


        private void ResolveFont(string exportPath)
        {
            if (!fontFile) throw new UnityException(fontFile.name + "is not a valid font-xml file");

            Font font = new Font();

            XmlDocument xml = new XmlDocument();
            xml.LoadXml(fontFile.text);

            XmlNode info = xml.GetElementsByTagName("info")[0];
            XmlNodeList chars = xml.GetElementsByTagName("chars")[0].ChildNodes;

            CharacterInfo[] charInfos = new CharacterInfo[chars.Count];

            for (int cnt = 0; cnt < chars.Count; cnt++)
            {
                XmlNode node = chars[cnt];
                CharacterInfo charInfo = new CharacterInfo();

                charInfo.index = ToInt(node, "id");
                charInfo.width = ToInt(node, "xadvance");
                charInfo.uv = GetUV(node);
                charInfo.vert = GetVert(node);

                charInfos[cnt] = charInfo;
            }


            Shader shader = Shader.Find("Unlit/Transparent");
            Material material = new Material(shader);
            material.mainTexture = textureFile;
            AssetDatabase.CreateAsset(material, exportPath + ".mat");


            font.material = material;
            font.name = info.Attributes.GetNamedItem("face").InnerText;
            font.characterInfo = charInfos;
            AssetDatabase.CreateAsset(font, exportPath + ".fontsettings");
        }


        private Rect GetUV(XmlNode node)
        {
            Rect uv = new Rect();

            uv.x = ToFloat(node, "x") / textureFile.width;
            uv.y = ToFloat(node, "y") / textureFile.height;
            uv.width = ToFloat(node, "width") / textureFile.width;
            uv.height = ToFloat(node, "height") / textureFile.height;
            uv.y = 1f - uv.y - uv.height;

            return uv;
        }


        private Rect GetVert(XmlNode node)
        {
            Rect uv = new Rect();

            uv.x = ToFloat(node, "xoffset");
            uv.y = ToFloat(node, "yoffset");
            uv.width = ToFloat(node, "width");
            uv.height = ToFloat(node, "height");
            uv.y = -uv.y;
            uv.height = -uv.height;

            return uv;
        }


        private int ToInt(XmlNode node, string name)
        {
            return Convert.ToInt32(node.Attributes.GetNamedItem(name).InnerText);
        }


        private float ToFloat(XmlNode node, string name)
        {
            return (float) ToInt(node, name);
        }
    }
}

工具生成后會在Unity窗口目錄多出一個入口 BitmapFontExporter->Create


image.png

引入fnt和png兩個文件


image.png

點擊create后就會生成字體文件了,就可以使用到Text中了


image.png

四侦副、文字描邊腳本

創(chuàng)建一個Shader:

Shader "Custom/OutlineShader"
{
    Properties
    {
        _MainTex ("Main Texture", 2D) = "white" {}
        _Color ("Tint", Color) = (1, 1, 1, 1)
        _OutlineColor ("Outline Color", Color) = (1, 1, 1, 1)
        _OutlineWidth ("Outline Width", Int) = 1

        _StencilComp ("Stencil Comparison", Float) = 8
        _Stencil ("Stencil ID", Float) = 0
        _StencilOp ("Stencil Operation", Float) = 0
        _StencilWriteMask ("Stencil Write Mask", Float) = 255
        _StencilReadMask ("Stencil Read Mask", Float) = 255

        _ColorMask ("Color Mask", Float) = 15

        [Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
    }

    SubShader
    {
        Tags
        {
            "Queue"="Transparent"
            "IgnoreProjector"="True"
            "RenderType"="Transparent"
            "PreviewType"="Plane"
            "CanUseSpriteAtlas"="True"
        }

        Stencil
        {
            Ref [_Stencil]
            Comp [_StencilComp]
            Pass [_StencilOp]
            ReadMask [_StencilReadMask]
            WriteMask [_StencilWriteMask]
        }

        Cull Off
        Lighting Off
        ZWrite Off
        ZTest [unity_GUIZTestMode]
        Blend SrcAlpha OneMinusSrcAlpha
        ColorMask [_ColorMask]

        Pass
        {
            Name "OUTLINE"

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            sampler2D _MainTex;
            fixed4 _Color;
            fixed4 _TextureSampleAdd;
            float4 _MainTex_TexelSize;

            float4 _OutlineColor;
            int _OutlineWidth;

            struct appdata
            {
                float4 vertex : POSITION;
                float2 texcoord : TEXCOORD0;
                float2 texcoord1 : TEXCOORD1;
                float2 texcoord2 : TEXCOORD2;
                fixed4 color : COLOR;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float2 texcoord : TEXCOORD0;
                float2 uvOriginXY : TEXCOORD1;
                float2 uvOriginZW : TEXCOORD2;
                fixed4 color : COLOR;
            };

            v2f vert(appdata IN)
            {
                v2f o;

                o.vertex = UnityObjectToClipPos(IN.vertex);
                o.texcoord = IN.texcoord;
                o.uvOriginXY = IN.texcoord1;
                o.uvOriginZW = IN.texcoord2;
                o.color = IN.color * _Color;

                return o;
            }

            fixed IsInRect(float2 pPos, float2 pClipRectXY, float2 pClipRectZW)
            {
                pPos = step(pClipRectXY, pPos) * step(pPos, pClipRectZW);
                return pPos.x * pPos.y;
            }

            fixed SampleAlpha(int pIndex, v2f IN)
            {
                const fixed sinArray[12] = { 0, 0.5, 0.866, 1, 0.866, 0.5, 0, -0.5, -0.866, -1, -0.866, -0.5 };
                const fixed cosArray[12] = { 1, 0.866, 0.5, 0, -0.5, -0.866, -1, -0.866, -0.5, 0, 0.5, 0.866 };
                float2 pos = IN.texcoord + _MainTex_TexelSize.xy * float2(cosArray[pIndex], sinArray[pIndex]) * _OutlineWidth;
                return IsInRect(pos, IN.uvOriginXY, IN.uvOriginZW) * (tex2D(_MainTex, pos) + _TextureSampleAdd).w * _OutlineColor.w;
            }

            fixed4 frag(v2f IN) : SV_Target
            {
                fixed4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;
                if (_OutlineWidth > 0)
                {
                    color.w *= IsInRect(IN.texcoord, IN.uvOriginXY, IN.uvOriginZW);
                    half4 val = half4(_OutlineColor.x, _OutlineColor.y, _OutlineColor.z, 0);

                    val.w += SampleAlpha(0, IN);
                    val.w += SampleAlpha(1, IN);
                    val.w += SampleAlpha(2, IN);
                    val.w += SampleAlpha(3, IN);
                    val.w += SampleAlpha(4, IN);
                    val.w += SampleAlpha(5, IN);
                    val.w += SampleAlpha(6, IN);
                    val.w += SampleAlpha(7, IN);
                    val.w += SampleAlpha(8, IN);
                    val.w += SampleAlpha(9, IN);
                    val.w += SampleAlpha(10, IN);
                    val.w += SampleAlpha(11, IN);

                    val.w = clamp(val.w, 0, 1);
                    color = (val * (1.0 - color.a)) + (color * color.a);
                }
                return color;
            }
            ENDCG
        }
    }
}

創(chuàng)建一個材質(zhì)將Shader掛載在材質(zhì)球上


image.png

創(chuàng)建一個OutlineEx腳本

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

namespace src.Tool
{
    /// <summary>
    /// UGUI描邊
    /// </summary>
    public class OutlineEx : BaseMeshEffect
    {
        public Color OutlineColor = Color.white;
        [Range(0, 6)] public int OutlineWidth = 0;

        private static List<UIVertex> m_VetexList = new List<UIVertex>();


        protected override void Start()
        {
            base.Start();

            var shader = Shader.Find("Custom/OutlineShader");
            base.graphic.material = new Material(shader);

            var v1 = base.graphic.canvas.additionalShaderChannels;
            var v2 = AdditionalCanvasShaderChannels.TexCoord1;
            if ((v1 & v2) != v2)
            {
                base.graphic.canvas.additionalShaderChannels |= v2;
            }

            v2 = AdditionalCanvasShaderChannels.TexCoord2;
            if ((v1 & v2) != v2)
            {
                base.graphic.canvas.additionalShaderChannels |= v2;
            }

            this._Refresh();
        }


#if UNITY_EDITOR
        protected override void OnValidate()
        {
            base.OnValidate();

            if (base.graphic.material != null)
            {
                this._Refresh();
            }
        }
#endif


        private void _Refresh()
        {
            base.graphic.material.SetColor("_OutlineColor", this.OutlineColor);
            base.graphic.material.SetInt("_OutlineWidth", this.OutlineWidth);
            base.graphic.SetVerticesDirty();
        }


        public override void ModifyMesh(VertexHelper vh)
        {
            vh.GetUIVertexStream(m_VetexList);

            this._ProcessVertices();

            vh.Clear();
            vh.AddUIVertexTriangleStream(m_VetexList);
        }


        private void _ProcessVertices()
        {
            for (int i = 0, count = m_VetexList.Count - 3; i <= count; i += 3)
            {
                var v1 = m_VetexList[i];
                var v2 = m_VetexList[i + 1];
                var v3 = m_VetexList[i + 2];
                // 計算原頂點坐標(biāo)中心點
                //
                var minX = _Min(v1.position.x, v2.position.x, v3.position.x);
                var minY = _Min(v1.position.y, v2.position.y, v3.position.y);
                var maxX = _Max(v1.position.x, v2.position.x, v3.position.x);
                var maxY = _Max(v1.position.y, v2.position.y, v3.position.y);
                var posCenter = new Vector2(minX + maxX, minY + maxY) * 0.5f;
                // 計算原始頂點坐標(biāo)和UV的方向
                //
                Vector2 triX, triY, uvX, uvY;
                Vector2 pos1 = v1.position;
                Vector2 pos2 = v2.position;
                Vector2 pos3 = v3.position;
                if (Mathf.Abs(Vector2.Dot((pos2 - pos1).normalized, Vector2.right))
                    > Mathf.Abs(Vector2.Dot((pos3 - pos2).normalized, Vector2.right)))
                {
                    triX = pos2 - pos1;
                    triY = pos3 - pos2;
                    uvX = v2.uv0 - v1.uv0;
                    uvY = v3.uv0 - v2.uv0;
                }
                else
                {
                    triX = pos3 - pos2;
                    triY = pos2 - pos1;
                    uvX = v3.uv0 - v2.uv0;
                    uvY = v2.uv0 - v1.uv0;
                }

                // 計算原始UV框
                //
                var uvMin = _Min(v1.uv0, v2.uv0, v3.uv0);
                var uvMax = _Max(v1.uv0, v2.uv0, v3.uv0);
                var uvOrigin = new Vector4(uvMin.x, uvMin.y, uvMax.x, uvMax.y);
                // 為每個頂點設(shè)置新的Position和UV侦锯,并傳入原始UV框
                //
                v1 = _SetNewPosAndUV(v1, this.OutlineWidth, posCenter, triX, triY, uvX, uvY, uvOrigin);
                v2 = _SetNewPosAndUV(v2, this.OutlineWidth, posCenter, triX, triY, uvX, uvY, uvOrigin);
                v3 = _SetNewPosAndUV(v3, this.OutlineWidth, posCenter, triX, triY, uvX, uvY, uvOrigin);
                // 應(yīng)用設(shè)置后的UIVertex
                //
                m_VetexList[i] = v1;
                m_VetexList[i + 1] = v2;
                m_VetexList[i + 2] = v3;
            }
        }


        private static UIVertex _SetNewPosAndUV(UIVertex pVertex, int pOutLineWidth,
            Vector2 pPosCenter,
            Vector2 pTriangleX, Vector2 pTriangleY,
            Vector2 pUVX, Vector2 pUVY,
            Vector4 pUVOrigin)
        {
            // Position
            var pos = pVertex.position;
            var posXOffset = pos.x > pPosCenter.x ? pOutLineWidth : -pOutLineWidth;
            var posYOffset = pos.y > pPosCenter.y ? pOutLineWidth : -pOutLineWidth;
            pos.x += posXOffset;
            pos.y += posYOffset;
            pVertex.position = pos;
            // UV
            var uv = pVertex.uv0;
            uv += (Vector4) pUVX / pTriangleX.magnitude * posXOffset * (Vector2.Dot(pTriangleX, Vector2.right) > 0 ? 1 : -1);
            uv += (Vector4) pUVY / pTriangleY.magnitude * posYOffset * (Vector2.Dot(pTriangleY, Vector2.up) > 0 ? 1 : -1);
            pVertex.uv0 = uv;


            // 原始UV框
            pVertex.uv1 = new Vector2(pUVOrigin.x, pUVOrigin.y);
            pVertex.uv2 = new Vector2(pUVOrigin.z, pUVOrigin.w);

            return pVertex;
        }


        private static float _Min(float pA, float pB, float pC)
        {
            return Mathf.Min(Mathf.Min(pA, pB), pC);
        }


        private static float _Max(float pA, float pB, float pC)
        {
            return Mathf.Max(Mathf.Max(pA, pB), pC);
        }


        private static Vector2 _Min(Vector2 pA, Vector2 pB, Vector2 pC)
        {
            return new Vector2(_Min(pA.x, pB.x, pC.x), _Min(pA.y, pB.y, pC.y));
        }


        private static Vector2 _Max(Vector2 pA, Vector2 pB, Vector2 pC)
        {
            return new Vector2(_Max(pA.x, pB.x, pC.x), _Max(pA.y, pB.y, pC.y));
        }
    }
}

五、使用

創(chuàng)建一個Text掛載制作好的字體和腳本


image.png

六秦驯、效果

image.png

七尺碰、描邊腳本也可以適用與image

image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市译隘,隨后出現(xiàn)的幾起案子亲桥,更是在濱河造成了極大的恐慌,老刑警劉巖固耘,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件题篷,死亡現(xiàn)場離奇詭異,居然都是意外死亡厅目,警方通過查閱死者的電腦和手機番枚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進(jìn)店門法严,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人葫笼,你說我怎么就攤上這事深啤。” “怎么了渔欢?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵墓塌,是天一觀的道長。 經(jīng)常有香客問我奥额,道長苫幢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任垫挨,我火速辦了婚禮韩肝,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘九榔。我一直安慰自己哀峻,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布哲泊。 她就那樣靜靜地躺著剩蟀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪切威。 梳的紋絲不亂的頭發(fā)上育特,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天,我揣著相機與錄音先朦,去河邊找鬼缰冤。 笑死,一個胖子當(dāng)著我的面吹牛喳魏,可吹牛的內(nèi)容都是我干的棉浸。 我是一名探鬼主播,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼刺彩,長吁一口氣:“原來是場噩夢啊……” “哼迷郑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起创倔,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤三热,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后三幻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體就漾,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年念搬,在試婚紗的時候發(fā)現(xiàn)自己被綠了抑堡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片摆出。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖首妖,靈堂內(nèi)的尸體忽然破棺而出偎漫,到底是詐尸還是另有隱情,我是刑警寧澤有缆,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布象踊,位于F島的核電站,受9級特大地震影響棚壁,放射性物質(zhì)發(fā)生泄漏杯矩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一袖外、第九天 我趴在偏房一處隱蔽的房頂上張望史隆。 院中可真熱鬧锭部,春花似錦驯耻、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽熔酷。三九已至,卻和暖如春豺裆,著一層夾襖步出監(jiān)牢的瞬間拒秘,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工留储, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人咙轩。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓获讳,卻偏偏與公主長得像,于是被迫代替她去往敵國和親活喊。 傳聞我的和親對象是個殘疾皇子丐膝,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,834評論 2 345

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