Unity提供了强大的Profiler工具,它专门查看每一帧内存的占用以及加载的耗时。此外,它还提供了自定义接口,开发者也可以自定义一个查看区间。
Unity还提供了Frame Debugger,它可以查看每一帧渲染的详细过程。如果DrawCall很多的话,可以参照它来想办法合并。
Unity有丰富的资源加载接口,我们可以在编辑模式下加载资源,在运行模式下加载本地资源和下载资源。
编辑模式加载资源
编辑器模式下Unity可以访问加载到硬盘上的任意资源,这对拓展引擎内置的编辑器是非常好的。引擎可以读取任意资源来丰富编辑器。
编辑模式下的资源可分为两类:一类是引擎可识别的资源,例如Prefab、声音、视频、动画和UI等;另一类是引擎无法识别的资源,例如外部导入资源,这类资源需要通过第三方工具将它的信息解析出来,最终组织成引擎内可识别资源才可以使用。
Unity标志性的类AssetDatabase
,它专门负责读取工程内的资源。它读取的资源必须放在项目的Assets目录下,其他位置的资源可以尝试用File
类来读取

我们在场景中随便放一个cube(没有父对象),坐标设置为(7,7,-6),并拉取放在Assets文件夹下成为一个prefab。然后删掉

我们使用以下代码,在选中场景的摄像机的情况下,执行Chapter11——LoadWrong
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| using UnityEngine; using UnityEditor;
public class GetPrefabWrong { [MenuItem("Chapter11/LoadWrong")] static void MyLoad() { GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Cube.prefab"); GameObject go = GameObject.Instantiate<GameObject>(prefab); go.transform.SetParent(Selection.activeTransform); } [MenuItem("Chapter11/LoadWrong",true)] static bool ValidateMyLoad() { if (!Selection.activeObject) return false; return true; } }
|
我们会发现GameObject.Instantiate
方法只是做了一个克隆,我们在Main Camera下挂载的并不是正常的Prefab(蓝色的)。而使用Transform.SetParent
默认会继承世界坐标,这个Cube(Clone)的坐标变成了(7,6,4)

之前Cube的坐标为(7,7,-6)世界坐标,这些已经在prefab中记录下来了,如果我们现在直接把这个prefab拖拽进Hierarchy里面,那么它和Cube(Clone)的位置就会重合。因为Main Camera的位置坐标为(0,1,-10)世界坐标,Cube(Clone)变成了Main Camera的子级,Cube(Clone)的坐标就变成了相对坐标,为了保持Cube之前在世界坐标的位置,Cube(Clone)的坐标被换算成了(7,6,4),这是(7,7,-6)减去(0,1,-10)得来的
把上面代码中最后改为go.transform.SetParent(Selection.activeTransform,false);
,就能让Cube(Clone)无视之前的世界坐标,不进行坐标转换的操作,依然是(7,7,-6);如果我们把Cube.prefab直接拖拽到Main Camera下作为它的子级,也是不进行坐标转换的操作
实例化Prefab
在编辑模式下,实例化Prefab需要使用PrefabUtility.InstantiatePrefab()
,这样实例化后的Prefab就是“蓝色”的了,并且使用方法中提供的Transform参数,就能自动绑定父级,这个绑定父级也是不会做世界坐标的换算。除了Prefab以外,FBX也属于一种特殊的引用关系的资源,它也可以使用这种方法实例化,这样会保持它的一些引用关系。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| using UnityEngine; using UnityEditor;
public class GetPrefabRight { [MenuItem("Chapter11/LoadRight")] static void LoadPrefabRight() { GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Cube.prefab"); GameObject go = PrefabUtility.InstantiatePrefab(prefab,Selection.activeTransform) as GameObject; } [MenuItem("Chapter11/LoadRight", true)] static bool ValidateLoadPrefabRight() { return Selection.activeGameObject != null && !EditorUtility.IsPersistent(Selection.activeGameObject); } }
|

如果我们使用Transform.SetParent
方法而不是在PrefabUtility.InstantiatePrefab
中设定父对象,就能保持原位了。
创建Prefab
使用PrefabUtility.SaveAsPrefabAssetAndConnect
方法可以创建Prefab,并且自动将Prefab替换创建prefab的对象。
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
| using UnityEngine; using UnityEditor;
public class CreateToPrefab { [MenuItem("Chapter11/CreatePrefabs")] static void CreatePrefabs() { GameObject[] objectArray = Selection.gameObjects;
foreach (GameObject gameObject in objectArray) { string localPath = "Assets/" + gameObject.name + ".prefab";
localPath = AssetDatabase.GenerateUniqueAssetPath(localPath);
PrefabUtility.SaveAsPrefabAssetAndConnect(gameObject, localPath, InteractionMode.UserAction); } }
[MenuItem("Chapter11/CreatePrefabs", true)] static bool ValidateCreatePrefabs() { return Selection.activeGameObject != null && !EditorUtility.IsPersistent(Selection.activeGameObject); } }
|
更新Prefab
还是使用上面的代码,进行相应的修改即可
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
| using UnityEngine; using UnityEditor;
public class RefreshThePrefab : MonoBehaviour { [MenuItem("Chapter11/RefreshPrefabs")] static void CreatePrefabs() { GameObject[] objectArray = Selection.gameObjects;
foreach (GameObject gameObject in objectArray) { string localPath = "Assets/" + gameObject.name + ".prefab"; PrefabUtility.SaveAsPrefabAssetAndConnect(gameObject, localPath, InteractionMode.UserAction); } }
[MenuItem("Chapter11/RefreshPrefabs", true)] static bool ValidateCreatePrefabs() { return Selection.activeGameObject != null && !EditorUtility.IsPersistent(Selection.activeGameObject); } }
|
卸载资源
在编辑模式下,使用GameObject.DestroyImmediate()
方法来卸载游戏对象,这个方法第二个参数是allowDestroyingAssets,设置为true的话意思是允许销毁资产引用占用的内存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| using UnityEngine; using UnityEditor;
public class UninstallResource { [MenuItem("Chapter11/UninstallResourse")] static void UninstallObject() { if (Selection.activeTransform) { Object.DestroyImmediate(Selection.activeTransform.gameObject,true); } } [MenuItem("Chapter11/UninstallResourse",true)] static bool ValidateUninstallObject() { return Selection.activeGameObject != null && !EditorUtility.IsPersistent(Selection.activeGameObject); } }
|
游戏对象与资源的关系
游戏对象与资源是一种引用关系。例如一个模型是由贴图和Mesh组成,将它拖入场景中时,生成的游戏对象就会引用这两种资源。当程序调用GameObject.Destroy()
或者GameObject.DestroyImmediate()
方法时,只会卸载掉它的对象,它身上引用的贴图和Mesh还在内存中。
我们来实验这个场景
新建PrefabDeleteTest
脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| using UnityEngine;
public class PrefabDeleteTest : MonoBehaviour { public GameObject prefab; GameObject temp = null; private void Start() { temp = Instantiate<GameObject>(prefab); } private void OnGUI() { if(GUILayout.Button("Delete Prefab")) { DestroyImmediate(temp, true); } } }
|
挂载这个脚本,我们在Assets里面找一个Prefab放在脚本的引用上,然后运行,按Delete Prefab就会删除它

如果我们的代码这么写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| using UnityEngine;
public class PrefabDeleteTest : MonoBehaviour { public GameObject prefab; private void Start() { Instantiate<GameObject>(prefab); } private void OnGUI() { if(GUILayout.Button("Delete Prefab")) { DestroyImmediate(prefab, true); } } }
|
我们点击Delete Prefab就会报错

也就是说,我们实例化一个Prefab之后,在Scene中出现的是一个克隆,我们要删也是删掉这个克隆(上上个代码中的temp对象),而不能删除Prefab最原始的“root game GameObject”
Unity这么做是有原因的:很多游戏对象的加载与卸载是很频繁的,如果每次都将引用的资源清理掉,无疑会造成IO的阻塞。
但是如果长期不卸载这些资源,那么内存必然涨上去,所以Unity又提供了一个方法来自动卸载无用资源。其中,无用资源表示没有被别的对象或者代码引用的资源。在编辑器模式下,调用EditorUtility.UnloadUnusedAssetsImmediate()
即可。
1 2 3 4 5 6 7 8 9 10
| using UnityEditor;
public class ReleaseUnusedAssets { [MenuItem("Chapter11/UnloadUnusedAssetsIm")] static void ReleaseUnusedAssetsImme() { EditorUtility.UnloadUnusedAssetsImmediate(); } }
|