加快编程流程

我们最近发表了两篇博文:加快编辑器工作流的五种方法和加快艺术工作流,这两篇博文都是基于我们为专业开发者编写的电子书:使用Unity 2020 LTS提高生产力的70+技巧。本文属系列第三篇也是最后一篇,我们将重点介绍能让编程事半功倍的工作流程和功能。先来看看怎样减少测试时的编译时间。
项目在进入Play模式时会与正常运行版一样的方式运行。在退出Play模式时,编辑器内的所有改动都会被重置。
在进入Play模式之际,Unity会执行两段重要的操作:
- Domain Reload(重新加载域): Unity将完成当前脚本的备份、卸载和重现。
- Scene Reload(重新加载场景): Unity摧毁并重新加载场景。
随着脚本和场景变得越来越复杂,这两个流程所花的时间也会越来越多。
这时,如果脚本不再有任何改动,你可以在Enter Play Mode Settings(Edit > Project Settings > Editor)来缩短编译时间。Unity可以禁用Domain Reload、Scene Reload或同时禁用两者,让Play Mode的进入和退出速度更快。
如果你打算继续修改脚本,别忘了重新启用Domain Reload就行了。同样地,如果场景层级视图有了改动,你也应该重新启用Scene Reload,否则引擎可能会出现意想不到的行为。

一个程序集是一种C#代码的集合,是多个类型(Type)和资源相互关联形成的功能逻辑单元。默认情况下,Unity会将几乎所有的游戏脚本编译到预设的Assembly-CSharp.dll程序集中。这种方法非常适合小型项目,但它有一些缺点:
- 每当有脚本被改动,Unity需要重新编译所有其他脚本。
- 某个脚本可以访问任意其他脚本中定义的类型。
- 应用每次部署都需编译一遍全部的脚本。
我们可以通过自定义程序集,来提高其模块化程度和重复使用性。自定义的程序集可将部分脚本排除在默认程序集外,还能控制脚本的访问权限。
你可以像上图一样把脚本分成多个程序集。这里,Main程序集中的任意改动都不会影响到Stuff中的代码。同样的,不依赖其它程序集的Library可以很简单地在其他项目中再度使用。
.NET的程序集自带了各类C#程序集的一般定义。请参考Unity文档的Assembly Definitions部分来详细了解如何在Unity中编写自己的程序集。
你是否遇到过在新建脚本后需要编写多段重复代码的情况?是否会机械地加上命名空间或删除Update函数?如果有,那就来试试脚本模板功能吧。用模板来减少几次键盘的敲打,统一整个团队的编程风格。
实际上,在你每次创建新脚本或着色器时,Unity本身就会使用模板。模板保存于
%EDITOR_PATH%\Data\Resources\ScriptTemplates:
- Windows:C:Program Files\Unity\Editor\Data\Resources\ScriptTemplates
- Mac:/Applications/Hub/Editor/[版本号]/Unity/Unity.app/Contents/Resources/ScriptTemplates
而这个是引擎默认的MonoBehaviour模板:
81-C# Script-NewBehaviourScript.cs.txt
除此之外,还有着色器、其它脚本和程序集定义的模板。
若想为特定项目创建的脚本模板,你可以新建一个Assets/ScriptTemplates文件夹,将模板复制到文件夹下来覆盖默认模板。
你也可以直接修改所有项目的默认脚本模板,但在修改之前请备份好原文件。
默认的81-C# Script-NewBehaviourScript.cs.txt文件看起来是这样的:
请注意这两个关键词:
- #SCRIPTNAME#代表了创建时输入的文件名或默认文件名(如NewBehaviourScript)。
- #NOTRIM#代表了大括号内的空行。
在修改后,你需要重新启动Unity编辑器,接着所有新建的MonoBehaviour脚本便都会应用新模板了。
你也可以以类似的方式修改其他模板。注意在项目文件夹以外的地方分别保留一份原模板和新模板,以便不时之需。
Unity带有的多种特性,可加在类、属性或函数的开头,来让其显现特殊行为。C#的特性需加在一个方括号内。
下方为几种脚本中常见的特性:
除了上方例子外,Unity的C#特性还有许多。你可以在保留值的前提下为变量更名,或者在没有空游戏对象的情况下调用逻辑。请在文档的Scripting API部分查看所有特性及其功能。
你甚至可以创建并应用自己的PropertyAttribute。
Unity最强大的功能之一是其可扩展的编辑器。使用UI Toolkit或IMGUI模式可用于制作编辑器UI、自定义编辑器窗口和检视器。
UI Toolkit的制作流程类似于标准网页开发,它使用的UXML markup语言模仿了HTML和XML,可用于编写UI和可重复使用的UI模板。你还可以用Unity Style Sheets (USS)来修改UI的视觉风格和行为。
你也可以使用即时模式的UI,即上文提到的IMGUI。首先在脚本中继承Editor基本类,然后在函数前加上CustomEditor特性。
这些都是自定义检视器的方法。
下方的自定义编辑器改变了MyPlayer脚本检视器界面:
请在Creating User Interfaces (UI)中详细了解怎样使用UI Toolkit或IMGUI自定义编辑器。请观看Gettings Started with Editor Scripting教程中快速了解UI Toolkit。
Addressable Asset System可简化游戏资源的管理方式。你可以将场景、预制件、文本等任意资源标记为“Addressable”,并加上一个独特的名称,然后用这个名称在任意位置调用资源。
在游戏和资源间建立一层抽象层可以让DLC打包等资源处理相关的流程更为简单,也能方便系统引用本地或网络上的资源。
在本例中,Addressables会跟踪所有的预制件。
要想使用系统,你须先在Package Manager中安装Addressables软件包,为项目做好基本设置。安装完成后,项目的每一个资源或预制件都应该有一个“Addressable”选项。勾选检视器资源名称下的选项便可为其分配一个唯一的默认地址。
启用Addressable后生成的默认Addressable Name
在设置完成后,资源将出现在Window > Asset Management > Addressables > Groups窗口中。
你可以在Addressables Groups中查看每个资源的地址及路径。
系统还带有另一个便利功能:你可以分开为每个资源修改Address字段的地址,也能一次性修改全部的地址。
你可以在菜单中单次修改Addressable Name,或单独进行修改。
默认构建脚本可生成Addressable Group资源集。
将资源捆绑成集合,便将资源集托管至远程服务器,或在本地进行分享。不论资源存储在哪个路径,系统都可以使用Addressable Name字符串检索文件。
Addressable资源现在支持使用Addressable API进行调用。
对比之下,如果没有Addressable,你需要遵循以下步骤来实例化预制件:
这样做的缺点在于所有预制件(如prefabToCreate),不论有没有必要,都会被加载到内存中。
如果使用Addressables,则:
这里,系统会使用地址字符串来加载资源,意味着预制件只会在需要的时候被加载到内存中(我们在CreatedPrefabWithAddress方法下调用了Addressable.Instantiate)。此外,你也可以使用Addressable来控制上层资源(如某预制件的父对象)的引用,在资源没用后自动在内存中卸载集合及下属资源。
Tales from the optimization trenches: Saving memory with Addressables一文中包含了怎样使用Addressable Group优化内存的例子,而Addressables: Introduction to concepts教程则能让你快速了解如何在项目中使用Addressable Asset System。
如果你正在运营一款在线游戏,你可以将Addressables与Unity的Cloud Content Delivery(云端资源分发,CCD)组合使用。由Addressable系统存储和分类的游戏资源可由系统自动检索、调用,这些资源接着可借助CCD直接推送到客户端,无需修改任何游戏代码。游戏程序可以有更小的体积,玩家们也不必在每次重大更新时重新下载、安装新的客户端。请在这篇博文中详细了解Addressable与CCD的整合方法。
Platform Dependent Compilation(平台独立编译)功能允许你针对特定的平台划分出需编译、执行的代码。
例:使用#if编译指令运行具体平台的#define指令
DEVELOPMENT_BUILD #define可用于识别脚本是否运行在设置了特定Development Build的运行版中。
你可以根据特定的Unity版本和/或编程后端来有选择地进行编译,甚至能在编辑器中编写自定义的#define指令。打开Player Settings下的Other Seetings,找到Scripting Define Symbols来创建自定义指令。
请在手册的Platform Dependent Compilation一节详细了解Unity的前处理器指令。
ScriptableObject是一种可在脚本外储存大量数据的数据容器。此类对象会尽量降低数据重复性,减少项目的内存占用。请在我们的电子书或在说明文档中详细了解ScriptableObject的使用。
请观看Better Data with ScriptableObjects in Unity教程来快速学习可编程对象的妙用,或观看Achieve Better Scene Workflow with ScriptableObjects来了解怎样用可编程对象实现场景管理。
优化技巧
我们推荐使用MessagePack、Protocol Buffers等二进制格式来保存数据,不要使用JSON、XML等文本类数据格式。二进制格式相较于后者可节省内存占用、性能问题更少。
Unity支持以下几种集成开发环境(IDE):
- Visual Studio:Windows与macOS最常用的IDE
- Visual Studio Code:Windows、macOS、Linux
- JetBrains Rider:Windows、macOS、Linux
所有三种IDE的整合文件都打包在了一个软件包中,包可在Package Manager中下载。
IDE整合文件包
Visual Studio默认会与Unity一起安装。如果你想使用另一种IDE,可在Unity > Preferences > External Tools > External Script Editor下选取自己的代码编辑器。
基于ReSharper打造的Rider在兼具前身的大部分功能外,还支持Unity(C# 8.0).NET 4.6脚本运行时的C#调试。更多详情请参阅JetBrains文档的Rider for Unity部分。
VS Code是一款免费的代码编辑器,支持脚本调试、单任务运行和版本控制。若想在Unity中使用VS Code,请先安装Mono(macOS、Linux)、Visual Studio Code C#和Visual Studio Code Debugger for Unity(尚无官方支持)。
每种IDE都有自己的优点,请在集成开发环境(IDE)支持一节详细了解哪个IDE最适合你。
请在我们的电子书中学习几种有益于开发的快捷键,观看Visual Studio Tips and Tricks to Boost Your Productivity来学习怎样优化自己的编程流程。
若想继续探索JetBrains Rider,请观看Fast C# scripting in Unity with JetBrains Rider,或在这里学习几种Rider编辑器的使用技巧。
Unity Debugger支持在运行模式下调试场景中的C#代码。你可以在代码编辑器中加入断点来检查某个时刻的脚本和变量状态。
要使用该功能,请在Unity Editor Status Bar的右下角将Code Optimization模式切换为Debug,或在Edit > Preferences > General > Code Optimization On Startup选项中将启动时的默认模式改为Debug模式。
Debug模式
你可以在代码编辑器中设定断点来让Debugger暂停运行:在需要设置断点的位置点击左侧侧边栏选项,或在右键菜单中进行选择。设置断点后,目标行的行数左侧会出现一个红圈(如下图)。
设置断点
在编辑器中选择Attach to Unity,然后在Unity编辑器中运行项目。
将Debugger整合至Unity
应用这时会在断点处暂停运行,让你有机会检查变量、检测是否有意外行为出现。
变量调试
如上方所示,你可以在调试时按步骤监测事件列表,检查脚本变量。
Debug控制按钮:Continue Execution(继续执行)、Step Over(跳过)、Step Into(进入)和Step Out(跳出)
你可以使用Continue Execution(继续执行)、Step Over(跳过)、Step Into(进入)和Step Out(跳出)按钮来控制试运行流程。
Stop控制按钮
Stop按钮可停止/恢复代码运行。
你也可以在Unity Player(运行版)中调试脚本代码。在构建Player前,请先在File > Build Settings中启用Development Build与Script Debugging两个选项。勾选Wait for Managed Debugger让Player在运行代码前加载Debugger。 要想将代码编辑器接入到Player,请先设定设备的IP地址(或设备名)和Player的端口,然后打开Visual Studio并启用Attach To Unity选项。
Unity提供的Debug类可显示出编辑器运行信息。你可以学习怎样在控制台打出消息或警告、在场景和游戏视图绘制示意线,或使用脚本来暂停编辑器的运行模式。此外还有一些其他技巧:
Debug.Break可暂停代码运行。如果游戏的暂停时机难以把握,你可以使用该方法来暂停并检查某个值。
Debug.Log、Debug.LogWarning和Debug.LogError是在控制台显示消息的常用方法。Debug.Assert会在特定条件无法满足时输出讯息,但方法仅在有明确的UNITY_ASSERTIONS定义时才会生效。 在控制台中记录消息、警告和错误。
你可以将某个对象传入Debug.Log来作为内容,点击这条消息,Unity将在层级视图中高光显示该对象。点击这条消息,Unity将在层级视图中高光显示该对象。
你可以使用Rich Text文本来完善Debug.Log的消息,让错误报告更为清晰。
Unity不会自动剥离非开发版的Debug API。如果想要精简Debug相关的代码,你可以将Debug Log方法调用放到自定义的方法下,为其加上[Conditional]特性。 要想一次性编译出所有的Debug消息,请移除Player Settings下对应的Scripting Define Symbol,这一步类似于将Debug代码放到#if……#endif前处理器代码块中。 更多详情请查看这份优化指南。
如果你想调试物理模拟,可以使用Debug.DrawLine和Debug.DrawRay来可视化射线。
Debug.DrawLine
如果你仅想在Development Build中运行代码,可以检测Debug.isDebugBuild是否为真。
你可以用Application.SetStackTraceLogType或Player Settings对应的选项来确定哪条控制台消息需要跟踪堆栈。堆栈跟踪信息有一定作用,但信息生成缓慢,还会产生垃圾数据。
控制台的每条消息默认会显示两行信息,你可以将两行合并成一行来提高可读性,如下。你可以将两行合并成一行来提高可读性,如下图所示。
Console Log Entry选项
或者,你可以加上更多行让消息更长。
Unity右下方的编译进度条不太容易被发现,你可以在编辑器脚本中调用EditorApplication.isCompling在编译期间生成一个悬浮窗来查看进度。在编译期间生成一个悬浮窗来查看进度。
使用MenuItem来初始化窗口,使用MenuItem来初始化窗口,你还可以用自己的GUIStyle来定制外观。
Unity整合了两款版本控制系统:Perforce与Plastic SCM。打开Player Settings > Editor,在Version Control一栏下配置服务器(Perforce还要求有证书)来添加版本控制服务器。
版本控制的项目配置。
Plastic SCM Cloud Edition免费提供5GB容量与三个席位。Plastic SCM可以同步所有的改动,并支持在Unity中直接查看项目修改历史。请在此处了解Plastic SCM的最新更新。
你也可以使用Git等第三方系统,也能用Git LFS(Large File Support)来高效地管理图像、音效等大型资源。 安装GitHub for Unity插件来享受GitHub托管服务器的便利,这款开源插件能让你在Unity中浏览修改历史、创建分支、提交改动并将代码推送至GitHub。
你还可以使用Unity的 .gitignore 文件来指明需要或不需要上传至代码库的文件,有选择地进行更新。
GitHub for Unity插件
Unity Teams是另一个辅助团队协作的选择。软件可将整个项目上传至云端,云端文件即可用作备份,也可四处分享,让项目的保存、分享和同步更为方便。
请在这里查看系列前两篇博文:5 ways to speed up your workflows in the Editor、Speed up your artist workflows。你也能下载Unity 2020 LTS使用指南,免费学习70多种提高效率、可操作性强的技巧。
如果你有其他想要了解的话题或内容,请在评论区留言,我们也欢迎大家签来分享你自己的高生产力技巧。