Unity 之 实现读取代码写进Word文档功能实现 -- 软著脚本生成工具

news2024/10/2 8:21:12

Unity 之 实现读取代码写进Word文档功能

  • 前言
  • 一,实现步骤
    • 1.1 逻辑梳理
    • 1.2 用到工具
  • 二,实现读写文件
    • 2.1 读取目录相关
    • 2.2 读写文件
  • 三,编辑器拓展
    • 3.1 编辑器拓展介绍
    • 3.2 实现界面可视化
  • 四,源码分享
    • 4.1 工具目录
    • 4.2 完整代码

前言

之所以有一篇这样的文章,是因为最进在申请软著时,要复制60页的代码到Word文档中,手动复制了一次,下一次就在也不想去复制了。记得之前好像看见过有人用Py写过这样的工具,但是Py我有不熟。就有了使用Unity写一个这样的工具的想法,一起来看看效果吧~

看看效果:

只需要选择想导出的脚本的根目录和保存Word文件的根目录(不选默认执行到工程的Asset目录);然后点击:"开始读取CS并写Word"即可:

生成的脚本在WPS打开:

PS:生成的文档完全按照代码中的格式来处理的,没有剔除空格和注释。需要的童鞋可以自行拓展一下工具脚本。


一,实现步骤

1.1 逻辑梳理

基本思路

  1. 在文件夹中找到要复制的Csharp脚本
  2. 读取Csharp脚本
  3. 保存读取到的内容到Word文档中

知识点

  1. 遍历文件夹,根据后缀找到指定文件
  2. 读取文件中的数据
  3. 将读取到的数据保存到文档中
  4. 编辑器拓展 - 分装成工具

1.2 用到工具

用到的是NPOI库:

  1. NPOI是指构建在POI 3.x版本之上的一个程序,NPOI可以在没有安装Office的情况下对Word或Excel文档进行读写操作。
  2. NPOI是一个开源的C#读写Excel、WORD等微软OLE2组件文档的项目。

工程目录:

PS:需要插件的同学,可以到文末工具资源包中获取。


二,实现读写文件

这里只对用的逻辑进行讲解,若需要全面学习文件的读取相关,可以看我之前写过的博文:

  1. 本地数据交互 – 文件概述 – File类介绍和使用
  2. 本地数据交互 – 文件相关类介绍 – 读写txt文本文件

2.1 读取目录相关

  1. 判断是否存在目录文件
private static string filePath = "Assets/ScriptTemp.docx";
// 检查目录是否存在
if (Directory.Exists(filePath))
{
    // 存在就删除
    Directory.Delete(filePath);
}
  1. 创建指定文件
private static string filePath = "Assets/ScriptTemp.docx";
FileStream fs = new FileStream(savePath + filePath, FileMode.Create);
  1. 遍历文件夹并筛选.cs后缀文件
/// <summary>
/// 递归文件夹下的cs文件
/// </summary>
/// <param name="folderPath"></param>
static void FileName(string folderPath)
{
    DirectoryInfo info = new DirectoryInfo(folderPath);
    foreach (DirectoryInfo item in info.GetDirectories())
    {
        FileName(item.FullName);
    }
    foreach (FileInfo item in info.GetFiles())
    {
        // 找到文件夹下的脚本
        if (item.FullName.EndsWith(".cs", StringComparison.Ordinal))
        {
            //将目录缓存下来,之后读文件的时候用
            //csScriptFullName.Add("Assets" + item.FullName.Replace(Application.dataPath, ""));
        }
    }
}

2.2 读写文件

  1. 读取文件内容

这里不适用ReadToEnd方法是因为,我发现在后续写入的时候会不会自动换行。所以使用循环的方式一行一行的读取文件内容。

// 读取脚本内容
StreamReader streamReader = new StreamReader(itemPath, Encoding.UTF8);
// 不适用
//string res = streamReader.ReadToEnd();
string res = "";
while (!streamReader.EndOfStream)
{
    res = streamReader.ReadLine() + "\n";
    //Debug.Log($"读取脚本内容: {res}");
}
// 释放资源
streamReader.Dispose();
  1. 写入Word文件

引用命名空间,没有的话就是没有导入1.2说的.dll文件,在到文末工具包中下载:

using NPOI.XWPF.UserModel;

写入Word步骤:创建文档 —> 创建段落 —>设置格式 —> 写入内容 —> 生成文档

XWPFDocument doc = new XWPFDocument();
// 新建段落
XWPFParagraph paragraph = doc.CreateParagraph();
// 左对齐
paragraph.Alignment = ParagraphAlignment.LEFT;
// 新建运行行
XWPFRun run = paragraph.CreateRun();
// 设置颜色
run.SetColor("000000");
// 字体
run.FontFamily = "宋体";
// 字号
run.FontSize = 10;
// 设置内容
run.SetText("内容内容内容");

// 写入文档
FileStream fs = new FileStream("文件目录", FileMode.OpenOrCreate);
// 写入
doc.Write(fs);
// 释放资源
fs.Close();
fs.Dispose();

三,编辑器拓展

3.1 编辑器拓展介绍

  1. MenuItem
    使用MenuItem标识可以为编辑器添加新的菜单。点击后执行一些特定的逻辑,没有额外的操作界面。只有静态方法可以使用该标识,该标识可以把静态方法转换为菜单命令。
    比如:
[MenuItem("Tools/生成Word")]
public static void CreateWindow()
{
    Debug.Log("todo... 点了按钮");
}

  1. EditorWindow
    继承自EditorWindow的类,可以实现更复杂的编辑器窗口功能。且这种窗口是可以自由内嵌到Unity编辑器内,共同组成编辑器的Layout

通过在OnGUI()函数内调用GUILayout、EditorGUILayout、GUI等类的一些方法来实现复杂的界面。

下面是结果常用Layout 示例代码:

private void OnGUI()
{
    // 接受用户输入
    float size = EditorGUILayout.FloatField("输入size:", size);
    
    EditorGUILayout.LabelField("提示信息 :");
    
    // 添加空行
    EditorGUILayout.Space();
    if (GUILayout.Button("点击按钮"))
    {
        pathRoot = EditorUtility.OpenFolderPanel("路径选择", pathRoot, "");
    }
}

3.2 实现界面可视化

  1. 创建脚本引用Editor命名空间,继承EditorWindow
  2. 新建OnGUI方法实现,可视化界面
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

public class TestEditor : EditorWindow
{
    /// <summary>
    /// 读取根目录
    /// </summary>
    private static string pathRoot = "Assets";
    
    private static float size;

    [MenuItem("Tools/Test111")]
    public static void CreateWindow()
    {
        TestEditor window = GetWindow<TestEditor>(false, "测试调试窗口", true);
        window.Show();
    }

    // 显示窗口
    private void OnGUI()
    {
        EditorGUILayout.LabelField("提示信息 :");
        size = EditorGUILayout.FloatField("输入size:", size);
        // 换行
        EditorGUILayout.Space();

        EditorGUILayout.LabelField("换行后的提示信息 :");
        EditorGUILayout.Space();
        // 按钮
        if (GUILayout.Button("选择脚本路径"))
        {
            pathRoot = EditorUtility.OpenFolderPanel("路径选择", pathRoot, "");
        }
    }
}


四,源码分享

4.1 工具目录

打包后的工具目录:

工程下载:源码和步骤都在上面分享过了,若还有什么不明白的,可以 点击链接下载 ,积分不够的童鞋关注下方卡片,回复:“Word” 或者 “软著脚本工具” 即可获得Demo源码~


4.2 完整代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;
using System;
using NPOI.XWPF.UserModel;
using System.Text;
using Debug = UnityEngine.Debug;

public class CreateWordDocxEditor : EditorWindow
{
    /// <summary>
    /// 读取根目录
    /// </summary>
    private static string pathRoot = "Assets";

    /// <summary>
    /// 保存根目录
    /// </summary>
    private static string savePath = "Assets";

    // 文件名称
    private static string filePath = "/ScriptTemp.docx";

    [MenuItem("Tools/生成Word")]
    public static void CreateWindow()
    {
        CreateWordDocxEditor window = GetWindow<CreateWordDocxEditor>(false, "配置生成文档需求", true);
        window.Show();
    }

    // 显示窗口
    private void OnGUI()
    {
        EditorGUILayout.BeginHorizontal();
        EditorGUILayout.Space();
        EditorGUILayout.LabelField("当前脚本路径 :" + pathRoot);
        EditorGUILayout.Space();
        EditorGUILayout.EndHorizontal();

        EditorGUILayout.BeginHorizontal();
        EditorGUILayout.Space();
        if (GUILayout.Button("选择脚本路径"))
        {
            pathRoot = EditorUtility.OpenFolderPanel("路径选择", pathRoot, "");
            Debug.Log("选择脚本路径 : " + pathRoot);
        }

        EditorGUILayout.Space();
        EditorGUILayout.EndHorizontal();

        EditorGUILayout.BeginHorizontal();
        EditorGUILayout.Space();
        EditorGUILayout.LabelField("选择保存路径 :" + savePath);
        EditorGUILayout.Space();
        EditorGUILayout.EndHorizontal();

        EditorGUILayout.BeginHorizontal();
        EditorGUILayout.Space();
        if (GUILayout.Button("选择保存路径"))
        {
            savePath = EditorUtility.OpenFolderPanel("路径选择", savePath, "");
            Debug.Log("选择保存路径 : " + savePath);
        }

        EditorGUILayout.Space();
        EditorGUILayout.EndHorizontal();

        EditorGUILayout.BeginHorizontal();
        EditorGUILayout.Space();
        if (GUILayout.Button("开始读取CS并写入Word"))
        {
            CreateWordDocxFile();
        }

        EditorGUILayout.Space();
        EditorGUILayout.EndHorizontal();
    }

    static void CreateWordDocxFile()
    {
        Debug.Log("打包开始执行");

        csScriptFullName.Clear();
        FileName(pathRoot);
        CreatOrOpenDoc();

        EditorUtility.ClearProgressBar();
        AssetDatabase.Refresh();

        Debug.Log("打包执行结束");
    }

    /// <summary>
    /// 暂存遍历到的CS脚本全路径
    /// </summary>
    static List<string> csScriptFullName = new List<string>();

    /// <summary>
    /// 创建或打开文档
    /// </summary>
    /// <param name="filePath"></param>
    private static void CreatOrOpenDoc()
    {
        try
        {
            // 检查目录是否存在
            if (Directory.Exists(savePath + filePath))
            {
                // 存在就删除
                Directory.Delete(savePath + filePath);
            }

            FileStream fs = new FileStream(savePath + filePath, FileMode.OpenOrCreate);
            XWPFDocument doc = new XWPFDocument();

            int index = 0;
            foreach (var itemPath in csScriptFullName)
            {
                //Debug.Log($"csScriptFullName[i]: {item}");

                // 读取脚本内容
                StreamReader streamReader = new StreamReader(itemPath, Encoding.UTF8);

                //string res = streamReader.ReadToEnd();
                string res = "";
                while (!streamReader.EndOfStream)
                {
                    res = streamReader.ReadLine() + "\n";
                    Debug.Log($"读取脚本内容: {res}");

                    // 新建段落 设置格式
                    XWPFParagraph paragraph = doc.CreateParagraph();
                    paragraph.Alignment = ParagraphAlignment.LEFT;
                    XWPFRun run = paragraph.CreateRun();
                    run.SetColor("000000");
                    run.FontFamily = "宋体";
                    run.FontSize = 10;
                    run.SetText(res);
                }

                // 释放资源
                streamReader.Dispose();

                EditorUtility.DisplayProgressBar("处理中...", "正在处理:" + itemPath,
                    index * 1.0f / csScriptFullName.Count);
                index++;
                Debug.Log($"文件生成完成:{savePath} {filePath} ");
            }

            try
            {
                doc.Write(fs);
            }
            catch (Exception e)
            {
                Debug.LogError($"文件不可写入,请查看原因:{e}");
            }

            fs.Close();
            fs.Dispose();
        }
        catch (Exception e)
        {
            Debug.LogError($"创建失败,同名文件被打开!问题:{e}");
        }

        Debug.Log($"文件生成在: {savePath + filePath}");
    }

    /// <summary>
    /// 递归文件夹下的cs文件
    /// </summary>
    /// <param name="folderPath"></param>
    static void FileName(string folderPath)
    {
        DirectoryInfo info = new DirectoryInfo(folderPath);
        foreach (DirectoryInfo item in info.GetDirectories())
        {
            FileName(item.FullName);
        }

        foreach (FileInfo item in info.GetFiles())
        {
            // 找到文件夹下的脚本
            if (item.FullName.EndsWith(".cs", StringComparison.Ordinal))
            {
                csScriptFullName.Add("Assets" + item.FullName.Replace(Application.dataPath, ""));
            }
        }
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【SPSS】两独立样本T检验分析详细操作教程(附案例实战)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

消除“马路黑洞”,计讯物联智能井盖综合管理系统平台有绝招

窨井盖作为市政公用设施的重要组成部分&#xff0c;一旦出现松动、损坏等问题时,不仅会影响市容市貌&#xff0c;还会给道路上的车辆、行人带来安全隐患&#xff0c;对社会安定与安全造成极大负面影响。 痛点分析 01 由于地下管线错综复杂、窨井盖分布广泛&#xff0c;加之信…

Linux 进程:父子进程

目录一、了解子进程二、创建子进程1.创建子进程2.区分父子进程三、理解子进程四、创建子进程的意义进程就是运行中的应用程序&#xff0c;如果一个程序较为庞大&#xff0c;我们可以给这个程序创建多个进程&#xff0c;每个进程负责一部分代码的运行。 A进程如果创建了B进程&am…

【华为OD机试模拟题】用 C++ 实现 - GPU 调度(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 去重求和(2023.Q1) 文章目录 最近更新的博客使用说明GPU 调度题目输入输出示例一输入输出说明示例二输入输出说明Code使用说明 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。

动态规划:完全背包基础、518.零钱兑换II、377.组合总和IV

leetcode 518.零钱兑换IIleetcode 377.组合总和IV完全背包基础有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品都有无限个&#xff08;也就是可以放入背包多次&#xff09;&#xff0c;求解将哪些物品装入背包里…

CleanMyMac4.20最新版新增功能及电脑清理垃圾使用教程

CleanMyMac4.20作为知名的Mac清理工具&#xff0c;仅需一键即可快速而安全地清理系统垃圾&#xff0c;释放磁盘空间&#xff0c;因此一直深受Mac用户的喜爱。在不断更新的版本中&#xff0c;CleanMyMac已经不仅仅满足于只做简单的Mac清理工具&#xff0c;而是为Mac用户提供更多…

借助Intune无感知开启Bitlocker

希望使用 Intune 部署 BitLocker&#xff0c;但不知道从哪里开始&#xff1f;这是人们最开始使用 Intune 时最常见的问题之一。在本博客中&#xff0c;你将了解有关使用 Intune 管理 BitLocker 的所有信息&#xff0c;包括建议的设置、BitLocker CSP 在客户端上的工作方式&…

彻底搞懂React-hook链表构建原理

写在前面的小结 每一个 hook 函数都有对应的 hook 对象保存状态信息useContext是唯一一个不需要添加到 hook 链表的 hook 函数只有 useEffect、useLayoutEffect 以及 useImperativeHandle 这三个 hook 具有副作用&#xff0c;在 render 阶段需要给函数组件 fiber 添加对应的副…

Spring Boot系列04--静态资源处理

目录1. 静态资源映射规则2. 欢迎页1. 静态资源映射规则 在项目中双击shift或ctrlN搜索WebMvcAutoConfiguration.class文件&#xff0c;文件中的addResourceHandlers方法如下&#xff1a; public void addResourceHandlers(ResourceHandlerRegistry registry) {if (!this.reso…

运维排查篇 | Redis占用内存过高怎么办

我们知道&#xff0c;Redis是一个key-value数据库&#xff0c;它的数据是运行在内存中的其读写效率比将数据存储到磁盘上的数据库要快很多虽然性能强大&#xff0c;但是如果我们不了解Redis的内存回收策略&#xff0c;就有可能导致Redis消耗内存过高甚至导致内存溢出&#xff0…

[优化]上下游交互策略

书接上文&#xff1a;https://blog.csdn.net/weixin_43303530/article/details/127227147?spm1001.2014.3001.5502&#xff0c;为满足产品提的在24小时内能重试尽量去重试&#xff0c;不计较重试的次数的要求&#xff0c;在第三方电子卡系统无法提升接口并发数量的情况下&…

电脑C盘空间不足?这样做就行了!

一般来说&#xff0c;电脑C盘都是系统盘&#xff0c;它的稳定关系到系统能否正常运行&#xff0c;但是很多朋友的C盘却总是红色的&#xff0c;这也就意味着C盘储存空间不足了&#xff0c;那么我们就需要进行以下操作&#xff0c;来让C盘重新拥有储存空间。方案一&#xff1a;运…

BLE Mesh蓝牙协议学习记录

BLE Mesh蓝牙协议学习 文章目录BLE Mesh蓝牙协议学习前言概述一、蓝牙技术整体框架二、经典蓝牙和低功耗蓝牙mesh协议架构图承载层&#xff08;Bearer Layer&#xff09;网络层&#xff08;Network Layer&#xff09;底层传输层&#xff08;Lower Transport Layer&#xff09;上…

JAVA连接数据库——JDBC的简单使用

JDBC即Java数据库连接.用来实现Java程序对数据库增删查改。 为了对接Java程序和数据库&#xff0c;java.sql提供了很多api包含在java.sql和javax.sql里面 结构: DriverManager接口: 每一个数据库的驱动程序都必须去到DriverManager注册&#xff0c;生成一个Connection Conn…

电商平台的促销活动如何抵御大流量的ddos攻击

每一次活动大促带来的迅猛流量&#xff0c;对技术人而言都是一次严峻考验。如果在活动期间遭受黑产恶意 DDoS 攻击&#xff0c;无疑是雪上加霜。电商的特性是业务常态下通常不会遭受大流量 DDoS 攻击&#xff0c;且对延迟敏感&#xff0c;因此只需要在活动期间按需使用 DDoS 防…

【第五章 AOP概述,底层原理,AOP术语,切入点表达式,AOP操作(基于注解方式,基于xml配置文件)】

第五章 AOP概述&#xff0c;底层原理&#xff0c;AOP术语&#xff0c;切入点表达式&#xff0c;AOP操作&#xff08;基于注解方式&#xff0c;基于xml配置文件&#xff09; 1.AOP概述&#xff1a; &#xff08;1&#xff09;什么是AOP&#xff1a; ①面向切面编程&#xff08;…

11-KMP算法

KMP算法是一个字符串匹配算法&#xff0c;总的意义是在给定的字符串A中利用优化的方法快速地找出字符串B的位置&#xff0c;相比于传统匹配算法&#xff0c;它能有效减少匹配时间&#xff0c;提高效率。 前缀和后缀 在我们看KMP算法前我们先考虑一个问题&#xff1a;假如我们…

基于框架的平台总线式开发

一、总线、设备、驱动 硬编码式的驱动开发带来的问题&#xff1a; 1. 垃圾代码太多 2. 结构不清晰 3. 一些统一设备功能难以支持 4. 开发效率低下 1.1 初期解决思路&#xff1a;设备和驱动分离 struct device来表示一个具体设备&#xff0c;主要提供具体设备相关的资源&am…

Telnet 基础实验2: SSH 实验

Telnet 基础实验2&#xff1a; SSH 实验 本实验只能使用 eNSP中 AR 系统的路由器做 拓扑图 SSH &#xff1a; Secure Shell 是一个网络安全协议&#xff0c;基本于 TCP 协议 22 端口传输数据&#xff0c;通过对网络数据的加密&#xff0c;使其能够在一个不安全的网络环境中&a…

网易新财报:游戏养家,教育维稳、音乐快走

配图来自Canva可画 随着互联网流量红利逐渐消退&#xff0c;互联网大厂们也告别高增长时代&#xff0c;逐渐进入稳定增长阶段。近两年&#xff0c;流量焦虑、业务失速等问题更是成为了一团浓雾&#xff0c;笼罩在互联网大厂周围。不过&#xff0c;面对所遭遇的难题&#xff0c…