此篇教程参考了很多内容,本体是siki学院的“暗黑战神”课程中某一节的内容,我将里面的细节进行了一番探索及拓展,现在记录下其中的过程:
在Unity中创建新的C#脚本时,会自动去加载某个路径下的模版,它的后缀为txt,创建的C#脚本初始自带的内容就是这个模版内的内容。
在Windows下,这个路径为:Unity安装目录EditorDataResourcesScriptTemplates81-C# Script-NewBehaviourScript.cs.txt
在Mac下,这个路径为Unity.app/Contents/Resources/ScriptTemplates/81-C# Script-NewBehaviourScript.cs.txt
下面看下里面有什么:
尝试着修改一下,加一个myFunc函数:
此时在Unity中创建一个脚本看下:
可以看到,myFunc已经进去了。
那现在有一个问题:模版中有public class #SCRIPTNAME# : MonoBehaviour这一行,显然是在创建时根据为脚本取的名字,动态地替换掉了模版中的#SCRIPTNAME#。
由于Unity引擎不开源,我们无法了解它底层是怎么实现的,但现在如果我们要进一步对其拓展,比如在模版中写一段自己的信息,里面也有一段需要根据创建时信息临时修改的内容,比如#Modify#,我们要在创建脚本时,Unity也根据对应的信息,比如当前时间,动态地替换掉#Modify#中的内容,怎么做呢?
这是通过实现UnityEditor.AssetModificationProcessor这个类下的OnWillCreateAsset方法实现的,看一下它们的文档:
文档对AssetModificationProcessor这个类的功能的描述不大容易理解,查阅了一些其它资料后总结一下:AssetModificationProcessor这个类可以用来监听Project视图中资源的创建、删除、移动、保存。类里有几个方法,既然要在创建脚本时替换掉模版中##内的关键词,那我们只需要关注OnWillCreateAsset方法就行了,按照翻译,这个方法会在资源被创建之前被调用,而且从例子中看到,这个方法是个static方法。
注意到这个方法有参数string assetName,Unity底层调用这个方法时会把要创建资源的名字作为参数传入。
那么现在思路就比较明确了:Unity为我们在底层固化了这种创建新脚本时以文件名替换模版中#SCRIPTNAME#的功能,我们在对其拓展时要实现AssetModificationProcessor类下的OnWillCreateAsset静态方法,在里面根据传入的资源名,读取对应的文本文件,将其中指定的一些##字符串,替换成我们动态获取的信息,随后把文本重新写入到这个模版中。
那现在有几个问题值得注意:
1.首先,OnWillCreateAsset这个方法在AssetModificationProcessor类中,我们难道要去修改AssetModificationProcessor类吗?
答案是不用,从上面的图中例子也可以看到,直接用一个类继承它即可,在继承的类中实现这个方法。
2.我们读取对应的文本文件,需要文件名,可是按照之前的说法,OnWillCreateAsset这个方法是在资源被创建之前调用的,这时候脚本都没创建完成,哪来的文件名?又怎么读取呢?
这是因为这个方法的解释不清楚,这个方法的解释是在资源被创建之前调用的,但其实更详细地说,它是在Unity内部将脚本模版的内容复制到新创建的脚本中,并将#SCRIPTNAME#替换为文件名之后被调用的。
现在我们要选一个函数打开对应传入参数的文件,可以选用ReadAllText函数,它可以打开一个文本文件,将文件中所有文本读取到一个字符串中,然后自动关闭此文件,位于命名空间System.IO中,参考文档如下:
https://docs.microsoft.com/zh-cn/dotnet/api/system.io.file.readalltext?redirectedfrom=MSDN&view=netframework-4.8#System_IO_File_ReadAllText_System_String_
由于我们看不到.NET Framework的源代码,只能猜测这个方法应该是封装了类似open,read和close这样的系统调用。
3.读取后的文本中要替换的内容从哪里取得?
比如我们要取得用户的用户名,这时可以使用Environment.UserName这个属性,它位于System命名空间中,它可以获取到当前已登录到操作系统的人员的用户名,参考文档如下:
https://docs.microsoft.com/zh-cn/dotnet/api/system.environment.username?redirectedfrom=MSDN&view=netframework-4.8
文档中说了,在Unix平台上,这个属性封装了对getpwuid_r的调用,我没有细查getpwuid_r的信息,但我猜测它的实现可能类似于who指令那样,是通过读取utmpx这个文件来获取utmpx结构体数组,随后访问里面的ut_user条目来得到用户名的,有关who指令的实现,可以参考我的文章:
https://www.cnblogs.com/czw52460183/p/10999434.html
那假如替换的内容中有当前系统的时间呢?这个怎么取得?
答案是可以用DateTime这个数据结构中的信息直接获取,具体就不描述了,参考文档如下:
https://docs.microsoft.com/zh-cn/dotnet/api/system.datetime?view=netframework-4.8
好,前戏结束,给出实际操作过程:
1.我们不想对模版做太多改动,只在头部加入文件名,作者名(登录用户名),邮箱(因为实际项目中你写的文件出了问题,要留联系方式方便别人找到你),日期和功能。因此,先对81-C# Script-NewBehaviourScript.cs.txt这个文件内容修改如下:
/**************************************************** 文件:#SCRIPTNAME#.cs 作者:#CreateAuthor# 邮箱: czw52460183@163.com 日期:#CreateTime# 功能:Nothing *****************************************************/ using UnityEngine; public class #SCRIPTNAME# : MonoBehaviour { }
2.此时在Unity中创建一个脚本测试一下,打开后结果如图:
3.现在我们开始写替换脚本,在写之前先要明确一点:在Unity中,创建任何资源时,目录下不仅会生成该资源(包括脚本,文件夹等各种资源),还会生成对应的同名的.meta文件,meta文件本质是一个文本文档,关于它是什么,可以参考:
https://blog.uwa4d.com/archives/USparkle_inf_UnityEngine.html
举个例子:我们在Asset目录下同时创建一个新的脚本文件和一个新的文件夹,结果如下:
现在问题是,传入到OnWillCreateAsset的参数到底是文件名还是带.meta的名字?
我们做个实验,在Asset下创建一个脚本,名为testInfo:
/**************************************************** 文件:testInfo.cs 作者:#CreateAuthor# 邮箱: czw52460183@163.com 日期:#CreateTime# 功能:Nothing *****************************************************/ using System; using System.IO; using UnityEngine; public class testInfo : UnityEditor.AssetModificationProcessor { private static void OnWillCreateAsset(string path) { Debug.Log(path); } }
此时重新创建testFolder文件夹和testScript脚本,Unity日志输出如下:
说明传入到OnWillCreateAsset的参数是带meta的,而且会带有路径。
因此我们处理传入的参数时,要先去掉这个meta后缀,此外,由于只为新建的脚本添加头部注释,因此也要限定必须是.cs文件才会进行处理。
删掉上面的文件,重新写脚本,命名为AddScriptInfo.cs,代码如下:
using System; using System.IO; public class AddScriptInfo : UnityEditor.AssetModificationProcessor { private static void OnWillCreateAsset(string path) { //将.meta后缀屏蔽 path = path.Replace(".meta", ""); //只对.cs脚本作操作 if (path.EndsWith(".cs")) { //读取该路径下的.cs文件中的所有文本. //注意,此时Unity已经对脚本完成了模版内容的替换,包括#SCRIPTNAME#也已经被替换为文件名了,读取到的是替换后的文本内容. string str = File.ReadAllText(path); //获取用户名和当前系统时间并替换对应位置内容 str = str.Replace("#CreateAuthor#", Environment.UserName).Replace( "#CreateTime#", string.Concat(DateTime.Now.Year, "/", DateTime.Now.Month, "/", DateTime.Now.Day, " ", DateTime.Now.Hour, ":", DateTime.Now.Minute, ":", DateTime.Now.Second)); //重新将文本写入.cs文件 File.WriteAllText(path, str); } } }
此时在Asset目录下重新创建文件testAgain.cs来测试,结果如下:
可以看到,功能已经实现了。
有趣的是,在我做这个实验时,网上查了许多资料,发现这个教程挺多的,但很多都强调,这个修改##信息的脚本(对应图中的AddScriptInfo),需要放到Editor文件夹下,却又都没讲为什么。而从实际测试来看,不放也能正常工作,也不知道什么情况。
不过考虑到这其实是个工具插件,和业务代码无关,可以考虑放到某个文件夹下方便管理,我们就按视频里的方法做吧:Assets下创建个Plugins文件夹,里面创建个Editor文件夹,放入此脚本,这里专门放置插件,方便管理。
完结。