【Unity】程序集

1 前言

        后续写下资源管理这块的内容,那么程序集是必不可少的。其可以实现对代码的物理划分,还是非常有用的。本文主要介绍Unity中程序集如何使用。

2 程序集

2.1 什么是程序集

        程序集是.net中的概念,程序集可以看作是给一堆相关类打一个包,相当于java中的jar包。命名空间大家都熟悉,与之比较,命名空间用于对类型进行逻辑分组,程序集则是程序的物理分组,对应于一个dll或exe文件。
        官方:程序集是 C# 代码库,其中包含由脚本定义的已编译类和结构并且还定义了对其他程序集的引用。有关 C# 中的程序集的一般信息,请参阅 [.NET 中的程序集]。

2.2 预定义程序集

        这里需要首先了解预定义程序集。

        默认情况下,Unity 几乎将所有游戏脚本都编译到预定义程序集Assembly-CSharp.dll中(Unity 还会创建[一些较小的专用预定义程序集])。这里主要记住Assembly-CSharp.dll即可。

2.3 为什么要划分程序集

        若不划分程序集,则我们的脚本基本都是在预定义程序集Assembly-CSharp.dll中的,随之而来的有什么问题呢?这种安排对于小型项目而言可以接受,但当项目大了,代码文件多了,缺点就出来了:

  • 每次更改一个脚本时,Unity 都必须重新编译所有其他脚本(因为同属一个程序集),从而增加迭代代码更改的整体编译时间。
  • 任何脚本都可以直接访问任何其他脚本中定义的类型(因为同属一个程序集),这样可能更加难以重构和改进代码。
  • 所有脚本都针对所有平台进行编译(因为同属一个程序集)。

        通过定义程序集来划分,可以促进代码的模块化、可重用性。代码文件划分到不同程序集中,修改代码文件时不用重新编译其他所有文件,且程序集之间代码文件中类型的访问可通过依赖来决定是否可以访问,另外可根据程序集来划分所针对的平台。

如图,由一个程序集划分为了多个程序集。其中Main.依赖Stuff.dll,而Stuff.dll依赖Library.dll,这里先简单了解下依赖。

2.4 划分脚本程序集

2.4.1 查看所属程序集

        在Unity项目中,我们也是可以给脚本划分程序集的。如图。

创建一个脚本后,选中,我们可以在Inspector面板中看到其所属程序集的信息。图中test脚本所属程序集为Assembly-CSharp.dll,也是脚本默认的程序集。这些.dll(程序集)文件可在如下路径中找到。

        另外,像项目中自带的Packages(如下图)中的内容也都是以程序集的方式存在的。都可以在上述路径文件夹中找到对应的.dll文件。

2.4.2 划分程序集

        划分方式也很简单,只需要在脚本对应目录下创建一个程序集文件,那么当前目录以及子目录下的脚本都会被划分到此程序集中。如图,我们在test.cs同目录下创建一个程序集文件,并命名为NewDll。

这时,我们再选中test.cs,可以看到其所属程序集发生了变化。

在ScriptAssemblies文件中,Assembly-CSharp.dll消失了,取而代之的是我们的NewDll.dll文件(注意,程序集文件NewDll和程序集NewDll.dll是两个概念,分清楚),Filename表示其被分到程序集NewDll.dll中,Definition表示所属程序集文件为NewDll。

        若我们想划分多个程序集,则创建多个程序集文件即可,比如这里NewDll程序集文件是创建在Scripts文件夹中的,那么此文件夹以及其子文件夹中所有的脚本都会属于Newdll.dll。比如我们在其中创建一个One文件夹,并在其中创建一个one.cs,那么此脚本依旧属于Newdll.dll。

但是,若我们在One文件中创建一个新的程序集文件,那么One文件夹以及其子级文件夹中所有的脚本都将属于这个新的程序集。这里我们创建一个命名为OneDDD的程序集文件,然后看下one.cs所属,以及ScriptAssemblies文件中的内容。

        所以多个程序集的划分可以总结为:若当前目录下无程序集文件,则向上层寻找,先找到了谁就属于谁对应的程序集,若找不到,则属于Assembly-CSharp.dll。换句话说就是范围划分,最大的范围是Assembly-CSharp.dll,创建一个新文件夹并在其中添加程序集文件,就会在此范围内单独划分一个区域,可以创建多个同目录文件夹(内带程序集文件)来同层划分,或是创建子级文件夹(内带程序集文件)来往深层划分。怎么感觉那么抽象说的,来个图吧。

就图中这种,基本是按照前面案例画的,其中又补了几个案例中没有的。

2.4.3 程序集引用文件

        前面我们一直在创建“程序集文件”来实现划分程序集,这里来说下“程序集引用文件”。它跟程序集之间的引用、依赖无关,仅仅是服务于程序集划分的文件。我们就继续看上面那个圆环图,比如C.dll程序集,我们要想将其修改为NewDll.dll该怎么修改?若我们将C.dll所对应的“程序集文件”名称修改为NewDll,Unity就会报错,所以这时候就需要借助“程序集引用文件”。先将C程序集文件删除,然后创建一个“程序集引用文件”,将NewDll程序集文件拖进此文件设置中,即可将C.dll修改为NewDll.dll,相当于映射了一个NewDll程序集文件到当前位置。

        如图,在One文件夹中创建了一个Two文件夹,并创建了一个two.cs,此时其属于OneDDD.dll:

创建一个“程序集引用文件”,然后选中它,此时Inspector面板可配置一个程序集文件:

将NewDll程序集文件拖入,并Apply:

此时,two.cs就属于NewDll.dll了:

2.5 程序集引用、依赖

        当类型A(如类或结构)使用类型B时,A类型依赖于B类型。当Unity编译脚本时,它还必须可以访问该脚本所依赖的任何类型或其他代码。同样,当已编译的代码运行时,它必须可以访问其依赖项的已编译版本。如果A、B类型处于不同的程序集中,我们需要指定程序集间的依赖关系,因为是A依赖于B,所以需要让A的程序集依赖于B的程序集,换言之,让A的程序集引用B的程序集。
        如何设置程序集间的引用呢?可通过程序集设置选项来实现。这个在后面介绍程序集设置时会讲,具体见本文Assembly Definition References。

2.5.1 默认引用

我们通过这个图来了解默认引用,1、2、3分别表示:

  1. 预定义程序集会引用所有其他程序集,包括使用程序集文件创建的程序集。
  2. 预定义程序集会引用所有其他程序集,包括使作为插件添加到项目当中的预编译程序集。
  3. 使用程序集文件创建的程序集会自动引用所有预编译程序集。

在默认设置中,预定义程序集中的类型可以使用项目中所有其他程序集中的类型。同样,使用程序集文件创建的程序集可以使用所有预编译程序集(插件)中的类型。

PS:

  1. 使用程序集文件创建的程序集中的类不能使用预定义程序集中定义的类型。
  2. Unity会按照由其依赖项确定的顺序编译程序集;无法指定进行编译的顺序。

2.5.2 循环引用

        当一个程序集引用第二个程序集,而第二个程序集又引用第一个程序集时,便存在程序集的循环引用。这是不允许的,会报告为错误并显示消息“Assembly with cyclic references detected”。
        通常,程序集之间的这类循环引用是由于程序集中定义的类中的循环引用而发生的。同一程序集中的类之间的循环引用是允许的,但不允许不同程序集中的类之间进行循环引用。如果遇到循环引用错误,则必须重构代码以移除循环引用或将相互引用的类置于同一程序集中。

2.6 程序集设置

        选中我们创建的程序集文件,Inspector面板如图所示,可以设置一些选项。

2.6.1 Name

        程序集名称,决定了.dll文件的名称,其与创建的程序集文件的名称是分开的。

2.6.2 General

2.6.2.1 Allow 'unsafe' Code

        允许不安全代码,这个在项目设置里也有。若我们在C#中使用了C++代码,这就是不安全代码,我们就需要勾选此项。

2.6.2.2 Auto Referenced

        当前程序集是否允许被自动引用。
        指定预定义的程序集是否自动引用此程序集。禁用 Auto Reference 选项后,在编译过程中不会自动引用该程序集,但这不会影响 Unity在打包时将此程序集.dll文件包含其中。另外,在禁用后,更改程序集中的代码时不会重新编译预定义程序集(因为不引用修改的程序集了),但也意味着预定义程序集无法直接使用此程序集中的代码。
        同样,可以通过在插件资源的“Plugin Inspector”面板中关闭 Auto Referenced属性来防止预定义程序集以及我们自己使用程序集文件创建的程序集自动引用它。
        关闭插件的Auto Referenced时,可以在其他程序集文件的Inspector中显式引用插件的程序集。需要先启用其他程序集文件中的Override References选项,然后添加对插件程序集的引用。这个在后面Override References中会讲。

PS:

  1. 无法指定预编译程序集的显式引用。
  2. 预定义程序集只能使用自动引用的程序集中的代码。
2.6.2.3 No Engine References

        启用此属性后,Unity 在编译程序集时不会添加对 UnityEditor 或 UnityEngine 程序集的引用。

2.6.2.4 Override References

        覆盖自动引用(关闭当前程序集自动引用其他程序集的功能,手动设置)。默认情况下,项目中使用程序集文件创建的所有程序集都会自动引用所有预编译程序集。这些自动引用意味着在更新任何一个预编译程序集时,Unity都必须重新编译所有程序集,即使未使用程序集中的代码也是如此。要避免这种额外开销,可以覆盖自动引用,即关闭自动引用,然后我们自己来指定我们想要引用的预编译程序集。

        在勾选Override References后,Inspector面板中将新增一个Assembly References选项,点击“+”添加新引用,然后可以在下拉列表中分配对预编译程序集的引用。该列表会显示项目中,适用于当前在项目Build Settings中设置的平台的所有预编译程序集。(可在“Plugin Inspector”中为预编译程序集设置平台兼容性)

        注意,这里只是设置了一个平台的,项目在针对不同平台构建时,每个平台都需要进行设置。

2.6.2.5 Root Namespace

        定义在此程序集中的脚本的默认命名空间。如果我们使用Rider或Visual Studio作为代码编辑器,它们会自动将此命名空间添加到属于此程序集的任何新创建脚本中。

2.6.3 Define Constraints

        定义约束,可以实现有条件的包含一个程序集。可以使用“预处理符号”来控制程序集是否进行编译并包含在程序的构建中(包括编辑器中的运行时的构建)。在Define Constraints中添加我们使用的“预处理符号”(自己决定,随便写),可以通过在名称前放置感叹号来“否定”符号。例如,约束SSS表示定义SSS时包含程序集,!SSS表示未定义SSS时包含程序集。

        下面我将演示下具体使用。首先我有一个MM.cs的脚本在预定义程序集中,有一个one.cs脚本在我自己定义的OneDDD程序集中,由于预定义程序集自动引用其他程序集,所以在MM.cs脚本中可以访问到类型one,如图。

这个时候我要给OneDDD程序集添加约束,让其满足一定条件才可以被包含。在Define Constraints中加一个SSS,表示需要定义SSS才能包含此程序集,这时候会发现“预处理符号SSS”后面有一个叹号,且MM.cs脚本中的one类无法识别,如图。

叹号表示不满足条件,即目前没有定义SSS,因此OneDDD程序集无法被包含,所以在MM.cs脚本中也无法识别。那么我们需要在哪里定义SSS呢?

答:

  1. 在Project Settings的Player中的Scripting Define Symbols中定义。
  2. 在同一个Inspector面板上的Version Defines中定义。
  3. Unity自身提供定义。Unity在不同情况下会提供不同的定义的符号,这些符号也可以满足上述约束。可参阅“条件编译”。举个例子,比如在XX平台上,Unity会提供xx定义,我们把xx约束加上,那就需要在XX平台上才能满足我们的xx约束。

以Scripting Define Symbols为例,我们在其中添加SSS,就实现了定义,如图。

此时一切恢复正常。Define Constraints的红色叹号消失了,MM.cs脚本也能访问到类型one。
        此时我们也可以试一试把Define Constraints中的SSS修改为!SSS,会发现再次报红,因为!SSS表示不定义SSS,而我们刚把SSS给定义了,所以需要我们去把Scripting Define Symbols中的SSS给删掉即可。这里就不演示了。

        PS:Scripting Define Symbols适用于当前在项目Build Settings中设置的平台。要为多个平台定义一个符号,必须切换到每个平台并单独修改Scripting Define Symbols字段。

2.6.4 Assembly Definition References

        前面说了程序集引用问题,那么如何引用程序集呢?答:就在这里。

        比如程序集NewDll要引用程序集OneDDD。我们需要先选中程序集NewDll的程序集文件,然后在Inspector面板中设置Assembly Definition References,点击“+”增加引用项,然后将OneDDD程序集的程序集文件拖入其中,Apply即可。如图:

另外,若启用Use GUIDs选项,我们去修改OneDDD程序集文件的Name(注意是Inspector面板设置中的Name,不是文件名),NewDll对其的引用不会丢失。但若不启用此选项,修改Name后,引用将会丢失,如图:

不过此时若把OneDDD程序集文件的Name再修改回来,引用将会恢复。

PS:请注意,如果删除了“被引用的程序集文件(这里是OneDDD)”的元数据文件,或者将文件移到 Unity 编辑器之外,而没有同时随它们移动元数据文件,则必须重置 GUID。

2.6.5 Platforms

        设置创建程序集所属的平台。勾选Any Platform时,下方勾选平台为剔除的平台。不勾选Any Platform时,下方勾选平台为选取的平台。为平台构建项目时,会根据选定平台包含(或排除)程序集。

2.6.6 Version Defines

2.6.6.1 版本定义

        可以实现当某些包或模块满足相应版本时,定义某些符号,进而实现只有在项目中也安装了给定包的特定版本时才可以使用某程序集。这与条件定义的功能有些类似。

        直接上演示,我们随便选一个程序集文件来演示,首先在其设置面板的定义约束中添加一个符号FFF。

可以看到是报红的,说明目前我们缺少对符号FFF的定义。接下来我们通过版本定义来实现对此符号的定义。在Version Defines中,我们先通过“+”来添加一个定义项,可以看到有四个参数,其中三个是可以设置的。

四个参数的含义:

  1. 选择的包或模块。
  2. 要定义的符号名。
  3. 表达式(定义版本范围)。
  4. 通过表达式计算得到的版本范围。

首先我的UI版本是1.0.0,在“菜单栏/Window/Package Manager”中可以看到。之后我设置参数如下。

我这里设置UGUI版本若>=1.0.0版本,则定义FFF,Apply后,发现成功定义了FFF,约束的红色标识消失了。因为我的UI版本1.0.0,其满足所给的版本范围,所以能成功定义。

然后我修改为1.1.0版本,即>=1.1.0,Apply后发现红色报警又回来了,说明定义失败。这是因为我的UI版本1.0.0已经不满足所给定的范围了,所以定义失败。

PS:在程序集文件设置中定义的符号,其作用范围仅在定义此符号的程序集的脚本范围内。

2.6.6.2 表达式计算方式

        可以使用表达式指定确切版本或版本范围,这里提供一些表达式案例。

  • [1.3,3.4.1] 计算为 1.3.0 <= x <= 3.4.1
  • (1.3.0,3.4) 计算为 1.3.0 < x < 3.4.0
  • [1.1,3.4) 计算为 1.1.0 <= x < 3.4.0
  • (0.2.4,5.6.2-preview.2] 计算为 0.2.4 < x <= 5.6.2.-preview.2
  • [2.4.5] 计算为 x = 2.4.5

PS:表达式中不允许存在空格、通配符。

2.6.6.3 Unity版本格式

        当前版本的Unity(以及所有支持程序集定义的版本)使用由三部分组成的版本标识符:MAJOR.MINOR.REVISION。例如2017.4.25f1、2018.4.29f1、2019.4.7f1。

  • MAJOR版本为目标发布年份,如2017年或2021年。
  • MINOR版本是目标发布季度,如1、2、3或4。
  • REVISION指示符有自己的三个部分,格式为:RRzNN,其中:
    • RR是一个一位数或两位数的修订号
    • Z是表示发布(Release)类型的字母:
    • a = alpha 发布
    • b = beta 发布
    • f = 正常的公开发布
    • c = 中国发布版本(相当于f)
    • p = 补丁发布
    • x= 实验版本发布
  • NN是一位数或两位数的增量数
  • 发表类型比较:a < b < f = c < p < x

换言之,alpha版本被认为比beta版本早,beta版本比正常(f)或中国(c)版本早。补丁版本的发布总是晚于具有相同版本号的普通版本或中国版本,而实验版本的发布总是晚于其他任何版本。请注意,实验版本不使用最后的增量数字。
        Unity版本号允许在REVISION组件后加上后缀,例如2019.3.0f11-向日葵。在比较版本时,任何后缀都会被忽略。
        另外,版本可以范围表达。比如 [2017,2019) 的表达式包含任何2017或2018版本的Unity,但不包括2019或以后的任何版本。

2.6.6.4 包和模块的版本号

        版本格式分四个部分,遵循语义版本控制格式:MAJOR.MINOR.PATCH-LABEL。前三个部分始终是数字,但标签(LABEL)是字符串。预览版的 Unity 包使用字符串preview或preview.n,其中n > 0。更多关于包版本号的内容可以参考包版本控制
        另外,版本可以范围表达。比如 [3.2,6.1] 的表达式包含从3.2到6.1之间的所有版本(其是MAJOR.MINOR)。

2.7 为编辑器代码创建程序集

        一般我们创建的编辑器脚本是需要放在Assets/Editor文件夹中。借助程序集文件,我们可以实现将编辑器脚本放在项目的任何位置。操作步骤如下:

  1. 在我们要创建编辑器脚本的文件中,创建一个程序集文件。
  2. 设置程序集文件,设置Platforms仅选中Editor。
  3. 若还有其他文件中要存放编辑器脚本,只需要创建程序集引用文件,然后引用我们刚才创建的程序集文件即可。

2.8 特殊文件夹

        Unity存在一些特殊文件夹,脚本在这些文件夹中的处理方式不同于其他文件夹。在这些文件夹中或上层级文件夹中创建程序集文件时,这些文件夹将会失去特殊性。在使用 Editor 文件夹时可能会注意到该变化,这些文件夹可能分散在整个项目中(取决于代码的组织方式以及所用的 Asset Store 包)。

        Unity 通常都会将名为 Editor 的文件夹中的所有脚本编译到预定义的 Assembly-CSharp-Editor 程序集中(无论这些脚本位于何处)。但是,如果在下级有一个 Editor 文件夹的文件夹中创建程序集文件,则 Unity 不再将这些编辑器脚本放入预定义编辑器程序集中。实际上,这些脚本会进入通过程序集文件创建的新程序集中(脚本可能不属于该程序集)。要管理 Editor 文件夹,可以在每个 Editor 文件夹中创建程序集文件或引用文件,以将这些脚本放置在一个或多个编辑器程序集中,请参阅本文的“为编辑器代码创建程序集”。

2.9 设置程序集属性

(没研究过,感兴趣的话可以自行查询学习。)

        可以使用程序集属性 (Attribute) 为程序集设置元数据属性 (Property)。按照惯例,程序集属性语句通常置于名为 AssemblyInfo.cs 的文件中。例如,以下程序集属性指定了一些 [.NET 程序集元数据值]、一个 InternalsVisibleTo 属性(对于测试可能十分有用)以及 Unity 定义的 [Preserve 属性](影响构建项目时如何从程序集中移除未使用的代码):

[assembly: System.Reflection.AssemblyCompany("Bee Corp.")]
[assembly: System.Reflection.AssemblyTitle("Bee's Assembly")]
[assembly: System.Reflection.AssemblyCopyright("Copyright 2020.")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("UnitTestAssembly")]
[assembly: UnityEngine.Scripting.Preserve]

2.10 在生成脚本中获取程序集信息

        在UnityEditor中使用CompilationPipeline类,可以编译命名空间,用于检索由Unity为项目构建的所有程序集的信息,包括那些基于程序集文件创建的信息。例如,下面的脚本使用CompilationPipeline类列出项目中,当前所有的Player类型程序集(这里Player是某一类型,在枚举AssembliesType中可查看所有类型):

using UnityEditor;
using UnityEditor.Compilation;
public static class AssemblyLister
{
    //提供菜单选项
    [MenuItem("Tools/List Player Assemblies in Console")]
    public static void PrintAssemblyNames()
    {
        UnityEngine.Debug.Log("== Player Assemblies ==");
        //使用CompilationPipeline,获取所有AssembliesType.Player类型的程序集
        Assembly[] playerAssemblies =
            CompilationPipeline.GetAssemblies(AssembliesType.Player);
        //遍历输出程序集名称
        foreach (var assembly in playerAssemblies)
        {
            UnityEngine.Debug.Log(assembly.name);
        }
    }
}

3 结束语

        结束。后续考虑再搞个具体的使用案例,比如将部分代码以程序集进行打包,拿到别的项目中使用这样。

  • 30
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值