脚本编译

编译规则

在编辑器下,每次修改完代码后,就会自动开始编译,最终所有的代码都将编译成DLL文件,在项目工程文件夹/Library/ScriptAssemblies目录下,称为预定义程序集

其中有四个主要的程序集,他们有固定的编译顺序,先编译的脚本无法访问后编译的脚本(如果没有符合编译阶段的脚本,Unity不会创建对应的程序集),程序集的创建是根据特殊文件夹内的脚本决定的。

阶段 程序集名称 文件夹规则
1 Assembly-CSharp-firstpass 在 Standard Assets、Pro Standard Assets 和 Plugins 文件夹内的脚本。
2 Assembly-CSharp-Editor-firstpass 名为 Editor 的文件夹(位于名为 Standard Assets、Pro Standard Assets 和 Plugins 的顶级文件夹中的任意位置)中的 Editor 脚本。
3 Assembly-CSharp 不在名为 Editor 的文件夹中的所有其他脚本。
4 Assembly-CSharp-Editor 位于名为 Editor 的文件夹中的脚本

可以参考官方文档

特殊文件夹名称 - Unity 手册 (unity3d.com)

特殊文件夹和脚本编译顺序 - Unity 手册 (unity3d.com)

优化编译

游戏代码大致可以分成两类,框架类代码和逻辑性代码。

框架类代码并不需要经常修改并且不需要访问逻辑性代码,可以把框架类代码放在Plugins目录下。

逻辑性代码如果量非常大,还是会造成编译慢的问题,此时我们可以把部分CS代码预先编译成DLL。

编译DLL

暂时还不理解,先把书上的摘抄下来

.Net可以把C/C++语言编译进DLL,但是游戏发布后,有的平台是识别不了的,例如移动平台,此时如果编译DLL只能编译C#代码。

需要在macOS系统中打开终端,输入编译指令,DLL编译完成后直接拖拽到项目中就可以了。

1
2
mcs -r:/Application/Unity/Unity.app/Contents/Managed/UnityEngine.dll
-target:library -out:test.dll *.cs
  • UnityEngine.dll 编译所依赖的DLL文件
  • -target:library 生成Library1类型
  • -out:test.dll 最终生成DLL的保存目录
  • *.cs 表示当前目录下所有的C#代码,如果有多个目录多个文件,可以以空格分开

脚本跨平台

Unity的两个核心DLL库:UnityEditor.dll和UnityEngine.dll

Unity编辑器(只支持Windows、macOS、Linux)大部分是由C#编写的并且编译在UnityEditor.dll中。

Unity引擎(UnityEngine.dll)通过Mono实现了跨平台,Unity只是把C#接口封装到了UnityEngine.dll中,至于更底层的实现,则是由这个DLL再去调用C++。

拿移动平台来说,编译C++的方式是不同的,Android需要编译成.so,iOS编译在.a中,Unity会针对每个平台编译出这份核心库,从而就实现了跨平台。

使用IL2CPP,将DLL代码直接转化成CPP,效率再次上升一个层次

程序集定义

指定某个文件夹(包括子文件夹)单独生成DLL,进一步简化编译代码的数量。

只需要在需要编译的文件夹内,点击创建“Assets-Create-Assembly Definition”,然后按照命名生成对应的Assembly。如果在其他文件夹内编译相同的Assembly,那么就创建Assembly Definition reference Asset

外部代码是可以直接访问自定义的DLL的,但是自定义的DLL无法互相直接访问,需要设置依赖关系

如果子文件夹内也设置了自定义的Assembly Definition或者Reference,会另外再编译一份DLL。

通过程序集定义这个功能,可以很好地将代码模块化分类管理。将每个独立的功能划分到一个程序集中,相互编译互不影响

日志

常用Debug类

  • Debug.Log(“Log”);
  • Debug.LogError(“LogError”);
  • Debug.LogWarning(“LogWarning”);

在开发阶段,多打印日志可以方便地查看程序的行为,但是一旦发布以后,要把日志关闭掉以避免不必要的性能开销。

可以在游戏的主要脚本中的Awake方法中输入

1
2
3
#if !UNITY_EDITOR
Debug.unityLogger.logEnabled = false;
#endif

错误日志的现场往往非常珍贵,我们需要尽可能地将错误日志保存下来。在移动平台,保存和提取日志并不方便,可以监听错误以及异常,并及时将其打印在屏幕上。

监听Application.logMessageReceived事件即可捕捉日志,最终在OnGUI中打印在屏幕上

新建DisplayLogOnGUI脚本

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
using System.Collections.Generic;
using UnityEngine;

public class DisplayLogOnGUI : MonoBehaviour
{
//错误详情
private List<string> m_LogEntries = new List<string>();
//是否显示错误窗口
private bool m_IsVisible = false;
//窗口显示区域
private Rect m_WindowRect = new Rect(0, 0, Screen.width, Screen.height);
//窗口滚动区域
private Vector2 m_scrollPositionText = Vector2.zero;
void Start()
{
//监听错误
Application.logMessageReceived += (condition, stackTrace, type) =>
{
if (type == LogType.Exception || type == LogType.Error)
{
if (!m_IsVisible)
{
m_IsVisible = true;
}
m_LogEntries.Add(string.Format("{0}\n{1}", condition, stackTrace));
}
};

//创建异常以及错误
for (int i = 0; i < 10; i++)
{
Debug.LogError("生成错误");
}
int[] a = null;//正确写法应该是= new int[10];目前 = null;是初始化,没有实例化
a[1] = 100;

}
private void OnGUI()
{
if (m_IsVisible)
{
m_WindowRect = GUILayout.Window(0, m_WindowRect, ConsoleWindow, "Console");
}
}
//日志窗口
void ConsoleWindow(int windowID)
{
GUILayout.BeginHorizontal();
if (GUILayout.Button("Clear", GUILayout.MaxWidth(200)))
{
m_LogEntries.Clear();
}
if (GUILayout.Button("Close", GUILayout.MaxWidth(200)))
{
m_IsVisible = false;
}
GUILayout.EndHorizontal();

m_scrollPositionText = GUILayout.BeginScrollView(m_scrollPositionText);
foreach (var entry in m_LogEntries)
{
Color currentColor = GUI.contentColor;
GUI.contentColor = Color.red;
GUILayout.TextArea(entry);
GUI.contentColor = currentColor;
}
GUILayout.EndScrollView();
}
}

窗口打印错误日志

脚本调试

在VS中添加断点后,点击“附加到Unity”,就可以开启Unity的Debugger模式,启动Debugger模式会重编译所有的代码,需要在Unity自行选择当前session还是整个project

鼠标悬停在断点处就能查看变量赋值情况,可以逐语句或逐过程慢慢调试,在下方可以自行输入检索各种能访问的变量。比如GameObject.name或者GameObject.transform.positon