Unity3D]Unity3D遊戲開發之Unity與Android互動呼叫研究
本文轉載自: http://blog.csdn.net/qinyuanpei/article/details/39348677
記得"仙劍之父“姚壯憲作為評委參加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,具體的大家可以自己去檢視它的類空間。好了,我們下面編寫這樣一個指令碼:
[csharp] view plaincopyprint?- 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);
- }
- }
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:
[html] view plaincopyprint?- <?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>
<?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:
[java]
view plaincopyprint?
- 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);
- }
- }
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、給應用分配相應的許可權:
[html]
view plaincopyprint?
- <?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>
<?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
[csharp] view plaincopyprint?- 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","");
- }
- }
- }
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()來實現的,博主個人覺得這是一種委託吧。大家注意到最後螢幕的顏色變成了紅色,說明這個方法被呼叫了。好了,今天的內容就是這樣啦,寫完這篇文章博主感覺好累啊!