最近发现当工具仔的频率有点高,但是常用的接口总是记不住,决定还是整个笔记纪录一下都有哪些接口,以后回来查的时候也方便一些。
0.做个工具入口
1 |
[UnityEditor.MenuItem("Test/test")] |
1.用路径加载对象
1 2 3 4 5 6 |
//这里用的路径是Assets/XXX/XXX/XXX.prefab GameObject go = AssetDatabase.LoadAssetAtPath<GameObject>(path); //根据类型在指定目录下查找资源 string[] resPaths = { btPath }; var graphList = AssetDatabase.FindAssets("t:graph", resPaths); |
2.搜索指定路径下的所有文件
1 2 3 |
//这里还包含一个linq语句剔除掉了.meta文件~ var files = Directory.GetFiles(LEVEL_FOLDER_PATH, "*.*", SearchOption.AllDirectories).Where((s) => !s.EndsWith(".meta")).ToArray(); |
3.修改完了如何保存资源
1 2 3 4 5 |
//先在循环内用setDirty将修改后的预制体进行标记 EditorUtility.SetDirty(go); //然后在循环结束的时候调用保存所有被标记的资源与刷新 AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); |
4.编辑器下到底应该如何卸载资源?
使用第一种方式加载了对应的预制体后,发现这个东西居然不能用正常的方式卸载?!而且因为工具需要遍历的资源太多太大,这些资源无法及时卸载导致内存爆炸最后crash。接下来纪录一下为了卸载这个玩意儿我都试了哪些办法:
-
Resources.UnloadAsset
很明显这个接口不行,因为这个接口只能释放资源,无法释放GameObject。调用卸载GameObject会报错:
UnloadAsset may only be used on individual assets and can not be used on GameObject's / Components / AssetBundles or GameManagers -
Object.DestroyImmediate
这个接口也以失败告终,因为Unity会认为你在直接修改资源,如果直接删除对象会导致资源丢失,报错:
Destroying assets is not permitted to avoid data loss. -
Resources.UnloadUnusedAssets
我尝试着先将资源的引用置为null,然后调用这个接口进行释放资源,虽然执行起来没有报错,但是我的内存仍然还是在上涨,并无法回到代码执行前的内存水平。
我又试了一下多次运行这段代码,会出现内存仍然升高的现象,但是最终的值会稳定回到我第一次运行结束时的内存占用水平。 -
AssetDatabase.DeleteAsset
看名字感觉像是跟LoadAssetAtPath接口像是配套的接口,但是 完 全 不 是 !他喵的这个接口是用来删除资源的,调一调资源全删掉!还好我有版本控制工具MMP。
踩了四个坑,终于找到最终的接口了:
EditorUtility.UnloadUnusedAssetsImmediate();
用了这个接口之后,内存稳如老狗,随便加载随便卸载,实在是太爽了!!!(编辑器下才有用哦)
运行时应该还是要用Resources.UnloadUnusedAssets()进行卸载,不过现在暂时没用到,用到在研究吧~
5.加速importing
Assetdatabase.StartAssetEditing/StopAssetEditing
Unity官方的解释:Starts importing Assets into the Asset Database. This lets you group several Asset imports together into one larger import.通俗点讲就是对大量的import操作进行合批,进而加快import效率。StartAssetEditing/StopAssetEditing是成对出现的,Unity引擎内部有一个gLock字段用于记录StartAssetEditing/StopAssetEditing操作,每执行一次StartAssetEditing,gLock++,同样每执行一次StopAssetEditing,gLock--,当gLock=0时标志着一次import合批完成。为了实现import合批,unity会挂起StartAssetEditing/StopAssetEditing之间的所有Import和Refresh操作,等到StopAssetEditing后,一次性Import和Refresh。
StartAssetEditing/StopAssetEditing
6.给进程做个进度条吧~
代码虽然很简单,但是每次都忘了接口叫啥,每次都要去之前的代码里复制,这里也稍微提一下吧:
1 2 3 |
EditorUtility.DisplayProgressBar(标题, 内容, 进度); //记得清,不然这个弹窗就关不掉了!! EditorUtility.ClearProgressBar(); |
7.readonly和const的区别
readonly修饰的字段:其初始化仅是固定了其引用(地址不能修改),但它引用的对象的属性是可以更改的。
const:定义的是静态常量,在对象初始化的时候赋值。以后不能改变它的值,属于编译时常量(编译时就已经确定的值)。不能用new初始化。不能与static一起使用,因为const已经是静态的了。
8.is/as与拆装箱
is是一次类型检查,它只会检查对象是否与目标类型兼容,而不会进行类型转换。当然,is适用于所有类型,无论是值类型还是引用类型:
obj is Enum
obj is Vector2
obj is bool
as是一次类型转换,首先它会检查对象是否与目标类型兼容,兼容则进行类型转换并拆箱成对应类型,如果不兼容则返回null。
as内部虽然做了一次类型检查,但是使用as的你并不知道检查结果,所以在使用as之后通常还是需要一次检查结果是否为null。
as无法用在可空类型上,即值类型无法使用as进行类型转换。
那么我们在拆箱时具体要怎么办呢?
- 对于引用类型,可以直接使用as进行拆箱,拆箱后记得判空
- 对于值类型,只能先对类型使用is检查,然后进行强转。如
12if(obj1 is int )int nValue = (int)obj1;
P.S.
is和as都需要一个实例,也就是一个obj是否是另外一个类型,但是有的时候我们需要一些类型间的比较该怎么办呢?比如想判断一个类型是否为另外一个类型的子类,我们就可以使用下面的接口进行判断:
1 2 |
//Type B是否继承自TypeA,是的话返回true,如果是TypeA=TypeB也为true bool res = {TypeA}.IsAssignableFrom({TypeB}) ; |
9.程序集与类型
比如我们想获取某一个类型的所有子类,首先我们需要获取对应的程序集。
通过类型可以直接获取类型所在的程序集,如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//程序集 var assembly = typeof(你的目标类型)).Assembly //程序集内所有类型 var allTypes = assembly.GetTypes(); foreach (var targetType in allTypes) { //创建一个类型实例 var instance = Activator.CreateInstance(targetType); //获取字段,如果想获取属性就.getproperty var field = targetType.GetField("_value", BindingFlags.Instance | BindingFlags.NonPublic); //获取属性,想获取要先有实例才行 field.GetValue(instance); } |
其中如果我们想拿某个类型的父类,可以通过类型的.BaseType
获取父类,但是如果你的类型继承自一个泛型父类,那么使用 targetType.BaseType == typeof(父类<>) 进行类型判断是不行的,因为在编译结束的时候,这个类的父类就已经是确定类型了,比如你的泛型传入的是int,那么类型的父类就是父类
10.结果写入文件
很简单,几行代码就解决了,但是代码是啥总忘,记一下。
1 2 3 4 5 |
FileStream fs = new FileStream("Assets/Lua/config/GlobalLuaActionDescribe.lua.txt", FileMode.Create); StreamWriter st = new StreamWriter(fs); st.Write(sb.ToString()); st.Close(); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
//读文件 X:/XXXX/Assets/Excel.xlsx var path = Application.dataPath.Replace(@"\", "/")+"/Excel.xlsx"; FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read); ExcelPackage package = new ExcelPackage(fileStream); ExcelWorksheet sheet = package.Workbook.Worksheets[0]; for (int rowIndex = sheet.Dimension.Start.Row, maxRow = sheet.Dimension.End.Row; rowIndex <= maxRow; rowIndex++) { //读取信息,按行读取,列的起始下标为1,这里只读第一列数据 sheet.GetValue<string>(rowIndex, 1); } //写文件 ExcelPackage excel = new ExcelPackage(); ExcelWorksheet worksheet = excel.Workbook.Worksheets.Add("Sheet"); int index =0; //遍历要写的数据列表 foreach (var item in dataList) { //写文件,行和列都从1开始,这里把数据按行写入 index++; worksheet.Cells[index, 1].Value = item; } //把写完的数据存起来,存到X:/XXXX/Assets/Result.xlsx File.WriteAllBytes(Application.dataPath.Replace(@"\", "/")+"/Result.xlsx", excel.GetAsByteArray()); |
11.如何计算函数耗时和内存?
1 2 3 |
var beforeTime = System.DateTime.Now; var afterTime = System.DateTime.Now; Debug.LogError($"耗时{afterTime.Subtract(beforeTime)}"); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public static double TimeWatcher(Action action) { System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch(); watch.Start(); action(); watch.Stop(); var useTime = (double) watch.ElapsedMilliseconds/1000; return useTime; } public static long MemoryWatcher(Action action) { long start = GC.GetTotalMemory(true); action(); GC.Collect(); GC.WaitForFullGCComplete(); long end = GC.GetTotalMemory(true); long useMemory = (end - start)/(1024*1024); return useMemory; } |