美文网首页
Unity像素中文字体和描边效果制作

Unity像素中文字体和描边效果制作

作者: IT魔幻师 | 来源:发表于2024-05-22 01:12 被阅读0次

    一、字体制作工具下载

    下载安装windows下免费的位图字体制作工具Bitmap Font Generator
    下载地址 http://www.angelcode.com/products/bmfont/

    二、创建字体文件

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

    载入新字符
    Edit->Selecting text from file...
    要提前准备字体文件,需注意txt文件的格式要对应,如果使用的Unicode编码就Font设置的时候就要选中Unicode。


    字体文件
    载入字符

    打开软件->Options -> Font Settings

    Size可以设定字体大小,需要多大就设定多大,这里是12
    Height可以设定字体的拉伸高度,保持默认100%就可以了

    Font Settings

    打开软件->Options -> Export Options

    image.png

    Padding:文字的内边框,或者理解为文字的周边留空要多大 做后期样式时这个属性很重要,需要预留空间来给描边、发光等特效使用 比如我预计我的样式要加一个2px的边框,然后加一个右下角2px的投影效果,所以我设定了padding:2px 4px 4px 2px
    BitDepth:必须32位,否则没有透明层
    Presets:字体初始化的预设的颜色通道设定,也就是说字体的初始颜色设定是什么样的,建议都用白色字,可以直接设定为White text with alpha,即白色字透明底。
    Font descript:字体描述文件,可以使用text或者xml 也就是fnt文件格式
    Textures:纹理图片格式,一般选png。

    最后设置好之后保存,会生成一个.png和一个.fnt两个文件
    option->Save bitmap font as

    三、Unity中生成字体

    生成的png和fnt文件导入Untiy中,创建一个工具:

    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

    四、文字描边脚本

    创建一个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
            }
        }
    }
    

    创建一个材质将Shader挂载在材质球上


    image.png

    创建一个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];
                    // 计算原顶点坐标中心点
                    //
                    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;
                    // 计算原始顶点坐标和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);
                    // 为每个顶点设置新的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);
                    // 应用设置后的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));
            }
        }
    }
    

    五、使用

    创建一个Text挂载制作好的字体和脚本


    image.png

    六、效果

    image.png

    七、描边脚本也可以适用与image

    image.png

    相关文章

      网友评论

          本文标题:Unity像素中文字体和描边效果制作

          本文链接:https://www.haomeiwen.com/subject/xczafjtx.html