在 Unity 2020.2 中使用 Roslyn 代码生成器

Ant*_*hii 6 code-generation unity-game-engine roslyn

长话短说

  • 如何从 Unity 引用自定义 Roslyn 代码生成器?
    • 当地“传统”项目的可靠性如何?
    • 如何在Unity中引用“传统”dotnet项目?
  • 我可以将 Roslyn 代码生成器制作为 Unity 包或至少制作为 asmdef 吗?如何制作?我该如何使用它?

深入

我计划在 Unity 项目中使用自定义 Roslyn 代码生成器。

我找到了有关如何使用 正确设置代码生成器的信息CodeGeneration.Roslyn。通过此设置,可以轻松将代码生成器导出为 NuGet 包并将其导入到任何其他项目中。

第一个问题是通过这样的 NuGet 包生成代码是否可以在Unity 2020.2 LTS下工作。我想知道在 Unity 项目中实现如下所示的 MSBuild 项目引用有多麻烦。我想避免自己尝试这个,因为它可能根本不起作用,但在等待答案时我无论如何都会这样做:

<ItemGroup>
    <PackageReference Include="CodeGeneration.Roslyn.Tool" Version="0.7.63">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers;buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
    
    <!-- This ProjectReference to the generator needs to have the OutputItemType metadata -->
    <ProjectReference Include="..\Duplicator.Generators\Duplicator.Generators.csproj"
                      ReferenceOutputAssembly="false"
                      SetTargetFramework="TargetFramework=net4.7.2"
                      OutputItemType="CodeGenerationRoslynPlugin" />

    <ProjectReference Include="..\Duplicator.Attributes\Duplicator.Attributes.csproj"
                      ExcludeAssets="runtime"
                      SetTargetFramework="TargetFramework=net4.7.2"
                      PrivateAssets="all" />
    
</ItemGroup>
Run Code Online (Sandbox Code Playgroud)

这种方法的问题是代码生成器将不在 Unity 项目内。NuGet 导出间接性将使调试生成器变得更加困难。

因此,第二个想法是使用 Unity 存储库中的代码生成器引用本地项目,但我不确定这是否可能。Unity 中的所有 .csproj 文件都是自动生成的,我不知道它在此类嵌入式“传统”项目中的表现如何。

此外,尚不清楚此类子项目应位于项目结构中的哪个位置。我想最简单的解决方案是将代码生成器项目与整个 Unity 项目文件夹放在同一级别以避免冲突。但目前尚不清楚是否可以从项目文件夹外部引用 cs 项目以及 Unity 的效果如何。我知道 Godot 在这方面做得很好,但我不确定 Unity 是否如此。

另一个问题是这样的项目结构是非常规的并且令人困惑。AFAIK Unity 有自己的包管理器,并鼓励将项目构建为更小的 .asmdef 子项目和/或包,但是,我不确定是否可以通过这种方式分发 Roslyn 分析器,更不用说 Roslyn 代码生成器了。然而,这个解决方案似乎对我最有吸引力,因为它允许我将代码生成器作为主 Unity 项目中的子项目,而不必嵌套存储库(至少在开发之初,它可以被简单地重构)之后通过它进入一个单独的存储库git submodule),并且如果需要的话,我的代码生成器能够依赖 Unity API。这里的问题是是否可以将 Roslyn 代码生成器编写为 Unity 包

现在,我知道新的 2021 Beta 版支持 C# 9 以及新的代码生成器功能,但是,我不愿意使用该 Beta 版,因为它肯定存在问题,因此令人沮丧。

我还想知道是否有一些 Unity 内置 API 可以完成此任务,也许是一个未记录的 API。我知道Unity使用Roslyn来编译代码,所以它不妨公开语法树并允许代码更改。

如果不支持这种代码生成或引用其他项目,我总是可以默认使用Roslyn 脚本,该脚本将手动扫描源以获取特定属性,并将代码文件发送到 Unity 中的特定文件夹中。这种方法的问题在于它速度慢得难以忍受,而且我预测元文件内容最终会导致冲突。要同步元文件,我必须将 autogen 文件夹签入源代码管理,这是我想避免的。所以我不想回到这个选项(我宁愿尝试测试版)。

附言。好吧,也许它不一定慢。许多其他工具都像这个一样使用这种方法。无论如何,这是更多的工作。

Unity 官方文档仅提供有关如何使用分析器的信息。没有关于创建自己的建议,也没有关于代码生成的任何建议。

更新1

事实上,实际的参考资料会简单得多。同一存储库提供了创建和链接元包的示例。因此,在消费项目中使用代码生成器的代码实际上类似于:

<ItemGroup>
    <PackageReference Include="Kari" Version="1.0.0"/>
</ItemGroup>
Run Code Online (Sandbox Code Playgroud)

它仍然忽略有关如何欺骗 NuGet 使用本地文件夹作为其包存储库的一些细节。使用他们的样本,我能够弄清楚如何使其发挥作用。看到这个

现在我面临着从 Unity 引用代码生成器 NuGet 包的问题。我将尝试NuGetForUnity,但我不完全确定它是否有效,因为依赖项是构建时的。

更新2

尝试过NuGetForUnity。显然,它所做的只是将引用的包的所有 dll 转储到一个文件夹中,然后让 Unity 盲目地导入该文件夹中的所有内容,无论是编译时依赖项还是运行时依赖项。我尝试将预编译的 nuget 包放入包存储库文件夹中,Unity 给出了有关导入同一 dll 的多个副本的错误(因为该工具具有共享项目的副本,加上一些项目目标多个 dotnet 版本)。如果我随后从 UI 刷新包,我复制的文件夹就会被删除。

请参阅回购协议

我将尝试寻找影响.csproj统一生成的方法。如果这不起作用,我将不得不默认使用预构建步骤生成代码。

更新3

决定编写自己的独立代码生成器。我开始以这个仓库为基础,逐渐添加我自己的功能。

然后我决定将代码生成器与主项目分离,我也这样做了。基本上,我有一个MasterEnvironment,它跟踪 Unity 项目中的各个 asmdef 并将它们分成更小的项目环境。它有一个列表Administrators。其中每一个都为流程的每个阶段定义了一个函数:初始化、分析(信息收集)、回调(与其他管理员的数据的关系)、代码生成。我通过将路径作为参数传递给这些 dll,从预编译的 dll(在启动代码生成器时将其称为插件)动态加载这些内容。因此,代码生成器与实际分析和生成代码的插件分离。

这种架构使得插件和各个子项目之间的并行计算变得非常容易。结果,代码生成器的速度相当快。

更新4

代码生成器现在位于单独的存储库中。在这里找到它。