Unreal与Unity双引擎联合开发

发表于2017-03-01
评论3 3.3k浏览
  Unity、Unreal是目前游戏开发领域使用者最多,普适性最好的两款引擎,而下面要给大家介绍的是Unreal与Unity双引擎联合去做游戏开发,相信有不少人不是很相信吧,那就一起看看吧。


  非常感谢我们的天才设计师“李一奇”同学为我的这篇文章设计的封面图标!
  前几天发了一条朋友圈,跟大家分享了一下近来一段时间一直在做的一项比较“大胆”的技术尝试。之所以用“大胆”来形容,是因为我们目前是一个创业团队,技术上的任何决策风险都可能导致整个公司陷入灾难。。。
  朋友圈发布之后很多朋友来找我聊具体的实现细节。于是我决定写这篇文章来跟大家仔细分享一下我的整个实现过程。

背景
  去年(16年)8月的某一天子雄来找我研究我们下一款产品的技术实现方案。在这之前我已经开始使用Unreal进行新产品Demo的开发。之所以选用Unreal是因为我们之前给老罗演示的产品Demo一直使用的是Unreal。但是子雄提出想更换Unity引擎做后续开发。由于具体理由会涉及我们的产品定位的一些细节,这个目前还在保密,不允许我说。。。 你们猜猜就好。。。
  在一番理由阐述及数据展示之后我被他说服了。本来使用Unity开发或者Unreal开发对我个人来说是无所谓的,但是他最后说了一句“我们未来的某一天可能会切回Unreal开发。。。”
此时我的内心是崩溃的。。。

初始版本
  为了保证我们某一天突然改变主意又不能影响研发进度,于是我做了第一步尝试:我把我之前开发的Unreal插件做了代码重构,然后在插件代码的上层做了一个对外暴露的C接口层。这样一来插件的功能就可以使用两种方式来调用:
1、Unreal自身的执行环境。
2、通过非Unreal的执行环境,直接利用C接口调用。
  以BlankPlugin为基础我写了一个小Demo。我绘制了一张类图,方便大家大概了解一下代码的结构:

  如上图所示,每一个插件专门写一套导出接口(***PluginForUnity),然后所有的插件连接到GameModule。GameModule再统一对外暴露一套接口。修改UnrealBuildTool(以下简称UBT),使GameModule在任何情况下都生成动态库(IOS除外),并且所有的插件都静态链接进入GameModule输出的动态库。
  最终输出的动态库导出的C接口在Unity的C#代码中可以直接调用:


         

  在开发过程中针对Unreal和Unity两种环境下使用的两套接口,在一些代码逻辑实现上会有一些差异。我们可以在一些实现有差异的地方加上宏将代码隔开:


          

     

然后我们在Unreal的Build文件中加上宏开关:


       


  这样一来只需要修改Build文件中的一个bool变量的值,然后重新编译代码即可发布相应引擎的功能模块。通过这种实现方式,在Demo实现阶段我就可以随时切换产品依赖的引擎了。

三方库接入
我们的产品需要一些三方库的支持,所以我需要为Unity插件添加一些三方库,在UBT的环境中添加三方库变得极为简单。UBT实在是太方便了,以至于我现在不管做什么编译都想移植到这个编译环境中来。
14年的时候我服务于国内原端游时代某巨头公司。我跟我的同事们曾经做过一个事情,我们把CryEngine3移植到移动端(Android,IOS),让CryEngine3变成了全平台次世代游戏引擎。其中在编译及跨平台这方面参考了很多UBT的做法。但是很遗憾由于种种原因这个项目在初期版本完成之后被弃用了。
 
  在这里给Unity开发者介绍一下我们在UBT下添加第三方库的具体做法。
  在UBT的环境中Unreal的ThirdParty和新的子模块的添加变得极为方便,我们可以通过定义一个Build文件就添加了新模块的各个平台的版本到我们的工程中。为Unity开发C++插件需要引入Unity提供的接口定义文件。我们可以在“UnityEditorDataPluginAPI”目录下找到这些文件:

              

  因为有的时候我们的多个模块都需要引入这些头文件,于是我将这些文件以ThirdParty的形式添加进入我们的工程:
1、在ThirdParty文件夹下建立子目录UnityPluginAPI。
2、将PluginAPI文件夹拷贝至UnityPluginAPI目录,然后创建UnityPluginAPI.build.cs文件。
3、在UnityPluginAPI.build.cs文件中我们添加如下代码:


现在我们就可以在我们的插件工程中引入新加入的UnityPluginAPI了:


    


产品化
  9月份的时候我们开始加入新的研发人员和我一起开发新产品。于是我开始考虑作为上线产品对于技术实现的一些要求。目前面临的最大问题是对于Unreal的大量模块的过度依赖导致插件编译出来之后体积较大,急需代码瘦身。代码瘦身的第一步是删除大量对于作为外部插件无用的Unreal模块,同时需要修改UBT,定制自己的编译需求。这部分也是整个过程中最重要,也是工作量最大的地方。
  我首先删除了大量的Unreal Runtime下代码模块。目前能用到的代码模块也只有这些:


                 

  以上这些模块中,Core做了大量的修改,其他模块有的做了一点点修改,有的甚至一点没动。在这个过程中还要修改UBT解决编译依赖的一些错误。最终修改编译工具,使我们的改造引擎在Android及Windows平台的Debug、Development及Shipping环境下编译出动态库,而IOS平台始终编译出静态库。这部分比较繁琐,而且也相对产品研发比较特异,所以就不详细介绍了。

  最后强调一句,以上所有看似使用Unreal辅助Unity开发,但是其实我们一直没有脱离Unreal的开发环境,并且我保持了Unreal插件开发要掌握的所有细节,只要换个Unreal引擎编译你写的所有代码,一下子就变成了为Unreal开发的代码。也就是说我们可以在给使用Unity的产品开发功能的同时顺便也为使用Unreal的产品开发了功能。

通过这种实现方案目前在我们的产品研发中我们收获了大量的好处:
  1、如果公司有多款产品同时使用Unreal和Unity开发,那我们在一些比较独立的功能模块研发上可以做到一份代码多个产品同时使用,再也不用为不同引擎开发相应的版本。
  2、Unreal提供了大量的C++库,极为方便和稳定,这为我们开发Unity 的Native插件带来了极大的帮助。
  3、写C++代码的时候注意跨平台,这方面Unreal也做了很多适应跨平台的封装。借助UBT的可以多平台编译的特性直接编译目标平台的代码。
  4、在我们产品中有一些比较重要的核心算法,我全部使用C++编写。这样就不用费劲去做C#的加密和混淆了,彻底地防止了反编译的发生。
  5、产品中的一些底层对效率要求极高的模块也用C++编写,保证了执行的效率。
  6、我们借助于UBT可以快速生成多平台的工程文件。这比针对各个平台手动创建工程要方便的多。而且代码文件的层次结构和本地存储目录保持一直,浏览及添加或删除新的代码文件变得极为方便。降低了手动维护出错的概率。UBT的编译配置也非常直观,可灵活配置并定义自己的编译环境。
  7、开发一个Unity产品有的时候我们需要接入一些三方库,这些库大部分是C/C++编写的。我们使用了跟Unreal一样的三方库接入方式。然后把一些三方库的接口根据我们的项目要求做封装,再把少量的接口暴露给C#。这样就简化了C++与C#的通信接口:

     


最后
  Unity的C#开发非常快速,门槛不高,代码维护及招人都非常容易。Unreal的C++开发能够保证执行效率,容易接入三方库,跨平台方面也不是问题,同时还能够保障代码安全性。我们使用Unity的C#开发应用上层逻辑,使用Unreal C++开发底层算法及其他基础功能模块。通过这种方式我们很好地发挥了两款引擎的各自优势。
  目前这套解决方案还不涉及大量Unreal渲染的东西,不处理那些Unreal特异的东西(例如UObject相关)。
  这套解决方案最大的难点在于我要大量改造Unreal代码,甚至编写自己的编译环境。这些东西又跟具体的项目关系特别紧密,没有办法给大家具体地展示。后续我会总结一些研发过程中可以提炼的细节(不涉密的)继续分享给大家。

  如果您对我的文章感兴趣可以扫描下方的二维码关注我的微信订阅号。在那里我会不定期更新我的技术文章。

                  

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引