IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> 【游戏开发创新】Unity狗屁不通文章生成器阐述点赞的意义,可生成文字长图保存到本地(Unity | 附源码 | Text转Texture长图 | 详细教程) -> 正文阅读

[游戏开发]【游戏开发创新】Unity狗屁不通文章生成器阐述点赞的意义,可生成文字长图保存到本地(Unity | 附源码 | Text转Texture长图 | 详细教程)

请添加图片描述
请添加图片描述
请添加图片描述

一、前言

嗨,大家好,我是新发。
我在GitHub上看到了一个名字叫BullshitGenerator(狗屁不通文章生成器)的项目,地址:https://github.com/menzi11/BullshitGenerator
有接近1.5k的星星,我下载下来玩了一下,有点意思,不过它是使用PythonJavaScript写的,嘛,那我来做一个Unity版的吧,顺便把文字生成长图的功能也做一下~

二、流程概览

首先,我们要测试一下BullshitGenerator的功能,看它做了什么;
接着,查看BullshitGenerator的源码,看它是怎么做的;
最后,我们使用Unity来实现,并进行拓展,比如生成文字长图。

在这里插入图片描述

三、BullshitGenerator项目测试

1、项目下载

BullshitGenerator项目地址:https://github.com/menzi11/BullshitGenerator

在这里插入图片描述
下载下来后,可以看到它提供了两个版本的实现:PythonJavaScript
在这里插入图片描述

2、测试JavaScript版

我们先来玩一下JavaScript版的,使用浏览器打开index.html
在这里插入图片描述
在主题输入框中输入文章主题,点击生成按钮,它就可以生成一篇狗屁不通的文章,
在这里插入图片描述

3、测试Python版

现在我们来玩下python版的,使用python执行这个自动狗屁不通文章生成器.py
在这里插入图片描述
输入文章主题,按回车,即可生成一篇狗屁不通的文章,
在这里插入图片描述

四、BullshitGenerator源码分析

1、JavaScript版源码分析

我们使用文本编辑器打开index.html
在这里插入图片描述

1.1、定义变量(配置表)

它首先定义了几个列表,其实就是配置表,

注:作者使用中文做为变量名函数名,一开始看有点不大习惯~

let 论述 = [ 
	"现在,解决主题的问题,是非常非常重要的。 所以, ",
	...
	]
let 名人名言 = [
	"伏尔泰曾经说过,不经巨大的困难,不会有伟大的事业。这不禁令我深思",
	...
	]
let 后面垫话 = [
	"这不禁令我深思。 ",
	...
	]
let 前面垫话 = [
	"曾经说过",
	...
	]

接着封装一些必要的函数,我来一一讲解一下~

1.2、随便取一句

使用一个Math.random函数生成一个0~1的随机数,乘以列表长度,再使用Math.floor函数向下取整,得到一个列表的随机索引,最后根据索引从列表中取值。

function 随便取一句(列表){
    let 坐标 = Math.floor( Math.random() * 列表.length );
    return 列表[坐标];
}
1.3、随便取一个数

从最小值和最大值之间随机取一个数,这个是用来做概率随机的,

function 随便取一个数(最小值 = 0,最大值 = 100){
    let 数字 = Math.random()*( 最大值 - 最小值 ) + 最小值;
    return 数字;
}
1.4、来点名人名言

先从名人名言列表中随机取一句,然后把这句话中的关键字“曾经说过”替换为前面垫话列表的随机一句,再把关键字“这不禁令我深思”替换为后面垫话列表的随机一句话,

function 来点名人名言(){
    let 名言 = 随便取一句(名人名言)
    名言 = 名言.replace("曾经说过", 随便取一句(前面垫话) )
    名言 = 名言.replace("这不禁令我深思", 随便取一句(后面垫话) )
    return 名言
}

画成图:
在这里插入图片描述

1.5、来点论述

论述列表中随机取一句,把关键字"主题"替换为我们的主题变量,注意这里用到了正则表达式,RegExp("主题", "g")是一个正则表达式,g表示全局匹配,即把句子中所有的关键字"主题"都进行匹配,比如我们取到一句论述:"主题的发生,到底需要如何做到,不主题的发生,又会如何产生。 ",这句论述中是有两个"主题"关键字的,我们将其替换为我们的主题变量,假设我们的主题变量为:"一天掉多少根头发",那么替换后的句子就是:"一天掉多少根头发的发生,到底需要如何做到,不一天掉多少根头发的发生,又会如何产生。 "

function 来点论述(){
    let 句子 = 随便取一句(论述);
    句子 = 句子.replace(RegExp("主题", "g"),主题);
    return 句子;
}

画个图:
在这里插入图片描述

1.6、增加段落

检查当前章节的文本的最后是否是一个空格,如果是,把章节的最后两个字符去掉:章节.slice(0,-2),为什么要这样做呢?因为配置的论述后面垫话的末尾都是一个标点符号然后留一个空格,比如:"每个人都不得不面对这些问题。 在面对这种问题时, ",如果在以此句作为章节的最后一句,则需要把,(空格)替换为句号,章节的开头要空两个空格:"  " + 章节 + "。 "

function 增加段落(章节){
    if(章节[章节.length-1] === " "){
        章节 = 章节.slice(0,-2)
    }
    return "  " + 章节 + "。 "
}

画个图:
在这里插入图片描述

1.7、生成文章

获取input的输入作为主体变量,
文章文字长度小于6000字的情况下一直循环执行:
5%的概率且文章字数大于200字时,生成新的章节;
15%的概率随机一句名人名言;
80%的概率随机一句论述;
每个章节都塞入到文章中;
最后显示文章文本到浏览器中。

function 生成文章(){
    主题 = $('input').value
    let 文章 = []
    for(letin 主题){
        let 章节 = "";
        let 章节长度 = 0;
        while( 章节长度 < 6000 ){
            let 随机数 = 随便取一个数();
            if(随机数 < 5 && 章节.length > 200){
                章节 = 增加段落(章节);
                文章.push(章节); 
                章节 = "";
            }else if(随机数 < 20){
                let 句子 = 来点名人名言();
                章节长度 = 章节长度 + 句子.length;
                章节 = 章节 + 句子;
            }else{
                let 句子 = 来点论述();
                章节长度 = 章节长度 + 句子.length;
                章节 = 章节 + 句子;
            }
        }
        章节 = 增加段落(章节);
        文章.push(章节);
    }
    let 排版 = "<div>" + 文章.join("</div><div>") + "</div>";
    $("#论文").innerHTML = 排版;
}

画个图:
在这里插入图片描述

到这里,JavaScript的源码我们就分析完了,其实整个逻辑很直接,不饶弯子,相信大家看一看就能看懂。下面我们再来看看Python版的源码吧~

2、Python版源码分析

我们使用文本编辑器打开自动狗屁不通文章生成器.py
在这里插入图片描述

2.1、读取配置

可以看到,开头是使用readJSON模块来读取data.json配置,存到变量中,

import os, re
import random,readJSON

data = readJSON.读JSON文件("data.json")
名人名言 = data["famous"] # a 代表前面垫话,b代表后面垫话
前面垫话 = data["before"] # 在名人名言前面弄点废话
后面垫话 = data['after']  # 在名人名言后面弄点废话
废话 = data['bosh'] # 代表文章主要废话来源

我们可以打开data.json看看,
在这里插入图片描述

格式是这样的:

{
    "title":"主题",
    "famous":[
		"爱迪生a,天才是百分之一的勤奋加百分之九十九的汗水。b",
		"查尔斯·史a,一个人几乎可以在任何他怀有无限热忱的事情上成功。b",
		...
	],	
    "bosh":[
		"现在,解决x的问题,是非常非常重要的。所以,",
		"我们不得不面对一个非常尴尬的事实,那就是,",
		...
	],		
    "before":[
		"这不禁令我深思。",
		"带着这句话,我们还要更加慎重的审视这个问题:",
		...
	],	
    "after":[
		"曾经说过",
		"在不经意间这样说过",
		...
	],		
}

我们再看回readJSON.py脚本,只有一个函数,就是先判断一下配置文件是不是.json结尾的,然后open配置文件,把文件内容read进来,最后json.loads把配置的文本(json格式的字符串)转为python的字典并return

def 读JSON文件(fileName=""):
    import json
    if fileName!='':
        strList = fileName.split(".")
        if strList[len(strList)-1].lower() == "json":
            with open(fileName,mode='r',encoding="utf-8") as file:
                return json.loads(file.read())

画个图:
在这里插入图片描述

2.2、对废话和名人名言进行洗牌

定义一个洗牌方法,然后对废话和名人名言进行洗牌,并返回迭代器,方便后续使用迭代器进行next操作,

重复度 = 2

def 洗牌遍历(列表):
    global 重复度
    池 = list(列表) * 重复度
    while True:
        random.shuffle()
        for 元素 in:
            yield 元素

下一句废话 = 洗牌遍历(废话)
下一句名人名言 = 洗牌遍历(名人名言)

注意,上面首先是把列表进行了2次重复,再进行洗牌。
例:

a = [1,2,3]
b = list(a) * 2
print(b)
# 输出[1, 2, 3, 1, 2, 3]

另外,上面用到了random.shuffle函数,它是将序列的所有元素随机排序(即洗牌)。

2.3、来点名人名言

使用next取出迭代器的下一个项,即下一句名人名言,然后对名言中的关键字"a""b"做替换:
"a"替换为随机一句前面垫话
"b"替换为随机一句后面垫话

def 来点名人名言():
    global 下一句名人名言
    xx = next(下一句名人名言)
    xx = xx.replace("a", random.choice(前面垫话))
    xx = xx.replace("b", random.choice(后面垫话))
    return xx

注意,data.json配置表中的名人名(famous)言格式为:
"名人a,名言。b"
例:

"爱迪生a,天才是百分之一的勤奋加百分之九十九的汗水。b",
"查尔斯·史a,一个人几乎可以在任何他怀有无限热忱的事情上成功。b",
"培根说过,深窥自己的心,而后发觉一切的奇迹在你自己。b",
...
2.4、另起一段

另起一段就是给文章追加句号.,换行"\r\n",新段落开头空四个空格,

def 另起一段():
    xx = ". "
    xx += "\r\n"
    xx += "    "
    return xx
2.5、main入口:生成文章

提示输入文章主题,当文章字数小于1000字时循环执行:5%的概率另起一段,15%的概率随机一句名人名言,80%的概率随机一句废话。
最后替换关键字"x"为主题,输出文章内容。

if __name__ == "__main__":
    xx = input("请输入文章主题:")
    for x in xx:
        tmp = str()
        while ( len(tmp) < 1000 ) :
            分支 = random.randint(0,100)
            if 分支 < 5:
                tmp += 另起一段()
            elif 分支 < 20 :
                tmp += 来点名人名言()
            else:
                tmp += next(下一句废话)
        tmp = tmp.replace("x",xx)
        print(tmp)

画个图:
在这里插入图片描述

五、界面设计

好了,现在我们开始动手制作Unity版本的狗屁不通文章生成器吧~
我们先使用axure快速原型设计工具先简单设计一下界面,
在这里插入图片描述

六、UI素材获取

简单的UI素材资源我是在阿里巴巴矢量图库上找,地址:https://www.iconfont.cn/
比如搜索按钮,
在这里插入图片描述
找一个形状合适的,可以进行调色,我一般是调成白色,
在这里插入图片描述
因为Unity中可以设置Color,这样我们只需要一个白色按钮就可以在Unity中创建不同颜色的按钮了。
弄点基础的美术资源,
在这里插入图片描述

七、创建Unity工程

创建一个2D模板的Unity工程,工程名我定为UnityBullshitGenerator,如下:
在这里插入图片描述
我想做成竖版的,Game视图分辨率设置为720*1280
在这里插入图片描述

八、制作界面预设:MainPanel.prefab

把上面我们获取的UI素材导入到Unity工程中,放在Assets / Textures目录中,如下:
在这里插入图片描述
注意图片类型设置为Sprite (2D and UI)
在这里插入图片描述
对部分特定的UI素材使用Sprite Editor进行九宫格切割,
在这里插入图片描述
接着,使用UGUI制作界面预设:MainPanel.prefab,保存到Assets /Prefabs目录中,
在这里插入图片描述
预设节点结构如下:
在这里插入图片描述
预设显示如下:
在这里插入图片描述

九、配置表:data.json

创建data.json,内容格式如下,可自行往配置中添加句子(注意有些句子是带可替换的字符的,比如a、b、x

{
    "title":"主题",
    "famous":[
		"爱迪生a,天才是百分之一的勤奋加百分之九十九的汗水。b",
		"查尔斯·史a,一个人几乎可以在任何他怀有无限热忱的事情上成功。b",
		...
	],	
    "bosh":[
		"现在,解决x的问题,是非常非常重要的。所以,",
		"我们不得不面对一个非常尴尬的事实,那就是,",
		...
	],		
    "before":[
		"这不禁令我深思。",
		"带着这句话,我们还要更加慎重的审视这个问题:",
		...
	],	
    "after":[
		"曾经说过",
		"在不经意间这样说过",
		...
	],		
}

data.json保存到Unity工程的Assets/Resources目录中:
在这里插入图片描述

十、程序代码

程序部分,分为LogicViewLogic实现逻辑,View实现界面交互。
在这里插入图片描述
BullshitGenerator.csMainPanel.cs都很好写,难点是Utils.cs:文字如何转Texture2D(文字长图)。不要怕,稍作研究就可以做出来滴,我们先把简单的做了~

1、BullshitGenerator.cs脚本

Assets / Scripts / Logic目录中创建BullshitGenerator.cs脚本,
在这里插入图片描述

BullshitGenerator.cs脚本要具体做什么呢?首先画一下思维导图:
在这里插入图片描述

1.1、使用LitJson解析配置

我们要解析json格式的配置表,需要使用Json库,我推荐使用LitJson开源库,可以从GitHub上下载,LitJson开源项目地址:https://hub.fastgit.org/LitJSON/litjson
在这里插入图片描述
我们下载下来后,把src目录中的LitJson文件夹整个拷贝到我们Unity工程中,如下:
在这里插入图片描述

接着我们就可以在代码中使用LitJson了,使用时引用命名空间

using LitJson;

我们先定义一些容器,用于存放配置表的内容,

// BullshitGenerator.cs

/// <summary>
/// 名人名言
/// </summary>
private List<string> m_famous = new List<string>();
/// <summary>
/// 废话
/// </summary>
private List<string> m_bosh = new List<string>();
/// <summary>
/// 前面垫话
/// </summary>
private List<string> m_after = new List<string>();
/// <summary>
/// 后面垫话
/// </summary>
private List<string> m_before = new List<string>();

封装一个LoadCfg方法,实现配置表读取的逻辑,

// BullshitGenerator.cs

/// <summary>
/// 读取配置表
/// </summary>
public void LoadCfg()
{
    // 读取配置文件(json格式的字符串)
    var jsonText = Resources.Load<TextAsset>("data").text;
    // 转为JsonData
    var jd = JsonMapper.ToObject(jsonText);
    // 名人名言
    m_famous = JsonMapper.ToObject<List<string>>(jd["famous"].ToJson());
    // 废话
    m_bosh = JsonMapper.ToObject<List<string>>(jd["bosh"].ToJson());
    // 前垫话
    m_before = JsonMapper.ToObject<List<string>>(jd["before"].ToJson());
    // 后垫话
    m_after = JsonMapper.ToObject<List<string>>(jd["after"].ToJson());
  
}
1.2、List洗牌

为了让名人名言和废话的获取具有随机性,我们写一个洗牌函数:

// BullshitGenerator.cs

/// <summary>
/// 对List进行洗牌
/// </summary>
private void Shuffle(ref List<string> list)
{
    var r = new System.Random();
    for (int i = list.Count - 1; i >= 0; i--)
    {
        int cardIndex = r.Next(i);
        var temp = list[cardIndex];
        list[cardIndex] = list[i];
        list[i] = temp;
    }
}

对名人名言和废话执行一次洗牌:

// BullshitGenerator.cs

// 对名人名言进行洗牌
Shuffle(ref m_famous);
// 对废话进行洗牌
Shuffle(ref m_bosh);
1.3、另起一段

封装一个StartNewLine函数,实现另起一段的功能(末尾标点符号替换为句号并追加一个换行符\n),

// BullshitGenerator.cs

/// <summary>
/// 开始新的一行,会将传入的文本的最后一个标点符号替换为句号然后换行(\n)
/// </summary>
/// <param name="txt">传入的文本</param>
/// <returns>处理后的文本</returns>
private string StartNewLine(string txt)
{
    if (txt.Length > 1)
        return txt.Substring(0, txt.Length - 1) + "。\n";
    return txt;
}
1.4、获取一句名人名言

封装一个GetFamous方法,实现获取下一句名人名言的功能,

/// <summary>
/// 获取一句名人名言
/// </summary>
private string GetFamous()
{
    // 取下一句名人名言
    var txt = Next(m_famous, ref m_curFamousIndex);
    // 替换前垫话
    txt = txt.Replace("a", RandomChoice(m_before));
    // 替换后垫话
    txt = txt.Replace("b", RandomChoice(m_after));
    return txt;
}

/// <summary>
/// 当前名人名言索引
/// </summary>
private int m_curFamousIndex = 0;

其中Next方法如下,实现从List中取下一个索引的值,

// BullshitGenerator.cs

/// <summary>
/// 取List的下一个索引的值
/// </summary>
/// <param name="list">List对象</param>
/// <param name="index">当前索引</param>
/// <returns></returns>
private string Next(List<string> list, ref int index)
{
    if (index < list.Count - 1)
    {
        ++index;
    }
    else
    {
        index = 0;
    }
    var result = list[index];
    return result;
}

RandomChoice方法如下,实现从List随机取一个值的功能,

// BullshitGenerator.cs

/// <summary>
/// 对List进行随机获取一个值
/// </summary>
/// <param name="list">List对象</param>
/// <returns></returns>
private string RandomChoice(List<string> list)
{
    return list[Random.Range(0, list.Count)];
}
1.5、获取一句废话
// BullshitGenerator.cs

/// <summary>
/// 当前废话索引
/// </summary>
private int m_curBoshIndex = 0;

// ...

// 获取一句废话
var bosh = Next(m_bosh, ref m_curBoshIndex);
1.6、段首空两格

我们需要对文字段落进行格式化,把英文的空格替换为中文的空格符\u3000,否则文字排版可能会出问题,如下:

// BullshitGenerator.cs

/// <summary>
/// 格式化空格(替换为中文的空格符:\u3000,并处理段首自动空两格)
/// </summary>
/// <param name="text">要处理的文本</param>
/// <returns>处理后的文本</returns>
private string FormatSpace(string text)
{
    // 开头两个空白符
    var temp_content = "\u3000\u3000" + text;
    // 新的一行缩进两个空白符
    temp_content = temp_content.Replace("\n", "\n\u3000\u3000");
    // 英文空格转中文空白符
    temp_content = temp_content.Replace(" ", "\u3000");
    return temp_content;
}
1.7、生成文章

封装一个DoGen函数,实现生成文章的功能,

/// <summary>
/// 根据标题生成狗屁不通文章
/// </summary>
/// <param name="title">标题</param>
/// <returns>生成的文章</returns>
public string DoGen(string title)
{
	string tmp = "";
	while (tmp.Length < 1000)
	{
	    var rd = Random.Range(0, 100);
	
	    if (rd < 5)
	    {
	        // 下一行
	        tmp = StartNewLine(tmp);
	    }
	    else if (rd < 20)
	    {
	        // 取一句名人名言
	        tmp += GetFamous();
	
	    }
	    else
	    {
	        // 下一句废话
	        tmp += Next(m_bosh, ref m_curBoshIndex);
	    }
	}
	// 文末空一行
	tmp = StartNewLine(tmp);
	// 给标题关键字标注颜色
	tmp = tmp.Replace("x", "<color=#960000ff>" + title + "</color>");
	// 格式化空格
	tmp = FormatSpace(tmp);
	return tmp;
}

2、MainPanel.cs脚本

Assets / Scripts / View目录中创建MainPanel.cs脚本,
在这里插入图片描述

2.1、UI成员变量

先定义一些必要的UI成员变量,

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

public class MainPanel : MonoBehaviour
{
    /// <summary>
    /// 主题输入框
    /// </summary>
    public InputField titleInput;
    /// <summary>
    /// 生成文章按钮
    /// </summary>
    public Button generateBtn;
    /// <summary>
    /// 保存图片按钮
    /// </summary>
    public Button saveBtn;
    /// <summary>
    /// 文章Text
    /// </summary>
    public Text articleText;

	// ...
}
2.2、初始化

Awake中做一下初始化,调用配置加载接口,

// MainPanel.cs

/// <summary>
/// 文章生成器
/// </summary>
private BullshitGenerator m_generator;

private void Awake()
{
    m_generator = new BullshitGenerator();
    // 加载配置
    m_generator.LoadCfg();
}
2.3、生成文章按钮的点击逻辑

Start函数中,实现生成文章按钮的点击逻辑,调用BullshitGeneratorDoGen来生成文章,

// MainPanel.cs

void Start()
{
    string generateText = "";

    // 生成文章
    generateBtn.onClick.AddListener(() =>
    {
        generateText = m_generator.DoGen(titleInput.text);
        articleText.text = generateText;
    });
}

3、测试文章生成

MainPanel.cs脚本挂到MainPanel.prefab预设上,并赋值成员变量。
在这里插入图片描述
运行Unity,测试效果如下,可以正常生成狗屁不通的文章~
请添加图片描述

十一、UGUI的Text如何转成Texture2D长图

现在,我们要将文字保存为长图,也就是把UGUIText转成Texture2D,再把Texture2D存为本地的jpg文件。
我的思路利用自定义字体生成字体纹理贴图,通过文字的uv坐标信息获取文字的像素信息,然后绘制到我们动态生成的Texture2D对象上。

1、制作字体

首先,我们找一个ttf字体,放到Unity工程的Assets / Fonts文件夹中,
在这里插入图片描述
将字体的Character设置为Custom Set
在这里插入图片描述
在字体统计目录中创建一个character.txt
在这里插入图片描述
把英文字母、数字、标点符号、常用汉字放到这个character.txt文件中,如下:
在这里插入图片描述
然后把整个文本内容复制,回到Unity中,粘贴到FontCustom Chars中,点击Apply

注:上面存为character.txt文件不是必要的,但建议你存成文件,方便你查找或修改字符。

在这里插入图片描述
此时字体会生成一个材质和纹理贴图,
在这里插入图片描述
字太多了,这样看看不大清,
在这里插入图片描述
我们先弄少一点字符,
在这里插入图片描述
看它生成的纹理贴图,我们可以看到,我们的字有的是上下颠倒的,有的是顺时针旋转了90度,(经过我多次测试,发现它只有这两种情况),这个我们后面写文字长图生成的时候需要小心了。
在这里插入图片描述

2、获取字体纹理贴图

var fontTexture = (Texture2D)font.material.mainTexture;

不过字体纹理本身不可读,我们无法对其调用GetPixels函数,所以我们需要拷贝一份可读的,

// 字体贴图不可读,需要创建一个新的可读的
var fontTexture = (Texture2D)font.material.mainTexture;
var readableFontTexture = new Texture2D(fontTexture.width, 
	fontTexture.height, 
	fontTexture.format, 
	fontTexture.mipmapCount, 
	true);
Graphics.CopyTexture(fontTexture, readableFontTexture);

3、根据字符获取对应纹理uv、宽高信息等

char charitem = "哈";
font.GetCharacterInfo(charitem, out CharacterInfo info);

CharacterInfo中我们就可以获取到字符的详细信息,包括uv、宽高等信息,
在这里插入图片描述

4、判断字符纹理是否是上下颠倒和顺时针90度

我们上面可以看到,字符纹理有的是上下颠倒的,我们可以使用uvTopLeftuvBottomRight来判断

if (info.uvTopLeft.y < info.uvBottomRight.y)
{
	// 字符是上下颠倒的
}
else
{
	// 顺时针旋转90度的
}

5、获取字符的宽高

字符的实际宽高的获取要小心了,如果是顺时针旋转90度的字符,则宽和高是对调的,

int charWidth, charHeight;// 字符宽高
 
if (info.uvTopLeft.y < info.uvBottomRight.y)
{
	// 字符是上下颠倒的
	charWidth = info.glyphWidth;
    charHeight = info.glyphHeight;
}
else
{
	// 顺时针旋转90度的
	charWidth = info.glyphHeight;
    charHeight = info.glyphWidth;
}

6、获取字符的像素信息

我们上面已经得到了可读的字体纹理贴图(Texture2D),又拿到了字符的uv坐标、字符宽高信息,那么,我们就可以通过Texture2DGetPixels接口获取到字符的像素信息了,

public Color[] GetPixels(int x, int y, int blockWidth, int blockHeight);

上下颠倒的字符的像素信息的获取:

Color[] charColor = readableFontTexture.GetPixels(
                    (int)(readableFontTexture.width * info.uvTopLeft.x),
                    (int)(readableFontTexture.height * info.uvTopLeft.y),
                    charWidth, charHeight);

顺时针旋转90度的字符的像素信息的获取:

charColor = readableFontTexture.GetPixels(
                    (int)(readableFontTexture.width * info.uvBottomRight.x),
                    (int)(readableFontTexture.height * info.uvBottomRight.y),
                    charWidth, charHeight);

7、如何给Texture2D写入像素

我们要创建一个长图(背景白色),然后给这个长图挨个字挨个字写入像素,所以,我们先创建一个Texture2D,并填充背景色,

// 背景色
Color backgroundColor = Color.white;
// 创建Texture2D
var textTexture = new Texture2D(textureWidth, textureHeight, TextureFormat.ARGB32, true);
// 填充背景色
Color[] emptyColor = new Color[textureWidth * textureHeight];
for (int i = 0; i < emptyColor.Length; i++)
{
    emptyColor[i] = backgroundColor;
}
textTexture.SetPixels(emptyColor);

接着我们就可以使用SetPixels接口给这个长图Texture2D挨个字挨个字写入字符像素了,

// 逐个字符绘制
foreach (var charitem in text.ToCharArray())
{
	// ...
	
	textTexture.SetPixel(x, y, color);
}

这里我们需要自己维护一个光标坐标,要控制好光标坐标不能超过Texture2D外面,还有每个字符的像素尺寸是有大有小的,我们需要计算中心对齐,不然字符就会高高低低(标点符号除外,否则比如逗号就会和文字是中心对齐的,实际上逗号要在文字的脚下)

8、最终Utils的TextToTexture2D接口

Assets / Scripts / Logic目录中新建一个Utils.cs脚本,封装一个TextToTexture2D接口,如下:

// Utils.cs

/// <summary>
/// UGUI的Text转Texture2D
/// </summary>
/// <param name="font">字体</param>
/// <param name="text">Text的文本</param>
/// <param name="textureWidth">图片的宽</param>
/// <param name="textureHeight">图片的高(最终会以字体的排版而裁剪掉多余的空白部分)</param>
/// <param name="drawOffsetX">要渲染的文字的x坐标偏移</param>
/// <param name="drawOffsetY">要渲染的文字的y坐标偏移</param>
/// <param name="textGap">每个字之间的间隔</param>
/// <param name="spaceGap">空白符的间隔</param>
/// <param name="fontSize">字体大小</param>
/// <param name="textColor">文字颜色</param>
/// <param name="backgroundColor">背景图颜色</param>
/// <returns></returns>
public static Texture2D TextToTexture2D(
    Font font,
    string text,
    int textureWidth, int textureHeight,
    int drawOffsetX, int drawOffsetY,
    int textGap, int spaceGap, int fontSize,
    Color textColor,
    Color backgroundColor)
{

    // 创建返回的Texture
    var textTexture = new Texture2D(textureWidth, textureHeight, TextureFormat.ARGB32, true);
    Color[] emptyColor = new Color[textureWidth * textureHeight];
    for (int i = 0; i < emptyColor.Length; i++)
    {
        emptyColor[i] = backgroundColor;
    }
    textTexture.SetPixels(emptyColor);

    // 字体贴图不可读,需要创建一个新的可读的
    var fontTexture = (Texture2D)font.material.mainTexture;
    var readableFontTexture = new Texture2D(fontTexture.width, fontTexture.height, fontTexture.format, fontTexture.mipmapCount, true);
    Graphics.CopyTexture(fontTexture, readableFontTexture);

    // 调整偏移量
    var originalDrawOffsetX = drawOffsetX; // 记录一下,换行用
    drawOffsetY = textureHeight - drawOffsetY - fontSize; // 从上方开始画

    // 逐个字符绘制
    foreach (var charitem in text.ToCharArray())
    {
        if ('\u3000' == charitem || ' ' == charitem)
        {
            drawOffsetX += spaceGap;
            continue;
        }

        if ('\n' == charitem)
        {
            // 换行
            drawOffsetX = originalDrawOffsetX;
            drawOffsetY -= fontSize;

            continue;
        }

        // 判断是否是中文
        bool isChinese = false;
        if (charitem >= 0x4e00 && charitem <= 0x9fbb)
        {
            isChinese = true;
        }

        if (drawOffsetX >= textTexture.width - fontSize)
        {
            // 换行
            drawOffsetX = originalDrawOffsetX;
            drawOffsetY -= fontSize;
        }

        int charWidth, charHeight;// 字符宽高
        Color[] charColor;// 字符颜色,数组内颜色的顺序为从左至右,从下至上

        font.GetCharacterInfo(charitem, out CharacterInfo info);

        if (info.uvTopLeft.y < info.uvBottomRight.y)// 处理被垂直翻转的字符
        {
            charWidth = info.glyphWidth;
            charHeight = info.glyphHeight;
            charColor = readableFontTexture.GetPixels(
                (int)(readableFontTexture.width * info.uvTopLeft.x),
                (int)(readableFontTexture.height * info.uvTopLeft.y),
                charWidth, charHeight);

            for (int j = 0; j < charHeight; j++)
            {
                for (int i = 0; i < charWidth; i++)
                {
                    if (charColor[j * charWidth + i].a != 0)
                    {
                        // 从上往下画,把字符颠倒过来
                        textTexture.SetPixel(
                            drawOffsetX + i,
                            drawOffsetY + charHeight - j + (isChinese ? ((int)((fontSize - charHeight) / 2f)) : 0),
                            textColor);
                    }
                }
            }

            drawOffsetX += charWidth + textGap;
        }
        else // 处理被顺时针旋转90度的字符
        {
            charWidth = info.glyphHeight;
            charHeight = info.glyphWidth;
            charColor = readableFontTexture.GetPixels(
                (int)(readableFontTexture.width * info.uvBottomRight.x),
                (int)(readableFontTexture.height * info.uvBottomRight.y),
                charWidth, charHeight);

            for (int j = 0; j < charHeight; j++)
            {
                for (int i = 0; i < charWidth; i++)
                {
                    if (charColor[j * charWidth + i].a != 0)
                    {

                        // 旋转
                        textTexture.SetPixel(
                            drawOffsetX + charHeight - j,
                            drawOffsetY + i + (isChinese ? ((int)((fontSize - charWidth) / 2f)) : 0),
                            textColor);
                    }
                }
            }
            drawOffsetX += charHeight + textGap;
        }
    }
    var realTextureHeight = textureHeight - drawOffsetY;
    textTexture.Apply();
    var finalTexture = new Texture2D(textureWidth, realTextureHeight, TextureFormat.ARGB32, true);
    Graphics.CopyTexture(textTexture, 0, 0, 0, drawOffsetY, textureWidth, realTextureHeight, finalTexture, 0, 0, 0, 0);
    Object.Destroy(textTexture);
    Object.Destroy(readableFontTexture);
    return finalTexture;
}

十二、Texture2D长图存为本地jpg文件

我们在Utils脚本中封装一个SaveTextureToLocal方法,这个比较简单,就不细讲了~

// Utils.cs

/// <summary>
/// 将Texture2D保存到本地文件
/// </summary>
/// <param name="texture">Texture2D对象</param>
/// <param name="fileName">要保存的文件名</param>
public static string SaveTextureToLocal(Texture2D texture, string fileName)
{
    var bytes = texture.EncodeToJPG();
    var savePath = Application.persistentDataPath + "/" + fileName;
#if UNITY_EDITOR
    savePath = Application.dataPath + "/" + fileName;
#endif
    File.WriteAllBytes(savePath, bytes);

    Debug.Log("SaveTextureToLocal: " + savePath);
    return savePath;
}

十三、测试文字长图的生成

MainPanel.cs脚本中的保存图片按钮的响应中添加Utils的调用,

// MainPanel.cs

public GameObject waitObj;
public GameObject tipsObj;
public Text tipsText;
private Coroutine saveTextureCoroutine;

private void Start()
{
	// ...
	
	// 保存为图片
	saveBtn.onClick.AddListener(() =>
	{
	    if (null != saveTextureCoroutine)
	        StopCoroutine(saveTextureCoroutine);
	    saveTextureCoroutine = StartCoroutine(SaveTexture(generateText));
	});
}

private IEnumerator SaveTexture(string generateText)
{
    waitObj.SetActive(true);
    yield return null;
    // 对文字做些处理
    generateText = generateText.Replace("<color=#960000ff>", "").Replace("</color>", "");
    generateText = string.Format("题目:{0}\n{1}\n博主:林新发\n博客:https://blog.csdn.net/linxinfa\n", titleInput.text, generateText);
    // 文字转Texture2D
    var texture2D = Utils.TextToTexture2D(articleText.font, generateText,
        (int)articleText.rectTransform.rect.width,
        (int)articleText.rectTransform.rect.height, 10, 10, 1,
        articleText.fontSize, articleText.fontSize, Color.black, Color.white);
    // 保存为jpg
    var savePath = Utils.SaveTextureToLocal(texture2D, "article.jpg");
    // 提示保存路径
    tipsObj.SetActive(true);
    tipsText.text = "保存成功,路径:\n" + savePath;
    waitObj.SetActive(false);
    // 2秒后自动隐藏提示
    yield return new WaitForSeconds(2);
    tipsObj.SetActive(false);
}

MainPanel.prefab中显示文章文本的Text的字体设置为我们自定义的字体,
在这里插入图片描述
运行Unity,生成文章,点击保存图片,

请添加图片描述
生成的长图如下(如果觉得不错,记得给我点个赞呀,我可是调试测试了好多次T_T):
请添加图片描述

十四、工程源码

本文工程源码我一上传到CODE CHINA,感兴趣的同学可自行下载下来学习。
地址:https://codechina.csdn.net/linxinfa/unitybullshitgenerator
注:我使用的Unity版本为:2021.1.7f1c1
在这里插入图片描述

十五、完毕

好了,就写到这里吧~
我是林新发:https://blog.csdn.net/linxinfa
原创不易,若转载请注明出处,感谢大家~
喜欢我的可以点赞、关注、收藏,如果有什么技术上的疑问,欢迎留言或私信~

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章           查看所有文章
加:2021-08-07 21:50:17  更:2021-08-07 21:50:22 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/3 21:18:45-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码