Unity编辑器的源码

例子:查找ManagedStaticReference()静态引用

在Profiler里面,有的时候会看到某个资源一直无法GC,其原因可能是被static静态引用了。

首先在Assets里面新建Resources文件夹,在里面放一个hierarchy的游戏对象,命名为“prefab”,然后在Scripts文件夹里新建一个名为HardReferenceResourse脚本并挂载在场景中的某个物体上

1
2
3
4
5
6
7
8
9
10
using UnityEngine;

public class HardReferenceResourse : MonoBehaviour
{
public static GameObject prefab;
private void Start()
{
prefab = Resources.Load<GameObject>("prefab");
}
}

运行游戏后,使用下面的脚本在Unity中使用反射来递归查询这个资源被代码哪里static强引用了。

在Editor文件夹中新建CheckoutWhichStatic脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Reflection;
using System;
using System.Text;

public class CheckoutWhichStatic : Editor
{
[MenuItem("Root/Report/脚本Static引用")]
static void StaticRef()
{
//静态引用
LoadAssembly("Assembly-CSharp-firstpass");
LoadAssembly("Assembly-CSharp");

}

static void LoadAssembly(string name)
{
Assembly assembly = null;
try
{
assembly = Assembly.Load(name);
}
catch (Exception ex)
{

Debug.LogWarning(ex.Message);
}
finally
{
if(assembly != null)
{
foreach (Type type in assembly.GetTypes())
{
try
{
HashSet<string> assetPaths = new HashSet<string>();
FieldInfo[] listFieldInfo = type.GetFields(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
foreach (FieldInfo fieldInfo in listFieldInfo)
{
if (!fieldInfo.FieldType.IsValueType)
{
SearchProperties(fieldInfo.GetValue(null), assetPaths);//返回assetpaths的方式存疑
}
}
if (assetPaths.Count > 0)
{
StringBuilder sb = new StringBuilder();
sb.AppendFormat("{0}.cs\n", type.ToString());
foreach (string path in assetPaths)
{
sb.AppendFormat("\t{0}\n", path);
}
Debug.Log(sb.ToString());
}
}

catch(Exception ex)
{
Debug.LogWarning(ex.Message);
}
}
}
}
}
static HashSet<string> SearchProperties(object obj,HashSet<string> assetPaths)
{
if(obj != null)
{
if(obj is UnityEngine.Object)
{
UnityEngine.Object[] depen = EditorUtility.CollectDependencies(new UnityEngine.Object[] { obj as UnityEngine.Object });
foreach (var item in depen)
{
string assetPath = AssetDatabase.GetAssetPath(item);
if (!string.IsNullOrEmpty(assetPath)) { assetPaths.Add(assetPath); }
}
}
else if(obj is IEnumerable)
{
foreach (object child in (obj as IEnumerable))
{
SearchProperties(child, assetPaths);
}
}
else if(obj is System.Object)
{
if (!obj.GetType().IsValueType)
{
FieldInfo[] fieldInfos = obj.GetType().GetFields();
foreach (FieldInfo fieldInfo in fieldInfos)
{
object o = fieldInfo.GetValue(obj);
if(o != obj)
{
SearchProperties(fieldInfo.GetValue(obj), assetPaths);
}
}
}
}
}
return assetPaths;
}
}

image-20220102220711851

在开发中,一定要避免A引用B,B引用C,C引用A这种情况,使用上文的方式检测不出这种情况

UIElements

这一 部分现在已经整合到UI Toolkit里面,今后单独学习整理

查询系统窗口

使用以下脚本来找到Unity Editor某些窗口的源码位置,方便自定义窗口时阅读引用这些窗口(结合前面的Unity源码阅读工具)

演示代码

1
2
3
4
5
6
7
8
[MenuItem("Root/GetEditorWindow")]
static void GetEditorWindow()
{
foreach(var item in Resources.FindObjectOfTypeAll<EditorWindow>())
{
Debug.Log(item.GetType().ToString());
}
}

比如在Unity运行游戏时,打开Profiler窗口,会在Console窗口中弹出“UnityEditor.ProfilerWindow”我们就能知道这个窗口的代码是写在哪里的

自定义资源导入类型

Unity可以自动识别大部分的文件,不过我们可以手动创建自己的文件类型比如说.atao.atao2017等,首先我们先新建一个文件,键入以下内容并把后缀改为.atao,这本质上属于Json文件,我们在Unity中使用JSON的方式读取它

1
{"x":100,"y":200,"z":1}

在Editor文件夹中新建CustomFileImporter脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using UnityEngine;
using UnityEditor.AssetImporters;
using System.IO;

//监听的后缀名
[ScriptedImporter(1, "atao")]
public class CustomFileImporter : ScriptedImporter
{
//监听自定义资源导入
public override void OnImportAsset(AssetImportContext ctx)
{
//创建立方体对象
var cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
//提取参数
var position = JsonUtility.FromJson<Vector3>(File.ReadAllText(ctx.assetPath));

cube.transform.position = position;
cube.transform.localScale = Vector3.one;

//将立方体绑定到对象身上
ctx.AddObjectToAsset("obj", cube);
ctx.SetMainObject(cube);

//添加材质
var material = new Material(Shader.Find("Standard"));
material.color = Color.red;
ctx.AddObjectToAsset("material", material);

var tempMesh = new Mesh();
DestroyImmediate(tempMesh);
}

}

将自己的.atao文件放进Unity里面,

image-20220103154930666

image-20220103154951088