Unity 和 Android 的工程集成
有时候,我们需要在 Unity 里调用一些 Android 的功能,这些功能在 Unity 中可能并没有提供接口,需要在 Android 平台上实现。此时,我们需要有一个方法来让 Android 代码和 Unity 代码互调用。这里记录一下操作方法,并提供一个工具来简化两个工程之间的集成流程。
示例工程
下面的记录中所使用的工程可以参考 UnityAndroidExample。其中,根目录是 Unity 工程,可以直接用 Unity 打开。根目录下的 AndroidSample 子目录是 Android 工程,可以用 Android Studio 打开。
Unity 工程运行后如下图左所示,只有一个文本和一个按钮,点击按钮就会触发 Unity 到 Anrdoid 的调用,在主界面上产生一个 toast,同时,触发一次从 Anrdoid 到 Unity 的调用,主界面上的文本变为「Hello From Android」:
具体操作方式
新建一个 Android 工程
这里随便用 Android Studio 建立空一个工程就行了。建立好工程后,参考官方文档在工程里添加一个自定义的模块:
- 菜单栏点击 「File」-「New」-「New Module…」
- 弹出窗口中左侧选「Android Library」
- 右侧填入相关信息后创建模块
假设创建的模块名为「mod」,那么就会在工程根目录下新增一个名为 mod 的目录。此时可以删除工程根目录中默认创建的 app 目录,并将工程根目录中 settings.gradle 文件里的 include ':app'
这一行删除。
添加 Unity jar 依赖
为了在 Android 中和 Unity 互交互,我们需要引入 Unity 提供的库,这个库以 jar 包的形式提供。以下目录中都有这个 classes.jar 文件,有 mono 和 il2cpp 版本,还区分 Release 和 Development:
- YOUR_EDITOR_PATH/Data/PlaybackEngines/AndroidPlayer/Variations/il2cpp/Release/Classes
- YOUR_EDITOR_PATH/Data/PlaybackEngines/AndroidPlayer/Variations/mono/Release/Classes
- YOUR_EDITOR_PATH/Data/PlaybackEngines/AndroidPlayer/Variations/il2cpp/Development/Classes
- YOUR_EDITOR_PATH/Data/PlaybackEngines/AndroidPlayer/Variations/mono/Development/Classes
这里的「YOUR_EDITOR_PATH」是 Unity editor 程序所在的路径,例如,如果在 Windows 上用 Unity Hub 安装了 2020.3.5fc1 的 Unity,那么这个路径就是 C:\Program Files\Unity\Hub\Editor\2020.3.5f1c1\Editor。
参考Android 官方文档添加依赖,将该 jar 文件复制到 Android 工程中的对应模块的 libs 目录中,具体是复制哪一个 jar 无关紧要,因为后面的流程中并不会实际加入这个 jar 包。在复制的之后可以修改一个名字,例如修改为 unity.jar。然后修改 gradle 构建文件,注意这里是修改模块目录下的 build.gradle 而非根目录下的。在 dependencies
中添加如下内容:
dependencies {
// ... 前面有一堆默认的
// 添加 unity 的 jar
// 注意这里必须是 compileOnly 以避免该 jar 包被打入 aar 包中,否则会在之后发生命名冲突
compileOnly files('./libs/unity.jar')
// 如果还有别的自定义的 jar 就用 implementation
implementation files('./libs/some_other_lib.jar')
}
修改完后,Android Studio 会提示是否要同步,点击「Sync Now」即可。
引入 UnityPlayerActivity
我们在实现自己的 Activity 时不能直接实现,而是需要继承 Unity 的 UnityPlayerActivity
,这个类型会按照一定的规则去调用 Unity 的回调函数,以确保程序的正确运行。从前这个类就在刚刚我们引入的 unity.jar 中,而在新版本的 Unity 中这个类却以单独文件的形式存在,需要自己拷贝一下,这个文件所在的路径为:YOUR_EDITOR_PATH/Data/PlaybackEngines/AndroidPlayer/Source/com/unity3d/player/UnityPlayerActivity.java。
我们直接将 YOUR_EDITOR_PATH/Data/PlaybackEngines/AndroidPlayer/Source/com 这个目录直接拷到工程里的 mod/src/main/java 目录下,这样一来,我们的代码就可以看到这个 Activity 了。
新增一个 Activity 继承 UnityPlayerActivity
在这里,我们添加一个 showMessage
函数用于给 Unity 调用,同时,在这个函数里面,我们通过 UnityPlayer.UnitySendMessage
来调用 Unity 中的函数。这个 UnityPlayer
定义于我们引入的 unity.jar 文件中。其中第一个参数是 Unity 场景中的对象名,第二个参数是需要调用的函数名,第三个参数是传递的参数:
public class MainActivity extends UnityPlayerActivity {
// 被用于 Unity 调用的函数
public void showMessage(final String message) {
runOnUiThread(() -> Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show());
// 调用 Unity 的函数
UnityPlayer.UnitySendMessage("Canvas", "ChangeText", "Hello from Android!");
}
}
此时,文件结构如下图所示:
构建模块
菜单栏中选择「Build」-「Make Module ‘mod’」。等待构建完成后,会在 mod/build/output/aar 目录下看到构建出来的包。
和 Unity 集成
接下来,我们需要将这个库和 Unity 集成,并让 Unity 以这个 Activity 为入口启动程序。
将这个 aar 包解压,放入 Unity 工程的 Plugins/Android/mod 目录下,然后在这个目录下建立一个 project.properties 文件,填入如下内容:
android.library=true
再在 Plugins/Android 目录下(和 mod 同级)建立一个 AndroidManifest.xml 文件,填入如下内容,注意其中的 ACTIVITY_NAME
需要换成 main Activity 的完整类名(完整包名加上类名)。如果有什么需要申请的权限,也可在此加入:
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.unity3d.player"
android:installLocation="preferExternal"
android:versionCode="1"
android:versionName="1.0">
<supports-screens
android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="true"
android:xlargeScreens="true"
android:anyDensity="true"/>
<application
android:theme="@style/UnityThemeSelector"
android:icon="@drawable/app_icon"
android:label="@string/app_name"
android:debuggable="true">
<activity android:name="ACTIVITY_NAME"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data android:name="unityplayer.UnityActivity" android:value="true" />
</activity>
</application>
</manifest>
要在 Unity 中调用 Android 的函数,需要用类似这样的方法实现。首先,我们要找到当前的 Android Activity,然后我们通过 Call
方法来调用其中的逻辑。其中第一个参数是方法名,后面的参数是需要传递的参数:
#if UNITY_ANDROID && !UNITY_EDITOR
using (AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
{
using (AndroidJavaObject activity = jc.GetStatic<AndroidJavaObject>("currentActivity"))
{
activity.Call("showMessage", "Hello from Unity");
}
}
#else
#endif
这一块代码用 #if UNITY_ANDROID && !UNITY_EDITOR
包裹,只在 Android 设备上生效。
另外我们还需要在 Unity 场景中添加刚刚 Android 代码中调用 Unity 时 Unity 侧的接收者(在本示例中为 Canvas
),这块具体操作直接参考示例工程即可。
Unity 构建
在 Unity 菜单中点击「File」-「Build Settings…」,在弹出的窗口中选择 Android 平台,然后构建即可。
一个小工具
上面这个流程有些是只用操作一次的(例如新建工程),但也存在一批需要反复操作的(例如编译 Android 工程、删除 Unity 的 Activity 等),这些需要反复操作的流程在每次修改 Android 工程中的代码后,都需要进行一次,非常麻烦还容易出错,因此,这里提供一个简单的小工具来简化这个工作。这个工具的安装需要用到 go,需要先安装一下 go 的环境。
这个小工具可以编译指定的 Android 模块,然后将 aar 压缩包解压到 Unity 工程中,删除 Unity 的 Activity class,并生成 project.properties 和 AndroidManifest.xml 文件。在生成 AndroidManifest.xml 的时候,提供了默认的文件模板,允许通过命令行参数指定需要申请的 Android 权限。
例如这样的命令:
upack -a ./AndroidSample -e com.example.mod.MainActivity -m mod -p android.permission.BATTERY_STATS ./Assets/Plugins/Android
可以将 Android 工程 AndroidSample 编译,然后将数据解压到 ./Assets/Plugins/Android 目录下,其中参数 -e
用来指定入口 Activity 的类型全名,参数 -m
用来指定 Android 模块名,-p
用来指定需要申请的权限,如果有多个权限需要申请,则可以增加多个 -p
参数。
在示例工程中也可以体验这个工具,每次修改这个 Android 工程中的代码,都可以执行一下工程根目录下的 update_android.bat 脚本,这个脚本会调用这个工具,重新构建 Android 工程并自动将相关内容解压到 Unity 工程中。