Unity3D游戏开发之在Android视图中嵌入Unity视图
转载自:http://blog.csdn.net/qinyuanpei/article/details/39380717
今天我们继续来研究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 plain copy print ?
- packagecom.unity3d.player;
- importandroid.app.NativeActivity;
- importandroid.content.res.Configuration;
- importandroid.graphics.PixelFormat;
- importandroid.os.Bundle;
- importandroid.view.KeyEvent;
- importandroid.view.MotionEvent;
- importandroid.view.View;
- importandroid.view.Window;
- importandroid.view.WindowManager;
- publicclassUnityPlayerNativeActivityextendsNativeActivity
- {
- protectedUnityPlayermUnityPlayer;//don'tchangethenameofthisvariable;referencedfromnativecode
- //Setupactivitylayout
- @OverrideprotectedvoidonCreate(BundlesavedInstanceState)
- {
- requestWindowFeature(Window.FEATURE_NO_TITLE);
- super.onCreate(savedInstanceState);
- getWindow().takeSurface(null);
- setTheme(android.R.style.Theme_NoTitleBar_Fullscreen);
- getWindow().setFormat(PixelFormat.RGB_565);
- mUnityPlayer=newUnityPlayer(this);
- if(mUnityPlayer.getSettings().getBoolean("hide_status_bar",true))
- getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
- WindowManager.LayoutParams.FLAG_FULLSCREEN);
- setContentView(mUnityPlayer);
- mUnityPlayer.requestFocus();
- }
- //QuitUnity
- @OverrideprotectedvoidonDestroy()
- {
- mUnityPlayer.quit();
- super.onDestroy();
- }
- //PauseUnity
- @OverrideprotectedvoidonPause()
- {
- super.onPause();
- mUnityPlayer.pause();
- }
- //ResumeUnity
- @OverrideprotectedvoidonResume()
- {
- super.onResume();
- mUnityPlayer.resume();
- }
- //Thisensuresthelayoutwillbecorrect.
- @OverridepublicvoidonConfigurationChanged(ConfigurationnewConfig)
- {
- super.onConfigurationChanged(newConfig);
- mUnityPlayer.configurationChanged(newConfig);
- }
- //NotifyUnityofthefocuschange.
- @OverridepublicvoidonWindowFocusChanged(booleanhasFocus)
- {
- super.onWindowFocusChanged(hasFocus);
- mUnityPlayer.windowFocusChanged(hasFocus);
- }
- //Forsomereasonthemultiplekeyeventtypeisnotsupportedbythendk.
- //ForceeventinjectionbyoverridingdispatchKeyEvent().
- @OverridepublicbooleandispatchKeyEvent(KeyEventevent)
- {
- if(event.getAction()==KeyEvent.ACTION_MULTIPLE)
- returnmUnityPlayer.injectEvent(event);
- returnsuper.dispatchKeyEvent(event);
- }
- //Passanyeventsnothandledby(unfocused)viewsstraighttoUnityPlayer
- @OverridepublicbooleanonKeyUp(intkeyCode,KeyEventevent){returnmUnityPlayer.injectEvent(event);}
- @OverridepublicbooleanonKeyDown(intkeyCode,KeyEventevent){returnmUnityPlayer.injectEvent(event);}
- @OverridepublicbooleanonTouchEvent(MotionEventevent){returnmUnityPlayer.injectEvent(event);}
- /*API12*/publicbooleanonGenericMotionEvent(MotionEventevent){returnmUnityPlayer.injectEvent(event);}
- }
package com.unity3d.player;import android.app.NativeActivity;import android.content.res.Configuration;import android.graphics.PixelFormat;import android.os.Bundle;import android.view.KeyEvent;import android.view.MotionEvent;import android.view.View;import android.view.Window;import android.view.WindowManager;public class UnityPlayerNativeActivity extends NativeActivity{protected UnityPlayer mUnityPlayer;// don't change the name of this variable; referenced from native code// Setup activity layout@Override protected void onCreate (Bundle savedInstanceState){requestWindowFeature(Window.FEATURE_NO_TITLE);super.onCreate(savedInstanceState);getWindow().takeSurface(null);setTheme(android.R.style.Theme_NoTitleBar_Fullscreen);getWindow().setFormat(PixelFormat.RGB_565);mUnityPlayer = new UnityPlayer(this);if (mUnityPlayer.getSettings ().getBoolean ("hide_status_bar", true))getWindow ().setFlags (WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);setContentView(mUnityPlayer);mUnityPlayer.requestFocus();}// Quit Unity@Override protected void onDestroy (){mUnityPlayer.quit();super.onDestroy();}// Pause Unity@Override protected void onPause(){super.onPause();mUnityPlayer.pause();}// Resume Unity@Override protected void onResume(){super.onResume();mUnityPlayer.resume();}// This ensures the layout will be correct.@Override public void onConfigurationChanged(Configuration newConfig){super.onConfigurationChanged(newConfig);mUnityPlayer.configurationChanged(newConfig);}// Notify Unity of the focus change.@Override public void onWindowFocusChanged(boolean hasFocus){super.onWindowFocusChanged(hasFocus);mUnityPlayer.windowFocusChanged(hasFocus);}// For some reason the multiple keyevent type is not supported by the ndk.// Force event injection by overriding dispatchKeyEvent().@Override public boolean dispatchKeyEvent(KeyEvent event){if (event.getAction() == KeyEvent.ACTION_MULTIPLE)return mUnityPlayer.injectEvent(event);return super.dispatchKeyEvent(event);}// Pass any events not handled by (unfocused) views straight to UnityPlayer@Override public boolean onKeyUp(int keyCode, KeyEvent event) { return mUnityPlayer.injectEvent(event); }@Override public boolean onKeyDown(int keyCode, KeyEvent event) { return mUnityPlayer.injectEvent(event); }@Override public boolean onTouchEvent(MotionEvent event) { return mUnityPlayer.injectEvent(event); }/*API12*/ public boolean onGenericMotionEvent(MotionEvent event) { return mUnityPlayer.injectEvent(event); }}熟悉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 plain copy print ?
- packagecom.unity3d.player;
- /**
- *@deprecatedUseUnityPlayerNativeActivityinstead.
- */
- publicclassUnityPlayerActivityextendsUnityPlayerNativeActivity{}
package com.unity3d.player;/** * @deprecated Use UnityPlayerNativeActivity instead. */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 plain copy print ?
- <RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- tools:context=".MainActivity">
- <Button
- android:id="@+id/BtnPre"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
- android:text="@string/BtnPre"/>
- <LinearLayout
- android:id="@+id/UnityView"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_above="@+id/BtnNext"
- android:layout_below="@+id/BtnPre"
- android:orientation="vertical">
- </LinearLayout>
- <Button
- android:id="@+id/BtnNext"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentBottom="true"
- android:text="@string/BtnNext"/>
- </RelativeLayout>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity" > <Button android:id="@+id/BtnPre" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:text="@string/BtnPre" /> <LinearLayout android:id="@+id/UnityView" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_above="@+id/BtnNext" android:layout_below="@+id/BtnPre" android:orientation="vertical" > </LinearLayout> <Button android:id="@+id/BtnNext" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:text="@string/BtnNext" /></RelativeLayout>
这里我们放置了两个Button按钮,分别用来控制Unity向前、向后切换模型,在屏幕中间有一个叫做UnityView的线性布局,它将作为Unity视图的父控件,即我们会将Unity的视图放到这个控件里。我们注意到在UnityPlayerNativeActivity.java类中有一个UnityPlayer类型的变量mUnityPlayer,该类型博主目前没有找到官方的相关说明,博主推测它应该是负责Unity界面渲染的一个类吧,在这个类中有一个getView()方法,它将返回一个View类型的值,我们便可以通过这种方法将其添加到Android视图里,因为我们已经准备好了一个UnityView的父控件,所以这一切实现起来变得相当地容易:
[java] view plain copy print ?
- packagecom.android.unityview4android;
- /*导入Unity提供的类*/
- importcom.unity3d.player.UnityPlayer;
- importcom.unity3d.player.UnityPlayerActivity;
- importandroid.os.Bundle;
- importandroid.view.Menu;
- importandroid.view.View;
- importandroid.view.View.OnClickListener;
- importandroid.widget.Button;
- importandroid.widget.LinearLayout;
- publicclassMainActivityextendsUnityPlayerActivity{
- privateButtonBtnPre,BtnNext;
- @Override
- protectedvoidonCreate(BundlesavedInstanceState){
- super.onCreate(savedInstanceState);
- //设置当前布局文件
- setContentView(R.layout.activity_main);
- //获取显示Unity视图的父控件
- LinearLayoutmParent=(LinearLayout)findViewById(R.id.UnityView);
- //获取Unity视图
- ViewmView=mUnityPlayer.getView();
- //将Unity视图添加到Android视图中
- mParent.addView(mView);
- //上一个
- BtnPre=(Button)findViewById(R.id.BtnPre);
- BtnPre.setOnClickListener(newOnClickListener()
- {
- @Override
- publicvoidonClick(Viewarg0)
- {
- UnityPlayer.UnitySendMessage("GameObject","ShowPrevious","");
- }
- });
- //下一个
- BtnNext=(Button)findViewById(R.id.BtnNext);
- BtnNext.setOnClickListener(newOnClickListener()
- {
- @Override
- publicvoidonClick(Viewarg0)
- {
- UnityPlayer.UnitySendMessage("GameObject","ShowNext","");
- }
- });
- }
- @Override
- publicbooleanonCreateOptionsMenu(Menumenu){
- //Inflatethemenu;thisaddsitemstotheactionbarifitispresent.
- getMenuInflater().inflate(R.menu.main,menu);
- returntrue;
- }
- }
package com.android.unityview4android;/*导入Unity提供的类*/import com.unity3d.player.UnityPlayer;import com.unity3d.player.UnityPlayerActivity;import android.os.Bundle;import android.view.Menu;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.LinearLayout;public class MainActivity extends UnityPlayerActivity {private Button BtnPre,BtnNext;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//设置当前布局文件setContentView(R.layout.activity_main);//获取显示Unity视图的父控件LinearLayout mParent=(LinearLayout)findViewById(R.id.UnityView);//获取Unity视图View mView=mUnityPlayer.getView();//将Unity视图添加到Android视图中mParent.addView(mView);//上一个BtnPre=(Button)findViewById(R.id.BtnPre);BtnPre.setOnClickListener(new OnClickListener(){@Overridepublic void onClick(View arg0) {UnityPlayer.UnitySendMessage("GameObject", "ShowPrevious", "");}});//下一个BtnNext=(Button)findViewById(R.id.BtnNext);BtnNext.setOnClickListener(new OnClickListener(){@Overridepublic void onClick(View arg0) {UnityPlayer.UnitySendMessage("GameObject", "ShowNext", "");}});}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {// Inflate the menu; this adds items to the action bar if it is present.getMenuInflater().inflate(R.menu.main, menu);return true;}}好了,这样我们就完成了Jar库的编写,我们只要将Jar库导入到Unity中,并且按照上一篇文章中所述的方法构建Android插件目录组织相关资源就可以了。最后我们给出AndroidManifest.xml文件:
[html] view plain copy print ?
- <?xmlversion="1.0"encoding="utf-8"?>
- <manifestxmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.unityview4android"
- android:versionCode="1"
- android:versionName="1.0">
- <uses-sdk
- android:minSdkVersion="9"
- android:targetSdkVersion="17"/>
- <application
- android:allowBackup="true"
- android:icon="@drawable/ic_launcher"
- android:label="@string/app_name"
- android:theme="@style/AppTheme">
- <activity
- android:name="com.android.unityview4android.MainActivity"
- android:label="@string/app_name">
- <intent-filter>
- <actionandroid:name="android.intent.action.MAIN"/>
- <categoryandroid:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
- </application>
- </manifest>
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.unityview4android" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="9" android:targetSdkVersion="17" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="com.android.unityview4android.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> </application></manifest>
好了,现在我们来创建Unity项目,我们这里创建一个简单地场景,场景中只有一个空游戏体和摄像机,我们将通过脚本的形式来控制Unity渲染,主要的脚本代码如下:
[csharp] view plain copy print ?
- usingUnityEngine;
- usingSystem.Collections;
- publicclassModelManager:MonoBehaviour{
- //模型
- publicGameObject[]Models;
- //当前索引
- privateintindex=0;
- //当前对象
- privateGameObjectmObject;
- voidStart()
- {
- this.name="GameObject";
- //生成第一个物体
- mObject=(GameObject)Instantiate(Models[index]);
- }
- publicvoidShowNext()
- {
- //使索引增加
- index+=1;
- //范围控制
- if(index>Models.Length-1){
- index=0;
- }
- //销毁原来物体
- Destroy(mObject);
- //生成新物体
- mObject=(GameObject)Instantiate(Models[index]);
- }
- publicvoidShowPrevious()
- {
- //使索引减少
- index-=1;
- //范围控制
- if(index<0){
- index=Models.Length-1;
- }
- //销毁原来物体
- Destroy(mObject);
- //生成新物体
- mObject=(GameObject)Instantiate(Models[index]);
- }
- }
using UnityEngine;using System.Collections;public class ModelManager : MonoBehaviour {//模型public GameObject[] Models;//当前索引private int index=0;//当前对象private GameObject mObject;void Start () {this.name="GameObject";//生成第一个物体mObject=(GameObject)Instantiate(Models[index]);}public void ShowNext(){ //使索引增加 index+=1; //范围控制 if(index>Models.Length-1){index=0; } //销毁原来物体 Destroy(mObject); //生成新物体 mObject=(GameObject)Instantiate(Models[index]);}public void ShowPrevious(){//使索引减少index-=1;//范围控制if(index<0){index=Models.Length-1;}//销毁原来物体Destroy(mObject);//生成新物体mObject=(GameObject)Instantiate(Models[index]);}}这段脚本我们绑定到这个空的游戏体上,脚本的两个方法ShowNext()和ShowPrevious()相对应,通过UnitySendMessage()实现通信。好了,我们来运行项目,如图所示:
我们可以注意到当程序打开的那一瞬间,是Android视图先加载、然后Unity视图才加载的,给人整体的感觉就是在Activity里面嵌套了一个Activity。可是博主马上就发现了一个问题,这Android视图中的按钮无法取得焦点啊,点击事件无效。这是为什么呢?后来博主找到的解决方法是在AndroidManifest.xml文件中的activity子节点下增加如下两行代码:
[csharp] view plain copy print ?
- <meta-dataandroid:name="android.app.lib_name"android:value="unity"/>
- <meta-dataandroid:name="unityplayer.ForwardNativeEventsToDalvik"android:value="true"/>
<meta-data android:name="android.app.lib_name" android:value="unity" /> <meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" android:value="true" />这样问题是解决了,可是我们得搞懂为什么吧,在Unity 官方文档中,我们找到了答案:
请注意,NativeActivity在Android2.3以后被引入而且不支持该版本以下的设备。因为触摸/运动事件处理在本机代码,Java视图通常不会看到这些事件。然而,在统一转发机制允许将事件传播到DalvikVM。为了使用这个机制,您需要修改manifest文件如下:
[html] view plain copy print ?
- <?xmlversion="1.0"encoding="utf-8"?>
- <manifestxmlns:android="http://schemas.android.com/apk/res/android"package="com.company.product">
- <applicationandroid:icon="@drawable/app_icon"android:label="@string/app_name">
- <activityandroid:name=".OverrideExampleNative"
- android:label="@string/app_name">
- <meta-dataandroid:name="android.app.lib_name"android:value="unity"/>
- <meta-dataandroid:name="unityplayer.ForwardNativeEventsToDalvik"android:value="true"/>
- <intent-filter>
- <actionandroid:name="android.intent.action.MAIN"/>
- <categoryandroid:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
- </application>
- </manifest>
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.company.product"> <application android:icon="@drawable/app_icon" android:label="@string/app_name"> <activity android:name=".OverrideExampleNative" android:label="@string/app_name"> <meta-data android:name="android.app.lib_name" android:value="unity" /> <meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" android:value="true" /> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application></manifest>这段话的意思就是说Android的事件是运行在本机代码中的,Java视图是看不到这些事件的,然而我们可以通过Unity提供的统一转发机制将这些事件发送到Android虚拟机,为了使用这个机制,我们必须修改Android的 AndroidManifest.xml文件。博主感觉Android插件这块官方目前做得不是很好,总是给人一种云里雾里的感觉,而且通过各种语言相互调用增加了调试的难度,关于Android更多的内容,大家可以参考官方的文档:点击这里.好了,今天的内容就是这样啦,希望大家喜欢。最后再来看看程序运行的效果吧
更多相关文章
- NPM 和webpack 的基础使用
- 【阿里云镜像】使用阿里巴巴DNS镜像源——DNS配置教程
- Android之NDK开发
- 将tensorflow训练好的模型移植到android
- Android(安卓)APK文件在电脑上面运行方法
- Android札记【2】——系统的认识心中的Android[首届 Google 暑期
- Android的4种文件类型Java,class,dex,apk
- Android生成APK文件
- android打包apk流程