在2018以及以前的版本中,unity到处android工程时是一个完整的AndroidStudio工程。从2019开始unity中到处工程是一个module,所以用起来也更加方便,且unity提供了完整的demo。本文基于使用经验对其做进一步的解释,并提供一些必坑方式。
在unity中如下图所示,在PlayerSettings界面勾选Export Project然后运行即可到处Android工程。如果AndroidStudio比较新,可能需要在OtherSettings->Configuration->ScriptingBackend选项选择IL2Cpp,然后在targetArchitecrue中勾选Arm64。以unity的demo中所示,到处的模块最外层目录为androidBuild,里面包含所需的文件,其中可用的主要为unityLibrary这个目录下的两个目录。
这个步骤稍微多一点,即如何在android工程中引入外部独立module的方法。步骤如下:
1)引入模块
在原生工程中找到setting.gradle文件,然后添加:
include ':unityLibrary' project(':unityLibrary').projectDir=new File('..\\UnityProject\\androidBuild\\unityLibrary')
由于一个AndroidStudio工程包含很多的子项目,上述命令就是告诉此工程要引入一个叫unityLibrary的模块(子工程),此工程的目录为…\UnityProject\androidBuild\unityLibrary,即unity导出的模块中的unityLibrary目录。下图为unity官方demo的截图,表示此工程由两个项目,一个是app,即原生的Android工程,另一个则是引入的unity模块。
此时点击SyncNow就会同步工程,结束后此moudule就会存在工程中(此处不用同步,可以改完后同一同步)。
2)工程中引入jar包
在工程中找到build.gradle(project:NativeAndroidApp),在allprojects/repositories节点下添加如下指令。
flatDir { dirs "${project(':unityLibrary').projectDir}/libs" }
3)添加依赖到编译路径
在工程中找到build.gradle(module:NativeAndroidApp.app),在dependencies 节点中添加如下指令:
implementation project(':unityLibrary') implementation fileTree(dir: project(':unityLibrary').getProjectDir().toString() + ('\\libs'), include: ['*.jar'])
4)同步工程
由于修改了gradle,所以会在右上角提示同步工程“SyncNow",然后点击即可同步,如果顺利就集成到工程中了。
上述只是将unity模块引入到Android工程中,此段则是如何使用。此部分需要有一定的android知识,就像unity此demo的github工程中所述。
集成步骤:
1)创建基于Unity的Activity,unity提供的工程中提供了MainUnityActivity,它继承自OverrideUnityActivity。OverrideUnityActivity跟unity默认的activity相同,即将UnityPlayer的画面通过setContentView送显。
建议直接使用MainUnityActivity,因为它里面提供了一个showMainActivity方法用来处理一些特殊情况下的异常。
部分代码:
public class MainUnityActivity extends OverrideUnityActivity { // Setup activity layout @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addControlsToUnityFrame(); Intent intent = getIntent(); handleIntent(intent); }
2)添加1)中创建的activity到manifest文件。安卓开发需要在manifest中声明,并设置一些属性。以unity的demo为例,其unity相关的activity叫MainUnityActivity:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.unity.mynativeapp" > <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" android:label="@string/app_name" android:theme="@style/AppTheme.NoActionBar" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:label="@string/app_name" android:name="com.unity.mynativeapp.MainUnityActivity" android:screenOrientation="fullSensor" android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale|layoutDirection|density" android:hardwareAccelerated="false" android:process=":Unity"> </activity> </application> </manifest>
注:在MainUnityActivity的标签中有一个process=”:Unity"属性,表示在其他进程中开启unity模块,去掉则是依附于主进程。建议采用默认的方式,在独立进程中显示。
将unity渲染画面显示在很小的范围内或者一个view或者Layout中。此功能unity是不推荐的,具体可以看Limitations部分。 但是我们根据实现来将unity画面塞到一个Layout中。在3.1部分讲到,unity是将UnityPlayer的一个实例作为画面的,UnityPlayer是一个Framelayout,所以就可以将其addView到其他layout中。至此可以实现将unity画面显示在一个组件中,也就是可以非全屏显示。
但是要处理好UnityPlayer的声明周期。在3.1中UnityPlayer的生命周期是跟随activity的生命周期的,所以比较好处理,但是如果作为一个普通的画面显示,那么需要开发者做好管控。比如当显示此画面时,调用Start或者Resume,不需要显示时调用Destroy或者Pause等。还有在部分机器上可能要依附于主进程,有的则只能在独立进程中才可以实现此功能。
由于Unity的画面是一个FrameLayout,也就意味着可以通过addView将原生android组件叠加到unity画面上。如在unity画面上添加一个原生的android Button,怎可以如下处理:
FrameLayout layout = mUnityPlayer; Button myButton = new Button(this); myButton.setText("Show Main"); myButton.setX(10); myButton.setY(500); myButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { showMainActivity(""); } }); layout.addView(myButton, 300, 200);
在android原生工程中接入Unity画面时遇到两个问题,第一个是编译时提示资源问题,第二个是编译完成安装后桌面上显示两个icon。
编译时包错android.content.res.Resources$NotFoundException。此问题是由于unity在某些地方调用想要获取game_view_content_description这个资源导致,所以需要在main-.>res->values下找到strings.xml文件(没有则新建),然后添加如下
<string name="game_view_content_description">Game view</string>
如下所示,至于文字则不是重点,只要name对即可
<resources> <string name="app_name">NativeAndroidApp</string> <string name="action_settings">Settings</string> // Add Code <string name="game_view_content_description">Game view</string> // End </resources>
在导入的工程中(unityLibrary)中src-main下面找到AndroidManifest文件,打开将
<intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter>
这几行注释掉即可,同时保证android主manifest文件,unity的activity中也没有上述代码,参考3.1中的manifest文件。
While we tested many scenarios for Unity as a library hosted by a native app, Unity does not control anymore the lifecycle of the runtime, so we cannot guarantee it’ll work in all possible use cases. For example:
Unity as a Library supports rendering only full screen, rendering on a part of the screen isn’t supported. Loading more than one instance of the Unity runtime isn’t supported. You may need to adapt 3rd party Plug-ins (native or managed) to work properly Overhead of having Unity in unloaded state is: 90Mb for Android and 110Mb for iOS
总之就是Unity提供了这个功能,但是会有各种问题,毕竟安卓手机太多了,还有各种厂商定制。所以根据多场景验证(tested many scenarios)以下情况不支持:
1)只支持全屏渲染,不支持渲染到部分屏幕(尽管3.2部分提供了实现方式)
2)不支持多个unity实例
3)第三方插件需要自己适配
4)不卸载情况,unity在android中大约占用90Mb(如果依附于主进程,即使卸载也没用),所以在unity的activity中有process=":Unity"属性。
虽然2019提供了更直接的集成方法,但是有一些限制,尽量不要做Limitations里的事情。尽量使用demo中提供的方式,尽量使用MainUnityActivity,否则多机型复杂状况下会出各种问题。