TextMeshPro图文混排的两种实现方式,不打图集

TMP图文混排

    • 方案一:TMP自带图文混排
      • 使用方法
        • 打包图集
        • 使用
    • 方案二:不打图集,可以使用任何图片

接到一个需求,TextMeshPro 图文混排。

方案一:TMP自带图文混排

优点布局适应优秀,字体左中右布局位置都很不错
缺点用到的图片需要打包,使用Resources加载

使用方法

打包图集

打包图集这里推荐使用TexturePacker工具,功能强大,完美适配。这个软件也可以用于压缩图集。

TexturePacker链接

1.将你需要的散图或文件夹拖到这个区域,或使用添加精灵按钮添加。

2.选中下图按钮,选择Json(Array), 注意是Array。
下面的图片模式和打包可以自己尝试,主要是控制压缩和图片格式,这里不做介绍。

3.点击发布,获得一个Json文件和一张PNG,Json 是PNG各个图片的位置信息。

4.将上一步的资源导入Unity,然后打开TextMeshPro的图集工具 Sprite Importer。

5.将Json 和PNG 拖入下图中对应的位置。点击生成,然后存储到自己的文件夹

使用

1.将生成的图集拖入TMP的Sprite Asset中,使用图片时,在字符串中插入<sprite=你的图片id>,例如图中“<sprite=0> 图片好看吗”

2. ??? 图片咋这样??? ,发现图片大小不对,被勾选了自动转变为2的n次方。这里改成None ,图片能完整显示了

3. 还是不对,位置偏差好多,按下图2修改X和Y的偏移,最下方的Global Offset 是同时修改所有图片

完成啦,撒花~~

方案二:不打图集,可以使用任何图片

优点当你有大量图片都可能用于图文混排,不方便打包时使用。代码没多少,随便修随便改- -
缺点对布局适配一般,只支持了整个组件的对齐,文字只支持了左对齐

原理就是将含图片的文本拆分成几段文本和图片的组合,计算位置,使用多个TMP和图片拼接
图片暂定格式 [img] 图片ID[/img]

using System.Collections.Generic;
using System.Text.RegularExpressions;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class TextImageView : MonoBehaviour
{
   
    public enum TextImageAlignmentH
    {
        Left,
        Center,
        Right
    }
    public enum TextImageAlignmentV
    {
        Top,
        Center,
        Bottom
    }
    public Transform content;
    public int FontSize;
    public float maxWidth;
    public TextMeshProUGUI tmp;
    public Image img;
    public TextImageAlignmentH alignmentH = TextImageAlignmentH.Center;
    public TextImageAlignmentV alignmentV = TextImageAlignmentV.Center;
    private float _lineHeight;
    private float _averageWidth;
    private float _maxLength;
    private List<KeyValuePair<string, int>> _contentList = new List<KeyValuePair<string, int>>();
    public string text
    {
        set { Test(value); }
    }

    public void Test(string text)
    {
        _contentList = StringToKeyValuePair(text);
        var reduceWidth = 0f;
        var height = 0f;
        tmp.fontSize = FontSize;
        tmp.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, maxWidth);
        tmp.gameObject.SetActive(true);
        tmp.text = "test";
        tmp.ForceMeshUpdate();
        _lineHeight = tmp.textInfo.lineInfo[0].lineHeight;
        tmp.gameObject.SetActive(false);
        
        content.DestroyChildren();
        for (var i = 0; i < _contentList.Count; i++)
        {
            if (_contentList[i].Value == 0)
            {
                var label = TestText(_contentList[i].Key, reduceWidth,height);
                
                for (var i1 = 0; i1 < label.textInfo.lineInfo.Length; i1++)
                {
                    var lineInfo = label.textInfo.lineInfo[i1];
                    var width = lineInfo.length;
                    if(_maxLength < width)
                    {
                        _maxLength = width;
                    }
                    
                    if(i1 == label.textInfo.lineCount - 1)
                    {
                        height = (label.transform.localPosition.y - i1*_lineHeight);
                        reduceWidth = maxWidth - width - 5;
                    }
                }

            }
            else
            {
                
                var imgData = TestImage(_contentList[i].Key, reduceWidth, height);
                reduceWidth = imgData.reduce + 5;
                if (_maxLength < (maxWidth - imgData.reduce))
                {
                    _maxLength = maxWidth - imgData.reduce;
                }
                if(imgData.changeLine)
                {
                    height -= _lineHeight;
                }
            }
            
        }
        height -= _lineHeight;
        var x = 0f;
        switch (alignmentH)
        {
            case TextImageAlignmentH.Left:
                x = 0;
                break;
            case TextImageAlignmentH.Center:
                x = (maxWidth - _maxLength) / 2;
                break;
            case TextImageAlignmentH.Right:
                x = maxWidth - _maxLength;
                break;
        }
        
        var y = 0f;
        switch (alignmentV)
        {
            case TextImageAlignmentV.Top:
                y = _lineHeight;
                break;
            case TextImageAlignmentV.Center:
                y = -height/2 +_lineHeight/2;
                break;
            case TextImageAlignmentV.Bottom:
                y = -height;
                break;
        }
        content.transform.localPosition = new Vector3(x,y,0);
    }
    // <color=#FFFFFF><sprite=1/> 计分时\n<color=#FF0000>+4 </color>倍率</color>
    private List<KeyValuePair<string, int>> StringToKeyValuePair(string text)
    {
        var result = new List<KeyValuePair<string, int>>();

        // 拆分字符串,匹配图片名和文字内容
        var splitText = Regex.Split(text, @"(\[img\]|\[/img\])");

        // 遍历拆分后的字符串
        for (var i = 0; i < splitText.Length; i++)
        {
            switch (splitText[i])
            {
                case "[img]":
                {
                    // 如果是图片名,将它加入结果列表
                    result.Add(new KeyValuePair<string, int>(splitText[i + 1], 1));
                    // 跳过处理下一个字符串
                    i++;
                }
                    continue;
                case "[/img]":
                    // 如果是 [/img] ,跳过处理下一个字符串
                    continue;
                default:
                {
                    var content = splitText[i];
                    if (string.IsNullOrEmpty(content))
                    {
                        continue;
                    }
                    result.Add(new KeyValuePair<string, int>(content, 0));
                }
                    break;
            }
        }

        return result;
    }
 
    public string testValue;
    [ContextMenu("测试")]
    
    public void Test1()
    {
        // TestText(testValue, 300,0);
        Test(testValue);
    }

    private TextMeshProUGUI TestText(string value,float reduceWidth,float height)
    {
        var obj = GameObject.Instantiate((UnityEngine.Object)tmp.gameObject) as GameObject;
        //GameObject obj = null;
        if (obj == null)
        {
            return null;
        }
        obj.SetActive(true);
        if(_averageWidth == 0)
        {
            _averageWidth = obj.GetComponent<TextMeshProUGUI>().GetPreferredValues(value).x/ value.Length;
        }
        var label = obj.GetComponent<TextMeshProUGUI>();
        label.fontSize = FontSize;
        label.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, maxWidth); 
        obj.transform.SetParent(content);
        obj.transform.localScale = Vector3.one;
        if (reduceWidth <= _averageWidth)//剩下的空间不够一个字,直接换行
        {
            obj.transform.localPosition = new Vector3(0, height - _lineHeight, 0);
        }
        else
        {
          
            value = $"<space={maxWidth - reduceWidth}>{value}"; // 创建新字符串
            obj.transform.localPosition = new Vector3(0, height, 0); 
        }
      
        
        value = DelUrlFlag(value);
        
        label.text = value;
        
        label.ForceMeshUpdate();
        return label; // 计算剩余宽度
        
    }
   
    private (bool changeLine,float reduce) TestImage(string value,float reduceWidth,float height)
    {
        
        var obj = GameObject.Instantiate((UnityEngine.Object)img.gameObject) as GameObject;
        if (obj == null)
        {
            return (false,reduceWidth);
        }
        obj.SetActive(true);
        var sp = obj.GetComponent<Image>();
        sp.SetIcon(int.Parse(value));//value是图片的ID,这里替换为自己的设置图片的方法
        var tempHeight = _lineHeight ;
        sp.SetNativeSize();
        var size = sp.rectTransform.sizeDelta;
        var rate = size.y / tempHeight;
        sp.rectTransform.sizeDelta = new Vector2(size.x / rate, tempHeight);
        var needWidth = sp.rectTransform.sizeDelta.x;
        obj.transform.SetParent(content);
        obj.transform.localScale = Vector3.one;
        if (needWidth > reduceWidth)
        {
            obj.transform.localPosition = new Vector3(-maxWidth/2 +needWidth/2, height - _lineHeight, 0);
            return (true,maxWidth -needWidth);
        }
        else
        {
            obj.transform.localPosition = new Vector3(-maxWidth/2 + (maxWidth - reduceWidth)+needWidth/2, height, 0);
            return (false,reduceWidth - needWidth);
        }
        
      
    }
    private string DelUrlFlag(string strTxt)
    {
        strTxt = strTxt.Replace("[/url]", "");
        int nStartLeftBracket = 0;
        int nStartRightBracket = 0;

        while (true)
        {
            nStartLeftBracket = strTxt.IndexOf('[', nStartLeftBracket);
            nStartRightBracket = strTxt.IndexOf(']', nStartRightBracket);

            if (nStartLeftBracket > -1 && nStartRightBracket > -1 && nStartRightBracket > nStartLeftBracket)
            {
                string sub = strTxt.Substring(nStartLeftBracket, nStartRightBracket - nStartLeftBracket + 1);
                if (sub.Contains("url"))
                {
                    strTxt = strTxt.Remove(nStartLeftBracket, nStartRightBracket - nStartLeftBracket + 1);

                    nStartLeftBracket = 0;
                    nStartRightBracket = 0;
                }
                else
                {
                    nStartLeftBracket = nStartRightBracket;
                    nStartRightBracket += 1;
                }
            }
            else
            {
                break;
            }
        }

        return strTxt;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/479425.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python第三次项目作业

打印课堂上图案 判断一个数是否是质数&#xff08;素数&#xff09; 设计一个程序&#xff0c;完成(英雄)商品的购买&#xff08;界面就是第一天打印的界面&#xff09; 展示商品信息(折扣)->输入商品价格->输入购买数量->提示付款 输入付款金额->打印购买小票&a…

【Vue3】走进Pinia,学习Pinia,使用Pinia

&#x1f497;&#x1f497;&#x1f497;欢迎来到我的博客&#xff0c;你将找到有关如何使用技术解决问题的文章&#xff0c;也会找到某个技术的学习路线。无论你是何种职业&#xff0c;我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章&#xff0c;也欢…

骑砍战团MOD开发(49)-使用ScoEditor编辑sco文件制作游戏场景

一.ScoEditor下载霸王•吕布 / ScoEditor GitCodehttps://gitcode.net/qq_35829452/scoeditor二.ScoEditor导出文件种类 mission_objects.json:场景物/出生点/通道等物体 layer_ground_elevation.pfm:场景terrain/ground地形增量,采用PFM深度图存储 ai_mesh.obj:AI网格静态模型…

购买阿里云服务器,有啥优惠吗?

购买阿里云服务器&#xff0c;有啥优惠吗&#xff1f;有的。2024年腾讯云服务器优惠价格表&#xff0c;一张表整理阿里云服务器最新报价&#xff0c;阿里云服务器网整理云服务器ECS和轻量应用服务器详细CPU内存、公网带宽和系统盘详细配置报价单&#xff0c;大家也可以直接移步…

[Linux]互斥锁(什么是锁,为什么需要锁,怎么使用锁(接口),演示代码)

目录 一、锁的概念 一些需要了解的概念 什么是锁&#xff1f;为什么需要锁&#xff1f;什么时候使用锁&#xff1f;怎么定义锁&#xff1f; 二、锁的接口 1.初始化锁 2.加锁 3.申请锁 4.解锁 5.销毁锁 三、实践&#xff08;写代码&#xff09;&#xff1a;黄牛抢票 一…

Matlab有限差分法求解狄利克雷(Dirichlet)边界的泊松(Poisson)问题,边界值为任意值

参考l链接&#xff1a; 有限差分法-二维泊松方程及其Matlab程序实现弹性力学方程 有限差分法matlab,泊松方程的有限差分法的MATLAB实现 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%% Matrix method for Poisson Equation %%%% %%% …

用redis lua脚本实现时间窗分布式限流

需求背景&#xff1a; 限制某sql在30秒内最多只能执行3次 需求分析 微服务分布式部署&#xff0c;既然是分布式限流&#xff0c;首先自然就想到了结合redis的zset数据结构来实现。 分析对zset的操作&#xff0c;有几个步骤&#xff0c;首先&#xff0c;判断zset中符合rangeS…

express+mysql+vue,从零搭建一个商城管理系统15--快递查询(对接快递100)

提示&#xff1a;学习express&#xff0c;搭建管理系统 文章目录 前言一、安装md5&#xff0c;axios二、新建config/logistics.js三、修改routes/order.js四、查询物流信息五、试错与误区总结 前言 需求&#xff1a;主要学习express&#xff0c;所以先写service部分 快递100API…

纹波和噪声有啥区别(一)

首先要知道的是他们都是在电源输出中出现的信号波动&#xff0c;但两者存在明显的区别。 一&#xff0c;纹波的产生 电源纹波是指电源输出时&#xff0c;叠加在稳定的直流电源上的交流成分。 这种波动主要是由于电源自身的开关、PWM 调节等因素引起的&#xff0c;其频率一般…

python的stone音乐播放器的设计与实现flask-django-php-nodejs

该系统利用python语言、MySQL数据库&#xff0c;flask框架&#xff0c;结合目前流行的 B/S架构&#xff0c;将stone音乐播放器的各个方面都集中到数据库中&#xff0c;以便于用户的需要。该系统在确保系统稳定的前提下&#xff0c;能够实现多功能模块的设计和应用。该系统由管理…

Word文档密码设置:Python设置、更改及移除Word文档密码

给Word文档设置打开密码是常见的Word文档加密方式。为Word文档设置打开密码后&#xff0c;在打开该文档时&#xff0c;需要输入密码才能预览及编辑&#xff0c;为Word文档中的信息提供了有力的安全保障。如果我们需要对大量的Word文档进行加密、解密处理&#xff0c;Python是一…

3.C#对接微信Native支付(注册微信支付)

在完成了所有的准备工作之后&#xff0c;我们开始进行实际的对接工作&#xff0c;由于官方没有提供C#版本的SDK我们需要自己手动实现所有的功能&#xff0c;介于再去研究文档太麻烦我们借助第三方的sdk 盛派微信 SDK 它是由苏震巍先生发起的国内知名的 .NET 开源项目。https://…

ZYNQ 自定义AXI接口 IP(PWM)

系统框图 1 FPGA PWM源码 / // Description: pwm model // pwm out period frequency(pwm_out) * (2 ** N) / frequency(clk); // // // Revision History: // Date By Revision Change Description //--------------------------------------…

Vue2(七):超详细vue开发环境搭建(win7),nodejs下载与安装,安装淘宝镜像(报错已解决),配置脚手架

一、安装node.js 本来想粗略写一下的&#xff0c;但是搭建脚手架的时候&#xff0c;遇到了很多问题&#xff0c;浪费快两天时间&#xff0c;记录一下自己的解决办法希望对你们有帮助&#xff01; 1.下载nodejs 安装包下载链接【CNPM Binaries Mirror】 下载我划线的这个&am…

vue学习日记14:工程化开发脚手架Vue CLI

一、概念 二、安装 1.全局安装&查看版本 注意启动cmd输入命令 要以管理员运行哦 安装了一次就行以后不用再创建了 yarn global addvue/cli vue --version 显示了版本号即可 2.创建项目架子 创建项目的路径在哪 项目就在哪 项目名字不能用中文 vue create project-n…

命令提示符——CMD基础操作介绍

&#x1f49e;&#x1f49e; 前言 hello hello~ &#xff0c;这里是大耳朵土土垚~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#x…

初识C++(一)

目录 一、什么是C 二、关键字&#xff1a; 三、命名空间 &#xff1a; 1. C语言存在的问题&#xff1a; 2. namespace关键字&#xff1a; 3. 注意点&#xff1a; 4.使用命名空间分为三种&#xff1a; 四、输入输出&#xff1a; 五、缺省函数&#xff1a; 1. 什么是缺省…

PTA L2-026 小字辈

本题给定一个庞大家族的家谱&#xff0c;要请你给出最小一辈的名单。 输入格式&#xff1a; 输入在第一行给出家族人口总数 N&#xff08;不超过 100 000 的正整数&#xff09; —— 简单起见&#xff0c;我们把家族成员从 1 到 N 编号。随后第二行给出 N 个编号&#xff0c;…

java网络原理(三)----三次握手四次挥手

三次握手 三次握手是建立连接的过程&#xff0c;四次挥手是断开连接的过程&#xff0c;三次握手发生在socket.accept()之前。 客户端和服务器尝试建立连接的时候服务器就会和客户端进行一系列的数据交换称为握手&#xff0c;这个过程建立完了后&#xff0c;连接就好了。 A和B…

2024不起眼的“致富”野路子,不想打工了,做做这些暴利创业项目。2024个人创业做什么项目好;最适合白手起家的创业项目

经济大环境差&#xff0c;并不代表就没有机会。相反&#xff0c;主流经济不好正是另一些人所看重的千载难逢的机会。就像股票市场一样&#xff0c;有人靠做多赚钱&#xff0c;有人靠做空赚钱。下面我们就来分析一下哪些行业会在这个时候崛起。 首先二手行业会迅速崛起&#xff…