各位朋友,大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是blog.csdn.net/qinyuanpei。在前一篇文章中,我们研究了Android平台上Unity3D的手势操作并在之前的基础上实现了手势旋转、放缩等功能。今天呢,我们继续来研究Unity在Android平台上扩展的内容。众所周知,Unity3D是一个强大的跨平台游戏引擎,和大多数喜欢Unity的朋友一样,博主在体验了Unity强大的跨平台能力后,被深深地震撼了,试想以前我们如果要开发一款游戏的话,我们需要对DX、OpenGL等图形库有足够的了解,由于游戏对性能的严格要求,诸如Unreal、CryEngine等顶级的商业游戏引擎通常都是使用C/C++这种底层的语言来开发,使得许多开发者望而却步。而此时此刻,得益于Unity3D强大的跨平台能力,博主可以用自己喜欢的C#语言来编写游戏,这无疑说明博主和成千上百使用Unity的开发者一样,我们正处于一个商业化引擎逐渐成熟的时代,作为开发者的我们是幸福的。
记得"仙剑之父“姚壮宪作为评委参加Unity亚洲区的比赛时曾经感慨道:"我学生时也是痴迷于自己不断钻研游戏开发,从各种小游戏和小工具做起,并不断的回头优化改良以前的作品,积累经验技巧。那时候没有商业引擎可以用,需要自己给自己做底层引擎。现在的年轻人是幸福的,有Unity这样向大众公开的引擎可以学习和使用,可以把精力更多地专注在游戏作品本身的创作,更应该把握这样的环境"。所以相比游戏界的前辈们用QBasic来写《仙剑奇侠传》这样的游戏作品,我们这代人无疑要幸福得多,可是我们这代人身上的担子却并不轻啊。如今的时代是一个多元化的时代,无论是在应用开发领域还是游戏开发领域,目标用户平台多样化成为我们不得不去面对的问题。以目前国内的移动手机平台为例,主流的移动手机平台就有IOS、Android、Window Phone三种。尽管从理论上来讲,我们可以不用写一行Object-C或者Java代码就可以开发出IOS、Android上的应用,可是如果我们需要和该平台的某些接口进行交互的时候,我们就不得不考虑Unity和这些平台的对接。举一个最为常见的例子,大家都知道在移动平台上流行一种应用内付费的模式,可是Unity并没有为我们提供相应的API,在IOS平台上由于苹果应用商店的唯一合法性,我们想在该平台上实现应用内付费就必须使用苹果官方提供的SDK,而苹果官方主推Object-C,所以我们不可避免地要和Object-C打交道。同样地,在Android平台上由于Android的开源导致Android设备的多样性、应用商店的多样性,我们选择将自己的应用上架的时候,同样面临着和Java或者C++(JNI)打交道的问题。所以,我们今天就来研究下Unity和其它平台的交互调用的问题,由于博主手头上只有一部用了很长时间的Android手机,所以我们今天就以Android平台为例,探讨下Unity和Android的交互吧!
Unity和Android交互通常有两种方式:
1、Unity调用为Android平台编写的插件
2、将Unity项目导出为Android项目,然后编写Android程序
这两种方式在实际的应用中各有优劣,我们今天先来讲解第一种方法,第二种方法博主稍后再和大家分享。首先来说说第一种方法的原理,我们首先用Eclipse编写一个Java的库文件(.Jar),在这个库文件中我们会封装一系列的方法来为Unity提供接口,我们将这个库文件导出后可以将其放置到一个特定的目录下(Plugins/Android),然后我们就可以利用Unity提供的API来调用这些方法。好了,下面我们来看具体的过程吧,首先我们创建一个Android项目,并将其设为一个库,这里将其包名设为com.android.android2unity,这个包名很重要,我们在Unity中将用到这个包名。我们接下来在MainActivity.java这个类中编写代码,它将作为我们封装Android API的一个类。在编写代码之前,让我们来做这样一件事情,将位于D:\Program Files\Unity\Editor\Data\PlaybackEngines\androidplayer\release\bin\classes.jar(不同的计算机上,这个位置可能会有所不同,大家按照自己的路径添加即可)这个库加入到我们的项目中来,如图:
这个库是Unity为Android提供的一个库,主要提供了支持该平台的Player,具体的大家可以自己去查看它的类空间。好了,我们下面编写这样一个脚本:
package com.android.android2unity; 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的包 */ import com.unity3d.player.UnityPlayerActivity; import com.unity3d.player.UnityPlayer; /* 如果需要Activity与Unity对接,可以通过继承UnityPlayerActivity来实现 */ /* 我们需要重写Activity的相关方法,在此节代码中,我们只需要调用Android API */ public class MainActivity extends UnityPlayerActivity { //当前上下文 private Context mContext=null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //初始化上下文 mContext=this; } /* 定义一个调用Unity方法的方法 ,基于UnitySendMessage实现 。由于当前Activity没有采用 */ /* Android的布局文件,所以我们无法使用Android的事件来完成这个方法的调用,我们 */ /* 采用Unity调用的方法,虽然这样显得舍近求远,可是我们知道了如何在Android中调用 */ /* Unity中定义的方法 */ public void InvokeUnity(String mStr) { UnityPlayer.UnitySendMessage("Vabille","SetCameraColor", ""); } /* 定义一个打开Activity的方法,我们将在Unity中调用此方法 */ public void StartWebView(String mUrl) { //创建一个Intent以打开一个新的Activity Intent intent=new Intent(mContext,WebActivity.class); //传入一个URL intent.putExtra("URL", mUrl); //打开Activity this.startActivity(intent); } /* 定义一个显示对话框的方法,我们将在Unity中调用此方法 */ public void ShowDialog(final String mTitle,final String mContent) { /* 在UI线程下执行相关方法 */ runOnUiThread(new Runnable() { @Override public void run() { //创建Builder AlertDialog.Builder mBuilder=new AlertDialog.Builder(MainActivity.this); //创建对话框 mBuilder.setTitle(mTitle) .setMessage(mContent) .setPositiveButton("确定", null); //显示对话框 mBuilder.show(); } }); } /* 定义一个使设备震动的方法,我们将在Unity中调用此方法 */ public void SetVibrator(long mTime) { Vibrator mVibrator=(Vibrator)getSystemService(VIBRATOR_SERVICE); mVibrator.vibrate(mTime); } /* 定义一个使设备震动的方法,我们将在Unity中调用此方法 */ public void ShowToast(String mContent) { Toast.makeText(mContext,mContent,Toast.LENGTH_LONG); } }在这段代码中我们让MainActivity类继承自Unity提供的UnityPlayerActivity,这样我们就可以在Android中使用Unity提供的某些方法。在这段代码中我们定义了5个方法,即用于显示对话框的ShowDialog()方法、用于显示Toast的ShowToast()方法、用于打开一个Activity的StartWebView()方法、用于使设备震动的方法SetVibrator()方法以及用于调用Unity中定义的方法的InvokeUnity()方法,其中ShowDialog()方法需要在UI线程下运行。在这里,我们不需要为当前的Activity设置一个布局文件,所以我们没有使用setContentView()方法。
接下来我们创建一个继承自Activity的类WebActivity,我们希望在这个页面中加载一个网页,网页的地址可以通过Unity来指定,我们首先来看布局文件avtivity_web.xml,它是一个简单的线性布局,在它的内部只有一个用于显示网页的WebView组件webView:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <WebView android:id="@+id/webView" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>相应地,它对应于WebActivity类,我们在MainActivity类中定义的StartWebView()方法即指向这个Acitivity:
package com.android.android2unity; import android.app.Activity; import android.os.Bundle; import android.webkit.WebView; public class WebActivity extends Activity { //网页组组件 private WebView mWebView; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //设置当前布局 setContentView(R.layout.activity_web); //获取WebView mWebView=(WebView)findViewById(R.id.webView); //获取URL String mUrl=this.getIntent().getStringExtra("URL"); //加载网页 mWebView.loadUrl(mUrl); } }它的意义很明确,从MainActivity中读取传来的字符型参数URL,这是一个网页地址,我们通过这个地址打开一个网页。最后,我们来看项目的配置文件AndroidManifest.xml文件,这是一个比较重要的文件,我们在这里要做的事情是注册WebActivity、给应用分配相应的权限:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.android2unity" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="9" android:targetSdkVersion="14" /> <uses-permission android:name="android.permission.VIBRATE"/> <uses-permission android:name="android.permission.INTERNET"/> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="com.android.android2unity.MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".WebActivity"/> </application> </manifest>好了,到现在为止,我们基本上完成了Android插件的编写,最后我们要做的就是将它输出为一个Jar库,以便我们在Unity中使用,在这里要注意的是,所有的Java类、Android生成的配置文件类(如属性、布局、值等)都要编译,博主之前就是因为没有搞懂Jar库的编译,结果在Java、Unity两边费了不少的周折、来回奔波。如果对Java熟悉的朋友,一定会采用命令来输出库文件吧,不过博主是个菜鸟,对Java命令不太熟悉,所以博主采用的方法是通过文件->导出来导出Jar文件的,如图,我们需要选中src目录和R.java文件,如果有第三方的库的话还需要选中libs文件夹:
这样我们就可以直接输出我们需要的Jar库文件了。这样我们就完成了在Android中编写Unity插件的任务,接下来,我们进入Unity的势力范围吧,哈哈!
在Unity这块呢,我们继续用我们前一篇文章中的项目,我们继续使用FF中这个漂亮的妹子(原谅我不知道她叫什么名字)如图:
下面请大家按照这样的结构来组织Android插件的目录:
----------Assets
----Plugins
-----Android
-----bin(存放导出的Jar)
-----libs(存放第三方的库)
-----res(资源文件夹,可直接复制Android项目)
-----AndroidManifest.xml(配置文件,可直接复制Android项目)
最终的效果应该是这样:
好了,下面我们来编写C#脚本AndroidAPI.cs
using UnityEngine; using System.Collections; public class AndroidAPI : MonoBehaviour { void Start() { //设置当前游戏体的名字,在Android中我们将使用这个名字 this.name="Vabille"; } //定义一个方法以改变摄像机背景颜色,我们将在Android中调用这个方法 void SetCameraColor() { //设置摄像机背景颜色 Camera.main.backgroundColor=new Color(1.0F,0.5F,0.5F); } void OnGUI () { //通过API调用对话框 if(GUILayout.Button("调用Android API显示对话框",GUILayout.Height(45))) { //获取Android的Java接口 AndroidJavaClass jc=new AndroidJavaClass("com.unity3d.player.UnityPlayer"); AndroidJavaObject jo=jc.GetStatic<AndroidJavaObject>("currentActivity"); //构造参数 string[] mObject=new string[2]; mObject[0]="Unity3D"; mObject[1]="Unity3D成功调用Android API"; //调用方法 jo.Call("ShowDialog",mObject); } //通过传值打开Activity if(GUILayout.Button("调用Android API中打开Activity",GUILayout.Height(45))) { //获取Android的Java接口 AndroidJavaClass jc=new AndroidJavaClass("com.unity3d.player.UnityPlayer"); AndroidJavaObject jo=jc.GetStatic<AndroidJavaObject>("currentActivity"); //打开博主的博客 jo.Call("StartWebView","http://blog.csdn.net/qinyuanpei"); } //通过API调用Toast if(GUILayout.Button("调用Android API中的Toast",GUILayout.Height(45))) { //获取Android的Java接口 AndroidJavaClass jc=new AndroidJavaClass("com.unity3d.player.UnityPlayer"); AndroidJavaObject jo=jc.GetStatic<AndroidJavaObject>("currentActivity"); //打开博主的博客 jo.Call("ShowToast","为Unity3D编写Android插件是件苦差事!"); } //通过API调用Toast if(GUILayout.Button("调用Android API中的震动方法",GUILayout.Height(45))) { //获取Android的Java接口 AndroidJavaClass jc=new AndroidJavaClass("com.unity3d.player.UnityPlayer"); AndroidJavaObject jo=jc.GetStatic<AndroidJavaObject>("currentActivity"); //打开博主的博客 jo.Call("SetVibrator",40); } //通过API调用Toast if(GUILayout.Button("通过SendMessage调用Unity中的方法",GUILayout.Height(45))) { //获取Android的Java接口 AndroidJavaClass jc=new AndroidJavaClass("com.unity3d.player.UnityPlayer"); AndroidJavaObject jo=jc.GetStatic<AndroidJavaObject>("currentActivity"); //打开博主的博客 jo.Call("InvokeUnity",""); } } }这里的方法都是调用在Android中定义好的方法,主要是利用AndroidJavaObject的Call()方法,该方法有两个参数,第一个参数是一个字符型的变量,是我们要调用的方法的名字,第二个参数是一个object[]类型,是我们要调用的方法的参数。好了,我们一起来看看手机上运行的效果吧!
这就是今天的成果了,不过大家都知道我的习惯,每次我在文章中解决不了的问题都会在博客里说出来让大家帮我解决,而今天的问题就是ShowToast()方法和SetVibrator()方法一直没有被调用,博主怀疑是不是Unity提供的Java接口能力有限,只能访问Android的某些接口,不知道大家是怎么看的,如果大家知道的话,希望大家可以告诉我啊,呵呵。
最后,想说的一点就是我们在C#里定义了一个SetCameraColor()的方法,这是一个改变摄像机背景的方法,我们在Android中使用InvokeUnity()方法来访问这个方法,由于在第一个页面中,我们没有使用Android的布局元素,因此Android的事件我们无法使用,我们依然采用在Unity中调用的方法,最然这样显得舍近求远,但是这说明了一个问题,Android可以调用Unity的的方法,而具体实现就是通过SendMessage()来实现的,博主个人觉得这是一种委托吧。大家注意到最后屏幕的颜色变成了红色,说明这个方法被调用了。好了,今天的内容就是这样啦,写完这篇文章博主感觉好累啊!
每日箴言:孤独是人生的伴侣,寂寞是人生的常客。人生就是一场修行,修行要有耐性,要能忍受孤独、无悔寂寞。
喜欢我的博客请记住我的名字:秦元培,我博客地址是blog.csdn.net/qinyuanpei。
转载请注明出处,本文作者:秦元培,本文出处:http://blog.csdn.net/qinyuanpei/article/details/39348677
本文源代码下载 :Unity项目 Android项目