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

一、字體制作工具下載

下載安裝windows下免費(fèi)的位圖字體制作工具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è)置的時(shí)候就要選中Unicode。


字體文件

載入字符

打開軟件->Options -> Font Settings

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

Font Settings

打開軟件->Options -> Export Options

image.png

Padding:文字的內(nèi)邊框,或者理解為文字的周邊留空要多大 做后期樣式時(shí)這個(gè)屬性很重要,需要預(yù)留空間來給描邊、發(fā)光等特效使用 比如我預(yù)計(jì)我的樣式要加一個(gè)2px的邊框,然后加一個(gè)右下角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è)置好之后保存,會生成一個(gè).png和一個(gè).fnt兩個(gè)文件
option->Save bitmap font as

三、Unity中生成字體

生成的png和fnt文件導(dǎo)入U(xiǎn)ntiy中,創(chuàng)建一個(gè)工具:

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窗口目錄多出一個(gè)入口 BitmapFontExporter->Create


image.png

引入fnt和png兩個(gè)文件


image.png

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


image.png

四、文字描邊腳本

創(chuàng)建一個(gè)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)建一個(gè)材質(zhì)將Shader掛載在材質(zhì)球上


image.png

創(chuàng)建一個(gè)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];
                // 計(jì)算原頂點(diǎn)坐標(biāo)中心點(diǎn)
                //
                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;
                // 計(jì)算原始頂點(diǎn)坐標(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;
                }

                // 計(jì)算原始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);
                // 為每個(gè)頂點(diǎn)設(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)建一個(gè)Text掛載制作好的字體和腳本


image.png

六、效果

image.png

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

image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容