UWP学习札记——浅析Unity工程

起因

最近在做项目时候遇到了一个问题——将 Unity 开发的游戏合并进入一个 C#写的 UWP 工程。既然 Unity 可以将游戏导出成为 UWP 工程,那么理论上来说,将这个工程合并进入原来的 C#项目也是完全可能的。

Unity 工程导出为 C#项目(UWP)

首先下载最新的 Unity 编辑器(至少也要是 5.2 版本以上),才可以支持导出为 UWP 项目。注意在使用安装器安装的时候就要选择 Windows10 的 build 的组件。

注意:因为之前 Win10 也经历过一次更新,版本号已经从 10240 到了 10586,所以 SDK 也发生了变化,之前的一些 Unity 版本可能会导致一些兼容性的问题,但可以通过更改工程支持的系统版本来修复这个问题,具体可以参考:Unity 游戏移植到 Windows10 之路 — 环境搭建

注意:安装完最新版本的 Unity 之后,打开项目可能需要进行版本转换,一般转换是不会出问题的。

打开工程后,点击 File 菜单,选择 Build Settings 选项,在 Scene In Build 里选择需要参与生成的场景。

接下来,在 Platform 中选择 Windows Store,点击 Switch Platform。接着在右侧的 SDK 中选择 Universal 10,Build Type 选择 XAML。对于 Unity C# Project,如果选择,则会在等下生成的解决方案中多两个未编译的工程,一个是 Unity 的 Script 文件的 Assembly-UnityScript 工程,一个是 Unity 的 Assembly-CSharp 的工程,这两个工程之后由 VS 进行编译,主工程会依赖这两个工程的类库。如果不勾选的化,则会直接编译生成这两个类库,不必使用 VS 对其进行编译。 这里假设没有勾选 Unity C# Project,在设置完成之后,选择 Build,选定目标文件夹,经过一系列编译之后,就在选定的文件夹中形成了一个 C#的解决方案。这时候,就可以关闭 Unity,使用 VS 来打开那个解决方案了。

Unity 导出解决方案的内容

打开解决方案,进入工程内部,可以看出,和一般的 UWP 应用非常类似。

首先可以看见 App.Xaml 和 ManPage.Xaml,这两个 Xaml 的作用和普通 UWP 应用完全一样,App.Xaml 用来作为启动 UWP 时的引导,MainPage.Xaml 作为引导成功后跳转到的页面。Assets 文件夹用来存放文件夹用于存放项目资产,也和普通应用一样,存放 App 的图标,多出来的是 Data 文件夹和 Unprocessed 文件夹,这两个文件夹是做什么的呢? Data 文件夹里面会存放经过 Unity 编译之后的游戏工程的文件,这些都是用户文件,不包含可以让程序成功运行的 dll,所以当在 Unity 没有更新的时候,每次重新从 Unity 中生成 UWP 项目只会更改这一个文件夹中的内容。

Unprocessed 文件夹从字面意义上来看应该是未处理过的一些类库,我也没有找到太多资料,大概理解就是可能包含一些 Unity 的 Unprocessed Plugin,在 Unity 里也有类似选项。而之前提及的 Assembly-UnityScript 和 Assembly-CSharp 两个工程生成的类库也在这里面。 翻开引用选项,可以看见其中有许多和 Unity 相关的引用,已经包含了脚本以及 UnityPlay 和用于和 Unity 和 C#交互的类库。这些类库都有特定的位置。 继续用资源管理器打开解决方案的文件夹探索,发现解决方案中包含的内容不止这些。

在解决方案中包含了 UnityOverwrite.txt 和 UnityCommon.props 两个 Unity 的配置文件,其中 UnityOverwrite 可以看作是像.gitignore 一类的文件,控制每次 Unity 编译写入时候需要更新的文件列表,默认是都不更新的。而 UnityCommon.props 文件以 XML 形式保存了一些 Unity 的配置信息。除了这些,还有 Unity、Players 和 Obj 几个文件夹,Unity 文件夹可能包含调试工具(只是猜测,不太清楚是干啥的),Players 包含了可以让项目运行的类库,看起来有 X86,X64 和 ARM 的(所以也是项目中最大的一个文件夹),但实际上现在只用过 X86 的,另外两个配置还没有用过(如果要生成针对其他平台的代码,需要将解决方案中的引用项都改为相应的 dll 或者 winmd,这也就是为何在后面在生成时都必须要选 X86 的原因)。而 Obj 文件夹应该存放生成 UWP 项目时候的一些中间文件。

进入到工程目录下面,还可以发现更多的类库,也都在项目的引用项中有所体现。 OK,项目整体大概就是如此了,接下来谈谈项目所引用的类库的问题。

浏览项目文件,可以看到类库主要集中在三个地方,解决方案文件夹的 Players 目录下、工程目录下和工程目录的 Unprocessed 目录下。而且有许多类库在不同目录中中包含了不同的版本。翻看项目引用类库的情况,可以看出,和开发者代码有关的类库均来自工程目录下,而和 Unity 引擎有关的类库均来自 Players 目录下。Unprocessed 目录下的类库没有被使用过。所以,基本可以断定 Unprocessed 目录下的类库都是原始未经过处理的,在使用之后会出现各种问题,比如无法调用 Unity 引擎或者引擎初始化失败等等(经过我的验证,的确如此)。关于 Unity 引擎类库的引用位置也就决定了当前的项目只能跑在 X86 环境上(切换成别的环境的类库暂时还没有尝试过,后面会持续更新)。

这些有关类库的位置对把 Unity 项目移植到一个新项目中是一个很强的指导(简单说吗,就是他们调用哪个库,我们也用哪个)。

浅探 App.xaml

进入 App.xaml.cs,可以看见在类中一开始便定义了两个量,AppCallbacks 和 SplashScreen,其中 AppCallbacks 是 UnityPlayer 的一个类,非常重要,是连同 Unity 和 UWP 的通道;而 SplashScreen 是启动屏幕的类,主要用于 Unity 获取窗体位置和大小,以便在正确未知绘图。

进一步观察,在几个 On 方法中,几乎都调用了 InitializeUnity 的方法,并且把一些参数以字符串的形式传了进去,具体含义可以参考应用生命周期-MSDN

在 cs 文件末尾,就是 InitializeUnity 方法的实现了,首先设置了一些窗体的属性,接着调用了一个非常重要的方法——appCallbacks.SetAppArguments(),最后创建了 rootFrame 并导航进去,激活窗口,并且防止 appcallbacks 类被重复初始化。

所以可以判断 AppCallbacks 方法在应用程序生命周期内只能被初始化一次,想要在别的地方调用它的方法需要将其作为一个静态属性或者利用 get/set 访问器将其静态化。

接下来,来说说这个 SetAppArguments()方法,它的作用是将程序初始化的一些信息传到 Unity 中,为什么在几个 On 方法中调用呢,是因为可以让 Unity 游戏知道现在处于那个生命周期中,以便选择游戏的启动点。但是我们这次的游戏没有这么复杂的根据生命周期启动点设置,所以完全可以利用这个方法给 Unity 内部通信,选择要启动的 Unity 中的游戏(我们将好几个游戏都合并在 Unity 中了)。在 Unity 内部,可以在启动时候使用 Unity 的 API:UnityEngine.WSA.Application.arguments 对传入的字符串进行获取。 Unity 的官方文档,写的不是很详细,把 cs 中的源码直接放上去了,解释也不怎么清楚,不过可以参考:AppCallbacks class

再探 MainPage.xaml

MainPage.xaml 中主要容器是 DXSwapChainPanel,它是事先 DirectX 和 Xaml 交互的控件,具体可以参考https://msdn.microsoft.com/zh-cn/library/windows/apps/windows.ui.xaml.controls.swapchainbackgroundpanel

在其代码隐藏文件中,WinRTBridge 也是 Unity 写的通 WindowsRT 交互的一个中间程序,但是源码不可知。appCallbacks 类又发挥了强烈作用,进行了游戏的初始化。

开始合并

合并的思路有两个,一个是把 UWP 程序的代码拷贝到 Unity 生成的工程(非决绝方案的另一工程)中(需要考虑 namespace 的问 题),另一个是将 Unity 生成的工程拷贝到原来的 UWP 项目中(也非解决方案的另一个工程)中(主要需要考虑引用的问题)。

采用第一种思路非常简单,只需要在 App.xaml.cs 中将导航到的页面更换,接着让其他的页面导航到 MainPage.xaml(也可以换一个其他的名字)即可。

第二种思路比较麻烦,需要把 Unity 生成工程中需要的类库都搬移到另一个工程中,然后在项目引用中添加进去,注意 pdb 同名文件的配套。在 App.xaml 中只要将 SplashScreen 和 AppCallbacks 创建,并且给 AppCallbacks 创建一个访问器即可在其他程序中调用它的 SetAppArguments 方法,将所需要的参数传入,然后 Navigate 到 Unity 的 MainPage 中即可。

注:访问器可以很简单的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
namespace StarrySky
{
sealed partial class App : Application
{
private AppCallbacks appCallbacks;
public SplashScreen splashScreen;

static public AppCallbacks AppCallBacks
{
private set;
get;
}

public App()
{
this.InitializeComponent();
appCallbacks = new AppCallbacks();
AppCallBacks = appCallbacks;
}
}
}

大概这样子就可以了。 最后,想说一下,Unity 的很多资料在百度上很难找到,推荐从 Google 上进行搜索,StackOverflow 上资料也不是很多,遇到问题还是得看自己来解决,在 UWP 上的开发和 Windows8 的开发有共通之处,许多资料也都可以参考。

参考资料:http://www.davebost.com/2013/08/30/creating-a-unity-game-for-windows-8