[Unity3D]Unity3D遊戲開發之在Android視圖中嵌入Unity視圖

  ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

   喜歡我的博客請記住我的名字:秦元培,我博客地址是blog.csdn.net/qinyuanpei

   轉載請註明出處,本文作者:秦元培,本文出處:http://blog.csdn.net/qinyuanpei/article/details/39380717

     ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

   

   大家好,我是秦元培,歡迎大家關注我的博客,我的博客地址是blog.csdn.net/qinyuanpei。最近博主發現博主的文章被很多的網站(如遊戲蠻牛)等非法轉載,在轉載的過程中博主原來的文章出處和鏈接均被去掉,更讓博主覺得生氣的事情是某些人將博主的文章複製一部分以原創文章的形式發表在CSDN上,這樣CSDN在根據Tag推薦相關文章的時候,就會將一部分文章連接到博主博客以外的地方,博主並不是一個小氣的人,只是這些博客都是傾注了博主自己心血而寫成的,如此作踐難免讓博主本人心寒。從今天起,博主所有的文章、配圖都會加上鍊接,希望以此告誡那些非法轉載博主文章的人懂得自重、懂得尊重博主的勞動成果。

   好了,這些人我暫且不去理會,今天我們繼續來研究Unity在Android平臺上的擴展,通過昨天的學習,大家已經知道Unity和Android是可以互相調用的,可是相信大家從昨天的文章中可以看出,如果單純地從調用Android接口的角度來看,我們已經可以實現這一目的。可是從實際開發的角度來看,我們只是邁出了很小的一步。爲什麼這麼說呢,因爲在實際的開發中可能我們不僅需要從接口上實現與Unity的對接,而且需要從界面上實現與Unity的對接。比如,現在主流的網遊都會在遊戲開始的時候給玩家一個選擇創建角色的過程,通常界面上會顯示各種類型的角色設定,玩家可以通過界面瞭解每種類型的角色的特點,從而選擇合適自己的角色。從技術上來講,這一部分我們可以完全使用Unity3D的GUI系統來實現,不過本文的目的在於探討Unity和Android的對接問題,因此我們可以假定Android在整個對接過程中扮演着界面渲染的角色,而Unity則負責遊戲邏輯的維護。那麼,這樣就誕生了我們今天的問題,能不能將Unity作爲Android界面的一部分嵌入到Android應用中呢?答案當然是肯定的。


    一、爲Unity編寫Android插件

    首先,我們來看Unity中爲Android提供的類文件UnityPlayerNativeActivity.java,該文件位於如下位置:

 D:\ProgramFiles\Unity\Editor\Data\PlaybackEngines\androidplayer\com\unity3d\player(在不同的計算機上可能會有所不同,大家靈活運用即可)

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.unity3d.player;  
  2.   
  3. import android.app.NativeActivity;  
  4. import android.content.res.Configuration;  
  5. import android.graphics.PixelFormat;  
  6. import android.os.Bundle;  
  7. import android.view.KeyEvent;  
  8. import android.view.MotionEvent;  
  9. import android.view.View;  
  10. import android.view.Window;  
  11. import android.view.WindowManager;  
  12.   
  13. public class UnityPlayerNativeActivity extends NativeActivity  
  14. {  
  15.     protected UnityPlayer mUnityPlayer;     // don't change the name of this variable; referenced from native code  
  16.   
  17.     // Setup activity layout  
  18.     @Override protected void onCreate (Bundle savedInstanceState)  
  19.     {  
  20.         requestWindowFeature(Window.FEATURE_NO_TITLE);  
  21.         super.onCreate(savedInstanceState);  
  22.   
  23.         getWindow().takeSurface(null);  
  24.         setTheme(android.R.style.Theme_NoTitleBar_Fullscreen);  
  25.         getWindow().setFormat(PixelFormat.RGB_565);  
  26.   
  27.         mUnityPlayer = new UnityPlayer(this);  
  28.         if (mUnityPlayer.getSettings ().getBoolean ("hide_status_bar"true))  
  29.             getWindow ().setFlags (WindowManager.LayoutParams.FLAG_FULLSCREEN,  
  30.                                    WindowManager.LayoutParams.FLAG_FULLSCREEN);  
  31.   
  32.         setContentView(mUnityPlayer);  
  33.         mUnityPlayer.requestFocus();  
  34.     }  
  35.   
  36.     // Quit Unity  
  37.     @Override protected void onDestroy ()  
  38.     {  
  39.         mUnityPlayer.quit();  
  40.         super.onDestroy();  
  41.     }  
  42.   
  43.     // Pause Unity  
  44.     @Override protected void onPause()  
  45.     {  
  46.         super.onPause();  
  47.         mUnityPlayer.pause();  
  48.     }  
  49.   
  50.     // Resume Unity  
  51.     @Override protected void onResume()  
  52.     {  
  53.         super.onResume();  
  54.         mUnityPlayer.resume();  
  55.     }  
  56.   
  57.     // This ensures the layout will be correct.  
  58.     @Override public void onConfigurationChanged(Configuration newConfig)  
  59.     {  
  60.         super.onConfigurationChanged(newConfig);  
  61.         mUnityPlayer.configurationChanged(newConfig);  
  62.     }  
  63.   
  64.     // Notify Unity of the focus change.  
  65.     @Override public void onWindowFocusChanged(boolean hasFocus)  
  66.     {  
  67.         super.onWindowFocusChanged(hasFocus);  
  68.         mUnityPlayer.windowFocusChanged(hasFocus);  
  69.     }  
  70.   
  71.     // For some reason the multiple keyevent type is not supported by the ndk.  
  72.     // Force event injection by overriding dispatchKeyEvent().  
  73.     @Override public boolean dispatchKeyEvent(KeyEvent event)  
  74.     {  
  75.         if (event.getAction() == KeyEvent.ACTION_MULTIPLE)  
  76.             return mUnityPlayer.injectEvent(event);  
  77.         return super.dispatchKeyEvent(event);  
  78.     }  
  79.   
  80.     // Pass any events not handled by (unfocused) views straight to UnityPlayer  
  81.     @Override public boolean onKeyUp(int keyCode, KeyEvent event)     { return mUnityPlayer.injectEvent(event); }  
  82.     @Override public boolean onKeyDown(int keyCode, KeyEvent event)   { return mUnityPlayer.injectEvent(event); }  
  83.     @Override public boolean onTouchEvent(MotionEvent event)          { return mUnityPlayer.injectEvent(event); }  
  84.     /*API12*/ public boolean onGenericMotionEvent(MotionEvent event)  { return mUnityPlayer.injectEvent(event); }  
  85. }  
熟悉Android開發的朋友一定對這個類覺得眼熟吧,NativeActivity是Google在Android3.0後推出的,其目的是爲了讓開發者使用C/C++在NDK環境下管理Activity的生存週期,於是我們可以大膽的猜測Unity之所以可以和Android進行交互是因爲NDK提供了方便之門,果然,博主在Unity的目錄下找到了
libmain.so、libunity.so、libmono.so這三個文件,這證實我們的猜想是正確的。在這個類中,我們可以看到Unity做了大量的初始化工作,並且重寫了NativeActvity的相關方法。不知道大家還記不記得我們在前一篇文章中定義主Activity時,我們是讓它繼承自UnityPlayerActivity而不是Android的Activity。那麼,這個UnityPlayerActivity是什麼呢?我們打開它的文件:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.unity3d.player;  
  2.   
  3. /** 
  4.  * @deprecated Use UnityPlayerNativeActivity instead. 
  5.  */  
  6. public class UnityPlayerActivity extends UnityPlayerNativeActivity { }  
這時候我相信大家和博主一樣都有一種恍然大悟的感覺,原來它是繼承自UnityPlayerNativeActivity的,換句話說Unity在Android這部分其實做了兩件事,即初始化Activity和重寫相關的方法。我們注意到代碼中的註釋提示這個類已經deprecated建議我們使用UnityPlayerNativeActivity,這點我們暫時不用管,我們依然使用UnityPlayerActivity,因爲它和UnityPlayerNativeActivity本質上是一樣的。好了,到目前爲止,如果大家已經理解了這裏所有的內容,那麼我們可以浩浩蕩蕩地衝向Eclipse開始編寫Android項目了。

    

     和昨天一樣,我們創建一個Android項目並將其設爲庫,這裏我們將其包名設爲com.android.unityview4android。這個名稱很重要,我們在Unity中將繼續使用這個名字。首先我們來創建一個Android的佈局文件activity_main.xml:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:orientation="vertical"  
  6.     tools:context=".MainActivity" >  
  7.     <Button  
  8.         android:id="@+id/BtnPre"  
  9.         android:layout_width="match_parent"  
  10.         android:layout_height="wrap_content"  
  11.         android:layout_alignParentTop="true"  
  12.         android:text="@string/BtnPre" />  
  13.     <LinearLayout    
  14.         android:id="@+id/UnityView"    
  15.         android:layout_width="match_parent"    
  16.         android:layout_height="match_parent"    
  17.         android:layout_above="@+id/BtnNext"    
  18.         android:layout_below="@+id/BtnPre"    
  19.         android:orientation="vertical" >    
  20.     </LinearLayout>  
  21.     <Button  
  22.         android:id="@+id/BtnNext"  
  23.         android:layout_width="match_parent"  
  24.         android:layout_height="wrap_content"  
  25.         android:layout_alignParentBottom="true"  
  26.         android:text="@string/BtnNext" />  
  27. </RelativeLayout>  

這裏我們放置了兩個Button按鈕,分別用來控制Unity向前、向後切換模型,在屏幕中間有一個叫做UnityView的線性佈局,它將作爲Unity視圖的父控件,即我們會將Unity的視圖放到這個控件裏。我們注意到在UnityPlayerNativeActivity.java類中有一個UnityPlayer類型的變量mUnityPlayer,該類型博主目前沒有找到官方的相關說明,博主推測它應該是負責Unity界面渲染的一個類吧,在這個類中有一個getView()方法,它將返回一個View類型的值,我們便可以通過這種方法將其添加到Android視圖裏,因爲我們已經準備好了一個UnityView的父控件,所以這一切實現起來變得相當地容易:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.android.unityview4android;  
  2.   
  3. /*導入Unity提供的類*/  
  4. import com.unity3d.player.UnityPlayer;  
  5. import com.unity3d.player.UnityPlayerActivity;  
  6.   
  7. import android.os.Bundle;  
  8. import android.view.Menu;  
  9. import android.view.View;  
  10. import android.view.View.OnClickListener;  
  11. import android.widget.Button;  
  12. import android.widget.LinearLayout;  
  13.   
  14. public class MainActivity extends UnityPlayerActivity {  
  15.   
  16.     private Button BtnPre,BtnNext;  
  17.     @Override  
  18.     protected void onCreate(Bundle savedInstanceState) {  
  19.         super.onCreate(savedInstanceState);  
  20.         //設置當前佈局文件  
  21.         setContentView(R.layout.activity_main);  
  22.         //獲取顯示Unity視圖的父控件  
  23.         LinearLayout mParent=(LinearLayout)findViewById(R.id.UnityView);  
  24.         //獲取Unity視圖  
  25.         View mView=mUnityPlayer.getView();  
  26.         //將Unity視圖添加到Android視圖中  
  27.         mParent.addView(mView);  
  28.   
  29.         //上一個  
  30.         BtnPre=(Button)findViewById(R.id.BtnPre);  
  31.         BtnPre.setOnClickListener(new OnClickListener()  
  32.         {  
  33.             @Override  
  34.             public void onClick(View arg0)   
  35.             {  
  36.                 UnityPlayer.UnitySendMessage("GameObject""ShowPrevious""");  
  37.             }  
  38.         });  
  39.           
  40.         //下一個  
  41.         BtnNext=(Button)findViewById(R.id.BtnNext);  
  42.         BtnNext.setOnClickListener(new OnClickListener()  
  43.         {  
  44.             @Override  
  45.             public void onClick(View arg0)   
  46.             {  
  47.                 UnityPlayer.UnitySendMessage("GameObject""ShowNext""");  
  48.             }  
  49.         });  
  50.     }  
  51.   
  52.     @Override  
  53.     public boolean onCreateOptionsMenu(Menu menu) {  
  54.         // Inflate the menu; this adds items to the action bar if it is present.  
  55.         getMenuInflater().inflate(R.menu.main, menu);  
  56.         return true;  
  57.     }  
  58.   
  59. }  
好了,這樣我們就完成了Jar庫的編寫,我們只要將Jar庫導入到Unity中,並且按照上一篇文章中所述的方法構建Android插件目錄組織相關資源就可以了。最後我們給出AndroidManifest.xml文件:
[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     package="com.android.unityview4android"  
  4.     android:versionCode="1"  
  5.     android:versionName="1.0" >  
  6.   
  7.     <uses-sdk  
  8.         android:minSdkVersion="9"  
  9.         android:targetSdkVersion="17" />  
  10.   
  11.     <application  
  12.         android:allowBackup="true"  
  13.         android:icon="@drawable/ic_launcher"  
  14.         android:label="@string/app_name"  
  15.         android:theme="@style/AppTheme" >  
  16.         <activity  
  17.             android:name="com.android.unityview4android.MainActivity"  
  18.             android:label="@string/app_name" >  
  19.             <intent-filter>  
  20.                 <action android:name="android.intent.action.MAIN" />  
  21.   
  22.                 <category android:name="android.intent.category.LAUNCHER" />  
  23.             </intent-filter>  
  24.         </activity>  
  25.     </application>  
  26.   
  27. </manifest>  


     二、創建Unity項目
     好了,現在我們來創建Unity項目,我們這裏創建一個簡單地場景,場景中只有一個空遊戲體和攝像機,我們將通過腳本的形式來控制Unity渲染,主要的腳本代碼如下:

[csharp] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. using UnityEngine;  
  2. using System.Collections;  
  3.   
  4. public class ModelManager : MonoBehaviour {  
  5.   
  6.     //模型  
  7.     public GameObject[] Models;  
  8.         //當前索引  
  9.     private int index=0;  
  10.     //當前對象  
  11.     private GameObject mObject;  
  12.   
  13.     void Start ()   
  14.     {  
  15.         this.name="GameObject";  
  16.         //生成第一個物體  
  17.         mObject=(GameObject)Instantiate(Models[index]);  
  18.     }  
  19.   
  20.     public void ShowNext()  
  21.     {  
  22.        //使索引增加  
  23.        index+=1;  
  24.        //範圍控制  
  25.        if(index>Models.Length-1){  
  26.             index=0;  
  27.        }  
  28.        //銷燬原來物體  
  29.        Destroy(mObject);  
  30.        //生成新物體  
  31.        mObject=(GameObject)Instantiate(Models[index]);  
  32.     }  
  33.   
  34.     public void ShowPrevious()  
  35.     {  
  36.         //使索引減少  
  37.         index-=1;  
  38.         //範圍控制  
  39.         if(index<0){  
  40.             index=Models.Length-1;  
  41.         }  
  42.         //銷燬原來物體  
  43.         Destroy(mObject);  
  44.         //生成新物體  
  45.         mObject=(GameObject)Instantiate(Models[index]);  
  46.     }  
  47. }  
這段腳本我們綁定到這個空的遊戲體上,腳本的兩個方法ShowNext()和ShowPrevious()相對應,通過UnitySendMessage()實現通信。好了,我們來運行項目,如圖所示:


我們可以注意到當程序打開的那一瞬間,是Android視圖先加載、然後Unity視圖才加載的,給人整體的感覺就是在Activity裏面嵌套了一個Activity。可是博主馬上就發現了一個問題,這Android視圖中的按鈕無法取得焦點啊,點擊事件無效。這是爲什麼呢?後來博主找到的解決方法是在AndroidManifest.xml文件中的activity子節點下增加如下兩行代碼:

[csharp] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <meta-data android:name="android.app.lib_name" android:value="unity" />   
  2. <meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" android:value="true" />  
這樣問題是解決了,可是我們得搞懂爲什麼吧,在Unity官方文檔中,我們找到了答案:

請注意,NativeActivity在Android2.3以後被引入而且不支持該版本以下的設備。因爲觸摸/運動事件處理在本機代碼,Java視圖通常不會看到這些事件。然而,在統一轉發機制允許將事件傳播到DalvikVM。爲了使用這個機制,您需要修改manifest文件如下:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.company.product">  
  3.   <application android:icon="@drawable/app_icon" android:label="@string/app_name">  
  4.     <activity android:name=".OverrideExampleNative"  
  5.              android:label="@string/app_name">  
  6.     <meta-data android:name="android.app.lib_name" android:value="unity" />  
  7.     <meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" android:value="true" />  
  8.         <intent-filter>  
  9.             <action android:name="android.intent.action.MAIN" />  
  10.             <category android:name="android.intent.category.LAUNCHER" />  
  11.         </intent-filter>  
  12.     </activity>  
  13.   </application>  
  14. </manifest>   
這段話的意思就是說Android的事件是運行在本機代碼中的,Java視圖是看不到這些事件的,然而我們可以通過Unity提供的統一轉發機制將這些事件發送到Android虛擬機,爲了使用這個機制,我們必須修改Android的AndroidManifest.xml文件。博主感覺Android插件這塊官方目前做得不是很好,總是給人一種雲裏霧裏的感覺,而且通過各種語言相互調用增加了調試的難度,關於Android更多的內容,大家可以參考官方的文檔:點擊這裏.好了,今天的內容就是這樣啦,希望大家喜歡。最後再來看看程序運行的效果吧



每日箴言:這年頭連夢想都是有期限的。有想法就去做,即使失敗了都比沒做來得好,因爲失敗至少給了你經驗,沒做只會帶給你懊悔。——朱德庸





   ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

   喜歡我的博客請記住我的名字:秦元培,我博客地址是blog.csdn.net/qinyuanpei

   轉載請註明出處,本文作者:秦元培,本文出處:http://blog.csdn.net/qinyuanpei/article/details/39348677

   ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

   

 爲方便大家研究這篇文章,最後給出源代碼:

Unity項目源代碼下載

Android項目源代碼下載


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章