背景

本人近期制作一服务端和客户端程序,采用下列架构实现:

  • 服务端(Server.exe)
  • 中间件(Shared.dll,实现两端共享的那部分代码)
  • 客户端(Client.exe,用 Unity 实现)

在不使用 Unity 的情况下,一般把三个工程(.csproj)放入同一解决方案(.sln),然后在服务端和客户端添加对中间件的引用即可。但是,现在用 Unity 实现客户端,Unity 的解决方案不支持引入外部工程或 NuGet 包,强行引入会在 Build 时被删掉。

自然会想到,将中间件的代码或 dll 每次手动放入客户端,但这样除了操作麻烦、版本管理不便等问题之外,还会出现 Unity 不能检测到文件更新等诸多罗乱。由此,本人才另寻他法。

在 Unity 中引入外部工程或 NuGet 包

简单来说,使用此工具 MSBuildForUnity(msb4u),按其说明文档来操作即可。喜欢阅读生肉的朋友可以关闭本文,因为文本的出发点只是对其稍作整理和说明,供自己查阅方便。
工具地址:https://github.com/microsoft/MSBuildForUnity

步骤 1:将 msb4u 引入 Unity

  1. 编辑 Unity 工程中的 Packages/manifest.json,遵循 json 语法,与 "dependencies" 同级,并在其上方添加下列内容:
"scopedRegistries": [
    {
        "name": "Microsoft",
        "url": "https://pkgs.dev.azure.com/UnityDeveloperTools/MSBuildForUnity/_packaging/UnityDeveloperTools/npm/registry/",
        "scopes": [
            "com.microsoft"
        ]
    }
  ],
  1. "dependencies" 的下级中添加下列内容:
"com.microsoft.msbuildforunity": "<version>",

其中,<version> 换成 这里 所提供的最新版(“@”后面那个就是版本号)。

弄好以后如下所示,保存。回到 Unity,Unity 会自动引入 msb4u,并弹出一些介绍和设置,关闭即可。

{
  "scopedRegistries": [
    {
        "name": "Microsoft",
        "url": "https://pkgs.dev.azure.com/UnityDeveloperTools/MSBuildForUnity/_packaging/UnityDeveloperTools/npm/registry/",
        "scopes": [
            "com.microsoft"
        ]
    }
  ],
  "dependencies": {
    "com.microsoft.msbuildforunity": "0.9.2-20221114.1",
    "com.unity.collab-proxy": "1.15.16",
    "com.unity.feature.2d": "1.0.0",
    "com.unity.ide.rider": "3.0.13",
    "      (后面省略)    ": ""
  }
}

步骤 2:添加引用

Unity 已经创建了下列文件:

  • Asset/{Unity工程名称}.Dependencies.msb4u.csproj
  • Asset/NuGet.config

在 Unity 外部使用文本编辑器修改 Asset/{Unity工程名称}.Dependencies.msb4u.csproj

<Project ToolsVersion="15.0">
<!--GENERATED FILE-->
<!--
    This file can be modified and checked in.
    
    It is different from the other generated C# Projects in that it will be the one gathering all dependencies and placing them into the Unity asset folder.
    
    You can add project level dependencies to this file, by placing them below:
    - <Import Project="$(MSBuildForUnityGeneratedProjectDirectory)\$(MSBuildProjectName).g.props" />
    and before:
    - <Import Project="$(MSBuildForUnityGeneratedProjectDirectory)\$(MSBuildProjectName).g.targets" />
    
    Do not add any source or compilation items.
    
    Examples of how you can modify this file:
    - Add NuGet package references:
        <ItemGroup>
            <PackageReference Include="Newtonsoft.Json" Version="11.0.1" />
        </ItemGroup>
    - Add external C# project references:
    <ItemGroup>
        <ProjectReference Include="..\..\..\ExternalLib\ExternalLib.csproj" />
    </ItemGroup>
-->

<Import Project="$([MSBuild]::GetPathOfFileAbove(MSBuildForUnity.Common.props))" Condition="Exists('$([MSBuild]::GetPathOfFileAbove(MSBuildForUnity.Common.props))')" />

<PropertyGroup>
    <TargetFramework>$(UnityCurrentTargetFramework)</TargetFramework>
</PropertyGroup>

<!-- SDK.props is imported inside this props file -->
<Import Project="$(MSBuildForUnityGeneratedProjectDirectory)\$(MSBuildProjectName).g.props" />

<ItemGroup>
    <!--Add NuGet or Project references here-->
    
    <!-- 注意这里 -->
    <!-- 注意这里,按照上方注释中的例子添加对外部工程或 NuGet 包的引用 -->
    <!-- 注意这里 -->
        
</ItemGroup>

<!-- SDK.targets is imported inside this props file -->
<Import Project="$(MSBuildForUnityGeneratedProjectDirectory)\$(MSBuildProjectName).g.targets" />
</Project>

注意在其中的 <ItemGroup> 标签内,按照上方注释中的例子添加对外部工程或 NuGet 包的引用。例如:

<ItemGroup>
    <!-- 引用外部工程 -->
    <ProjectReference Include="..\..\..\ExternalLib\ExternalLib.csproj" />
    <!-- 引用 NuGet 包 -->
    <PackageReference Include="Newtonsoft.Json" Version="11.0.1" />
</ItemGroup>

保存,回到 Unity。此时很可能 Unity 会报错,这个后文再说。正常情况下,以后点击菜单中的 MSBuild -> Build All Projects,msb4u 就会先把外部工程或 NuGet 包首先生成到下列位置:

  • Assets\Dependencies\netstandard2.0\

然后再通知 Unity 进行 Build。这之后,Unity 的解决方案(.sln)相当于已经添加了对(最新的)外部工程或 NuGet 包的引用,我们便可以在代码中使用它们了。

这些已经基本解决了本人的需求,所以没有进行更深入的研究。其实 MSBuildForUnity 还提供了几个例子,涉及到两个 Unity 工程之间互相引用、跨平台等复杂问题,详细了解请阅读原文。

步骤 3:解决报错

第二步做完后,Unity 很可能会报告下列错误:

  • Unable to find package Microsoft.Build.NoTargets
  • Error MSB4236: The SDK ‘Microsoft.Build.NoTargets/1.0.85’ specified could not be found.
  • 其他 SDK 版本错误字样

此问题可参考 Issue #153:https://github.com/microsoft/MSBuildForUnity/issues/153 这里简单总结如下。

主要原因:

  • 外部工程或 NuGet 包的框架版本不是 .NET Core 2.1 的(或者 .NetStandard 2.0,不确定)
  • msb4u 尚未缓存 build 其他版本框架所需的组件

解决步骤:

  1. 在 Unity 外部使用文本编辑器修改 Assets\NuGet.config,注释掉其中的 <clear /> 标签,如下:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <!-- <clear /> -->
    <add key="MSBuildForUnity" value="https://pkgs.dev.azure.com/UnityDeveloperTools/MSBuildForUnity/_packaging/UnityDeveloperTools/nuget/v3/index.json" />
  </packageSources>
</configuration>
  1. 在 Unity 工程的任意位置创建一个 global.json,写入下列内容:
{
  "sdk": {
    "version": "2.1.202"
  }
}

其中,2.1.202 修改为外部工程或 NuGet 包的框架版本的字符串。外部工程的版本字符串可以用文本编辑器打开其工程文件(.csproj),找到 TargetFrameworkVersion 标签,例如:

<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>

其中的 v4.7.2 即为所需。NuGet 包的版本字符串应该可以在 NuGet 包管理器页面中很容易地见到,不过一般来说 NuGet 包的版本比较全,可能不容易造成 Unity 报错。

  1. 回到 Unity,重新尝试 MSBuild -> Build,应该已经能够 Build 成功,不再报错。这时,msb4u 已经缓存了 build 该版本框架所需的组件(可能是位于 MSBuild\ 中的那些文件),因此前面两步的修改可以回滚,也就是:
  • 删除 global.json
  • 取消 Assets\NuGet.config 中对 <clear /> 标签的注释

注:不需要改变外部工程的框架版本,如果笔者没理解错的话。

参考资料

为了找到最佳的方法,笔者查阅了大量资料。本文所提到的方法应该并不唯一,这里把相关信息(仅一部分)也列举出来,以供参考。

极为相关的:

  1. MSBuildForUnity: https://github.com/microsoft/MSBuildForUnity
  2. MSBuildForUnity - IntegratedDependencies: https://github.com/microsoft/MSBuildForUnity/blob/master/Samples/IntegratedDependencies.Unity/README.md
  3. MSBuildForUnity Introduction: https://stackoverflow.com/a/67009872

有帮助的:

  1. 选择要使用的 .NET 版本及 global.json 语法:https://learn.microsoft.com/zh-cn/dotnet/core/versions/selection
  2. Use Package Manager to host and add your own Custom Packages as a dependency:https://stackoverflow.com/a/62402865
  3. Use git submodule: https://stackoverflow.com/a/73839806
  4. Use git submodule: https://stackoverflow.com/a/45365163
  5. Use source (code version) control: https://forum.unity.com/threads/referencing-another-project-in-visual-studio.504115/
  6. Use source (code version) control: https://answers.unity.com/questions/16908/how-to-share-c-source-between-unity-projects.html
  7. Use Visual Studio Tools: http://devleader.ca/2015/02/08/multiple-c-projects-unity-3d-solution/
  8. Use Assembly Definition files (.asmdef): https://stackoverflow.com/a/38413222