之前公司有个AR相关的项目,里面用到的3D模型是用Unity搭建的。
由于Unity程序在手机端独立运行于自己的虚拟机中(mono/il2cpp),因此具备了强大的跨平台能力,我们可以用它直接编译生成能够运行在IOS与Android两大平台的程序。
事实上整个项目绝大部分都是由Unity的C#编写的,这样好处很多,主要是不用针对不同平台做移植,节省了很大的资源。
不过事情也没有绝对,Andorid版后期需要开发一个录屏功能,可能是因为涉及到底层C/C++的库,所以必须要用原生的代码(Java)来写一个方法实现,然后由Unity来调用,操纵摄像头。
在网上查了很多资料,自己也踩了很多的坑,不过磕磕绊绊总算是有点头绪,所以决定趁这次机会总结归纳一下。
我们知道,Android程序都是运行在dalvik/art虚拟机上的,而Unity程序是在(mono/il2cpp)上。当一个Unity应用想要用到Andorid的方法的话,毫无疑问,这个应用就需要两套虚拟机同时运行,即两个虚拟机运行在同一个进程中。
那么,Unity与Android之间的交互问题,其实就是两个VM之间的相互调用:如下图
如上图所示,Unity通过UnityEngine提供的API调用Android的方法;Android借助com.unity.player包提供的API调用Unity的方法。
此次我们主要讨论Unity调用Android,Android调用Unity不过多深入。
Unity调用Android,需要在Android项目中封装一系列方法,然后打成Jar包供Unity使用。
由于AS没有直接导出Jar包的方法,所以这次我们利用了AS的Module Build会生成Jar包的原理。
打开AndroidStudio,New一个Project,我们这里命名为AndroidPlugin。之后我们再New一个Module出来,类型为Android Library,命名为android2u3d。
在此Module里,New一个Activity,并勾选上Launcher Activity选项。
接下来,我们会编写这个Activity,主要是在里面写一些方法供Unity调用。
在此之前,我们还要用到一个Unity提供给Android的包,这个包提供了支持该平台的Player。根据Unity版本的不同,包的位置会略有不同。笔者用的是5.3.4,位置在Unity安装目录\Editor\Data\PlaybackEngines\Androidplayer\Variations\il2cpp\Release\Classes\classes.jar(注意到Variations下有两个目录:il2cpp和mono,这是Unity项目脚本执行器的类型,有mono和il2cpp两种,与Unity项目的”Script Backend”一致)。接着将其放置到Module下的libs目录,右键点击Add As Library,将其置为Module的File Dependency。
开始编写Activity:
package com.example.yaoobs.android2u3d;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Vibrator;
import android.widget.Toast;
/* 引入Unity的包,在之前Unity的classes.jar里 */
import com.unity3d.player.UnityPlayer;
import com.unity3d.player.UnityPlayerActivity;
/* 如果需要Activity与Unity对接,可以通过继承UnityPlayerActivity来实现 */
public class MainActivity extends UnityPlayerActivity {
private Context mContext = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = this;
}
/* 定义一个调用Unity方法的方法 ,基于UnitySendMessage实现 。
* 由于没有布局文件,我们在这里通过Unity调用这个方法*/
public void InvokeUnity(String mStr) {
UnityPlayer.UnitySendMessage("Unity2Android", "SetCameraColor", "");
}
// 定义一个显示对话框的方法,在Unity中调用此方法
public void ShowDialog(final String mTitle, final String mContent) {
// 在UI线程下执行
runOnUiThread(new Runnable() {
@Override
public void run() {
AlertDialog.Builder mBuilder = new AlertDialog.Builder(MainActivity.this);
mBuilder.setTitle(mTitle).setMessage(mContent).setPositiveButton("OK", null);
mBuilder.show();
}
});
}
// 定义一个显示Toast的方法,在Unity中调用此方法
public void ShowToast(final String mStr2Show) {
// 同样需要在UI线程下执行
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), mStr2Show, Toast.LENGTH_LONG).show();
}
});
}
// 定义一个手机振动的方法,在Unity中调用此方法
public void SetVibrator(final long[] mTime) {
Vibrator mVibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
mVibrator.vibrate(mTime, -1); //-1:表示不重复 0:循环的震动
}
}
如上所示,我们删掉了布局文件,然后定义了4个方法,显示Dialog跟Toast,手机振动,还有一个调用Unity方法的。别忘了在AndroidManifest.xml添加权限:
<uses-permission android:name="android.permission.VIBRATE">uses-permission>
为了导出Jar包,我们需要在Module下的build.gradle里添加一个task,如下:
task makeJar(type: Copy) {
delete 'build/libs/android2u3d.jar'
from('build/intermediates/bundles/release/')
into('build/libs/')
include('classes.jar')
rename('classes.jar', 'android2u3d.jar')
}
makeJar.dependsOn(build)
上面的命令无非就是拷贝删除重命名而已,就不多说了。
打开AS的Terminal,输入:gradlew makeJar
等待出现BUILD SUCCESSFUL字样,就可以在Module/build/libs下找到android2u3.jar
如果出现lint检查错误,在Module下的build.gradle里添加:
android{
...
lintOptions{
abortOnError false
}
...
}
就没事了。
最好把src下的androidTest和test,即UI测试与单元测试相关的目录删掉,再把没有用到的譬如
testCompile ‘junit:junit:4.12’
compile ‘com.android.support:appcompat-v7:23.1.0’
的Dependency删掉。
打开Unity,New一个Project,命名为Unity2Android,在Project/Assets/Plugins/Android/bin(标黑的是需要自己Create的目录)下放入之前的android2u3d.jar。Android下放入Module的AndroidManifest.xml文件。
接着在Plugins的同级目录下Create一个Script目录,在其下Create一个C# Script,我们将其命名为:AndroidAPI.cs,编写代码如下:
using UnityEngine;
using System.Collections;
public class AndroidAPI : MonoBehaviour {
//在Android中我们将使用这个名字
void Start () {
this.name="Unity2Android";
}
void SetCameraColor()
{
//设置摄像头背景颜色
Camera.main.backgroundColor=new Color(1.0F,0.5F,0.5F);
}
void Update () {
// 返回键退出
if(Input.GetKey(KeyCode.Escape))
Application.Quit();
}
void OnGUI ()
{
// 通过API调用对话框
if(GUILayout.Button("调用安卓Jar中的方法ShowDialog",GUILayout.Height(50)))
{
//获取Android的Java接口
AndroidJavaClass jc=new AndroidJavaClass("com.unity3d.player.UnityPlayer");
AndroidJavaObject jo=jc.GetStatic("currentActivity");
//构造参数
string[] mString=new string[2];
mString[0]="Unity2Android";
mString[1]="Talk is cheap,Show me the code!";
//调用方法
jo.Call("ShowDialog",mString);
}
// 通过API调用Toast
if(GUILayout.Button("调用安卓Jar中的方法ShowToast",GUILayout.Height(50)))
{
AndroidJavaClass jc=new AndroidJavaClass("com.unity3d.player.UnityPlayer");
AndroidJavaObject jo=jc.GetStatic("currentActivity");
jo.Call("ShowToast","HELLO WORLD!");
}
// 通过API调用手机震动的方法
if(GUILayout.Button("调用安卓Jar中的方法SetVibrator",GUILayout.Height(50)))
{
long[] mTime = new long[]{ 200, 2000, 2000, 200, 200, 200 };
AndroidJavaClass jc=new AndroidJavaClass("com.unity3d.player.UnityPlayer");
AndroidJavaObject jo=jc.GetStatic("currentActivity");
jo.Call("SetVibrator",mTime);
}
//通过API调用Toast
if(GUILayout.Button("通过SendMessage调用Unity中的方法",GUILayout.Height(50)))
{
//获取Android的Java接口
AndroidJavaClass jc=new AndroidJavaClass("com.unity3d.player.UnityPlayer");
AndroidJavaObject jo=jc.GetStatic("currentActivity");
jo.Call("InvokeUnity","");
}
}
}
可以看到,在Unity里调用Android的方法是通过AndroidJavaObject与AndroidJavaClass这两个类来实现的。首先获取到MainActivity,接着调用指定方法(关于具体原理可以去看UnityPlayer与UnityPlayerActivity的源码,我们将放在之后讲解)。值得注意的是Android的InvokeUnity这个方法,Unity调用它后,它又去调用Unity的SetCameraColor这个方法。通过UnitySendMessage这个方法,可以调用Unity中指定GameObject所挂载的脚本的方法,而GameObject的名字,就是这里
void Start () {
this.name="Unity2Android";
}
指定的名字,必须与Android代码中的
public void InvokeUnity(String mStr) {
UnityPlayer.UnitySendMessage("Unity2Android", "SetCameraColor", "");
}
第一个参数保持统一,第二个参数是方法名,第三个参数应该是传参,没有验证。
之后将这个 AndroidAPI.cs 拖动到 Main Camera 上进行绑定。
如果是第一次使用,还需配置AndroidSDK的Location。在菜单栏 ->Edit -> Preferences -> External Tools里设置。
之后是BuildSettings设置,切换到Android平台,勾选Development build和Autoconnect profiler,Texture Compression选择 ETC(default),Player Settings设置BundleID和最小APIlevel,最后Create New Keystore,最后连接设备,BuildAndRun。
项目已上传GitHub,地址:https://github.com/Yaoobs/UnityAndroidPluginDemo
参考文章:
http://mp.weixin.qq.com/s?__biz=MzI1NjEwMTM4OA==&mid=2651231917&idx=1&sn=ce2e0f7251e26f7b5a9b2fddadf96bcc&scene=23&srcid=0705qPTKw0LXSdy8hRQfBegv#rd
http://blog.csdn.net/kuerjinjin/article/details/50177633
http://blog.csdn.net/qinyuanpei/article/details/39348677