读一读

using MiniJSON;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using TMPro;
using UnityEngine;
using UnityEngine.TextCore.LowLevel;

public class TextMeshFontAssetManager : MonoSingleton<TextMeshFontAssetManager>
{
    private Dictionary<string, TMP_FontAsset> AddFontWithPathList = new Dictionary<string, TMP_FontAsset>();

    [DllImport("__Internal")]
    private static extern string __NT_GetSystemFonts();

    public void AddFontAsset(ScriptableObject fontAsset)
    {
        if (CheckFontAsset(fontAsset)) return;
        var def = TMP_Settings.defaultFontAsset;
        def.fallbackFontAssetTable.Add(fontAsset as TMP_FontAsset);
    }

    public void RemoveFontAsset(ScriptableObject fontAsset)
    {
        if (!CheckFontAsset(fontAsset)) return;
        var def = TMP_Settings.defaultFontAsset;
        def.fallbackFontAssetTable.Remove(fontAsset as TMP_FontAsset);
    }

    public bool CheckFontAsset(ScriptableObject fontAsset)
    {
        TMP_FontAsset font = fontAsset as TMP_FontAsset;
        var def = TMP_Settings.defaultFontAsset;
        return def.fallbackFontAssetTable.Contains(font);
    }

    public void AddWithOSFont(List<string> tb)
    {
        //这里是获取到设备上的字体路径列表 提取名字把需要的动态添加
        Dictionary<string, string> fontPaths = new Dictionary<string, string>();
        IEnumerable tempPaths = null;
#if UNITY_IPHONE && !UNITY_EDITOR
        //IOS的Font.GetPathsToOSFonts在2019.4.7之前获取的为空,所以写了个接口获取,接口内容在Unity/IOS笔记本
        string jsonData = __NT_GetSystemFonts(); 
         if (!string.IsNullOrEmpty(jsonData))
         {
             tempPaths = Json.Deserialize(jsonData) as List<object>;
         }
 #else
        tempPaths = Font.GetPathsToOSFonts();
 #endif

        if (tempPaths == null)
            return;
        
        foreach (string path in tempPaths)
        {
            string key = Path.GetFileNameWithoutExtension(path);
            if(!fontPaths.ContainsKey(key))
                fontPaths.Add(key, path); 
        }

        for (int i = 0; i < tb.Count; i++)
        {
            string fontname = tb[i];
            if (fontPaths.ContainsKey(fontname))
            {
                AddFontAssetByFontPath(fontPaths[fontname]);
            }
        }
    }

    //可以从网上下载字体或获取到本地自带字体
    public void AddFontAssetByFontPath(string fontPath)
    {
        if (AddFontWithPathList.ContainsKey(fontPath))
            return;

        Font font = new Font(fontPath);
        TMP_FontAsset tp_font = TMP_FontAsset.CreateFontAsset(font, 20, 2, GlyphRenderMode.SDFAA, 512, 512);
        AddFontAsset(tp_font);
        AddFontWithPathList.Add(fontPath, tp_font);
    }

    public void RemoveFontAssetByFontPath(string fontPath)
    {
        if (!AddFontWithPathList.ContainsKey(fontPath))
            return;

        TMP_FontAsset tp_font = AddFontWithPathList[fontPath];
        RemoveFontAsset(tp_font);
    }

    public int GetSystemLangeuage()
    {
        return (int)Application.systemLanguage;
    }
}



先制作一张包含所有表情的图集,导入Unity,修改图片类型和模式,然后编辑图片,切割为一个一个Sprite表情(可以使用工具自动完成,TexturePacker打包图集和TexturePacker Importer插件将打包的图集转化)

image.png


然后右键大的Sprite,创建出Sprite Asset

image.png


最后,打开TMP Settings,将创建的Sprite Asset设置为默认的表情

image.png


使用:

打开Sprite Asset,可以在SpriteCharacterTable看到一个个的表情和对应的Index

使用的时候输入<sprite=Index>就可以



function M:SetLimitByteLength(length)
    self.limitLength = length + 1 --不知道为啥 不加1就不正确
    self:RemoveOnValidateInput()

    --self.unity_uitextmeshinput 为Input组件
    self.__onValidateInput = handler(self,self.OnValidateInput)
    self.unity_uitextmeshinput.onValidateInput = self.__onValidateInput
end

--lua去长度为字节长度
function M:OnValidateInput(text,charIndex,c)
     if (#(text .. c) > self.limitLength ) then
        return '\0' --大于的时候返回空字符 此时会输入无效了
     else
        return c
     end
end

function M:RemoveOnValidateInput()
    if self.__onValidateInput ~= nil then
        self.unity_uitextmeshinput.onValidateInput = nil
        self.__onValidateInput = nil
    end
end



image.png


ContentSizeFitter自适应内容

Horizontal Fit和Vertical Fit如果两个都选择PreferredSize将会优先水平方向的,也就是随着内容的增加,只会修改width属性使用内容。

如果要使用水平方向的,可以固定width宽度,设置Horizontal Fit为Unconstrained、Vertical Fit为PreferredSize,当内容这一个宽度不够放时就会向下扩展修改height了。


背景和文本结构:

image.png


背景MsgImage组件(使用九宫格):

image.png


文本MsgText组件(程序主要对这个组件进行操作):

image.png


结构设置完效果:

image.png


程序控制适应方向:

--item.txt_msg 文本组件
--item.fitter_msg  文本的Content Size Fitter组件
--self.MaxPreferredWidth 一行文本允许的最大宽度
--self.PreferredHeight  一行文本的高度

function M:SetItemText(item,content)
    self:ResetItem(item) --假设水平够用
    item.txt_msg:SetText(content)
    if(item.txt_msg:GetPreferredWidth() > self.MaxPreferredWidth) then --水平宽度超过了允许的最大宽度
        item.txt_msg:SetSizeDelta(Vector2(self.MaxPreferredWidth,0)) --设定固定宽度,垂直适应
        item.fitter_msg:SetFitVertical()

        local allTextHight = item.txt_msg:GetPreferredHeight() --获取到文本的总高度,对列表的item高度设置有用
    end
end

function M:ResetItem(item)
    item.fitter_msg:SetFitHorizontal()
    item.txt_msg:SetSizeDelta(Vector2(0,self.PreferredHeight)) --固定一行文本高度,水平适应
end


总结:

1.对文本组件挂载ContentSizeFitter,通过PreferredWidth、PreferredHeight来获取宽高

2.如果文本的PreferredWidth超过了最大设置宽度,则使用固定宽度使用垂直适应,如果没有,使用基础高度水平适应

2.背景使用计算的方式设置宽高


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

public class CaptureComponent : MonoBehaviour
{
    private Camera _uguiCamera;
    private RectTransform rectTransform;

    private string basePath;
    private void Awake()
    {
        _uguiCamera = GameObject.FindGameObjectWithTag("GuiCamera").GetComponent<Camera>();
        rectTransform = this.GetComponent<RectTransform>();
        basePath = Application.persistentDataPath + "/share/";
        if (!Directory.Exists(basePath)) Directory.CreateDirectory(basePath);
    }

    public void CaptureScreenByRect(Action<string> cb,string name="")
    {
        if (string.IsNullOrEmpty(name))
        {
            name = basePath + UnityExtends.Calculationmd5(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"))+".png";
        }
        StartCoroutine(CaptureScreenAsync(name, cb));
    }

    public IEnumerator CaptureScreenAsync(string path, Action<string> cb)
    {
        yield return new WaitForEndOfFrame();
        try
        {
            if (path != "")
            {
                Rect rect = rectTransform.rect;
                Vector3 startWorldPos = rectTransform.TransformPoint(rect.x, rect.y, 0);
                Vector3 endWorldPos = rectTransform.TransformPoint(rect.x + rect.width, rect.y + rect.height,0);
                startWorldPos.z = 0;
                endWorldPos.z = 0;
                Vector3 startViewPos = _uguiCamera.WorldToViewportPoint(startWorldPos);
                Vector3 endViewPos = _uguiCamera.WorldToViewportPoint(endWorldPos);
                rect.x = Screen.width * startViewPos.x;
                rect.y = Screen.height * startViewPos.y;
                rect.width = Screen.width * (endViewPos.x - startViewPos.x);
                rect.height = Screen.height * (endViewPos.y - startViewPos.y);

                if (rect.x < 0 || rect.y < 0 || (rect.x + rect.width) > Screen.width || (rect.y + rect.height) > Screen.height)
                {
                    //读取屏幕像素越界,界面不要超过屏幕显示区域
                    Debug.Log("窗体超过屏幕");
                    cb?.Invoke("");
                }
                else
                {
                    //Debug.Log(rect.width + " " + rect.height);
                    Texture2D capture = new Texture2D((int)(rect.width), (int)(rect.height), TextureFormat.RGB24, false);

                    capture.ReadPixels(rect, 0, 0);
                    capture.Apply();

                    byte[] datas = capture.EncodeToPNG();
                    File.WriteAllBytes(path, datas);
                    if (cb != null)
                    {
                        Debug.Log(path);
                        cb(path);
                    }
                }
            }
            else
                cb?.Invoke("");

        }
        catch (Exception e)
        {
            Debug.LogError("CaptureScreenByRect:" + e.Message);
        }

    }
}



Button btn = Images[k].GetComponent<Button>();
if (btn != null)
{
    if (btn.transition == Selectable.Transition.SpriteSwap)
    {
        int counter = 0;
        SpriteState state = btn.spriteState;//通过这个属性获取,是一个结构体
        if (state.highlightedSprite == a)
        {
            counter++;
            state.highlightedSprite = b;
        }

        if (state.pressedSprite == a)
        {
            counter++;
            state.pressedSprite = b;
        }

        if (state.selectedSprite == a)
        {
            counter++;
            state.selectedSprite = b;
        }

        if (state.disabledSprite == a)
        {
            counter++;
            state.disabledSprite = b;
        }

        if (counter > 0)
        {
            btn.spriteState = state;
        }
    }
}

首先在TextMeshPro的文字显示中标记为<link ID=内容 等下脚本获取到的>展示内容</link>


新建脚本挂载到有这个TextMeshPro组件的物体上

using UnityEngine;
using TMPro;
using UnityEngine.EventSystems;
using System;

[RequireComponent(typeof(TextMeshProUGUI))]
public class TextMeshProLinkClick :MonoBehaviour, IPointerClickHandler
{
    private TextMeshProUGUI _text;
    private Camera _uguiCamera;
    private Action<string> _action;
    private void Awake()
    {
        _text = this.GetComponent<TextMeshProUGUI>();
        _uguiCamera = GameObject.FindGameObjectWithTag("GuiCamera").GetComponent<Camera>();
    }

    public void OnPointerClick(PointerEventData eventData)
    {
        Vector3 pos = new Vector3(eventData.position.x, eventData.position.y, 0);
        int linkIndex = TMP_TextUtilities.FindIntersectingLink(_text, pos, _uguiCamera);
        if (linkIndex > -1)
        {
            TMP_LinkInfo linkInfo = _text.textInfo.linkInfo[linkIndex];
            if (this._action != null)
            {
                //linkInfo.GetLinkID() 获取的就是ID属性的内容
                this._action(linkInfo.GetLinkID());
            }
        }
    }

    public void SetOnClick(Action<string> callback)
    {
        _action = callback;
    }

    public void RemoveOnClick()
    {
        _action = null;
    }

}

标签内容如果一直到下一个标签且没有镶嵌的不需要关闭标签


1.文字排版

<align="right">Right
<align="center">Center
<align="left">Left


2.颜色

<color="red">Red <color=#005500>Dark Green <#0000FF>Blue <color=#FF000088>Semitransparent Red

<alpha=#FF>FF <alpha=#CC>CC


3.粗体和斜体

The <i>quick brown fox</i> jumps over the <b>lazy dog</b>.


4.字体间距

<cspace=1em>Spacing</cspace>


5.字体

<font="NotoSans" material="NotoSans Outline">a different material?


6.行高

<line-height=50%>Line height at 50%


7.小大写、首字母大写

<lowercase>Alice and Bob watched TV.</lowercase>
<uppercase>Alice and Bob watched TV.</uppercase>
<smallcaps>Alice and Bob watched TV.</smallcaps>


8.标记背景色透明度

<mark=#ffff00aa>can be marked with</mark>


9.不转义标签

<noparse><b></noparse>


10.水平定位

at <pos=75%>75%
at <pos=25%>25%
at <pos=50%>50%


11.字体大小

<size=100%>Echo</size>


12.图片精灵

<sprite name="Default Sprite Asset_4" color=#55FF55FF>


13.删除线和下划线

The <s>quick brown</s> fox jumps over <u>the lazy dog</u>.


14.上下标注

We have 1m<sup>3</sup> of H<sub>2</sub>O.


15.超链接

<link="ID"></link>


Sprite b;
Image image;
if (b.border.w > 0 || b.border.x > 0 || b.border.y > 0 || b.border.z > 0)//只要有一条边有拖动过,认为有九宫图设置
{
    image.type = Image.Type.Sliced;
}
else
{
    image.type = Image.Type.Simple;
}
image.sprite = b;