ANDROID嵌入式应用Unity3D视图(画廊3D模型)

转载请注明来自大型玉米的博客文章(http://blog.csdn.net/a396901990),谢谢支持!


效果展示:


 

             


开篇废话:

我如今所在的Team每周须要一个人给大家介绍一个知识点,或者新技术。

这礼拜正好轮到我了,因为我工作才一年。面对那帮老鸟讲知识点感觉有点作死。

所以我就准备选个新技术介绍一下。

因为我在大学里自学过一段时间Unity3D,所以我想介绍的技术就是它,但我如今做的是应用开发,不能做个小游戏去给大家演示。所以我想到比較简单,直观,并且有可能真正能用到的就是在Android应用中展示3D模型。比方在产品展示时直接把这个产品的3D模型展示出来而不是个图片,效果应该很棒(OpenGL应该也能够做)。

思路定下以后就发现大学时学的Unity3D的内容基本忘光了,尽管偶尔有Unity3D的文章都会点开看看,但还是得又一次学。记得当时学Unity3D的时候看过一个叫雨松MOMO的博客。那时年轻,懵懂,找不到方向的我还给雨松大神发了一封邮件去请教大学应该怎么学习和做游戏相关的问题,结果人家没回,导致我更加失落于是接着学android去了。。

。又扯远了。。。

于是我又找到他的博客,把Unity3D基础部分的相关文章都看了一遍。

可是他博客里有介绍怎样在Unity3D中调用Android,而我想做的是在Android中调用Unity3D,并且是把Unity3D嵌套在ANDROID的视图里面。最后费了九牛二虎之力才把这个Demo做出来。


准备工作:

以下是我总结的流程。目的是使本文思路更加清晰一些:

1.Android端代码能够在Eclipse中开发(AndroidStudio没有试,应该也能够)

2.Unity3D端代码要在Unity中开发

3.Android和Unity3D端,两边都须要增加一些代码从而能够使之关联交互。

4.将Android端代码编译成jar文件以插件形式放入到Unity端中

5.在Unity中将整个项目Build成apk文件。然后安装到手机或模拟器里执行

本文主要解说1,2,3。对于4,5建议大家去看雨松MOMO的Unity博客的第17篇和第18篇。


UnityPlay:

在编写Android端和Unity3d端代码前。有必要先了解一下能够使两部分交互的类UnityPlay。

个人理解UnityPlay是个Unity提供给外部交互的一个接口类。

为什么是“个人理解”?这我不得不爆粗口了,TMD官网根本就没有相关的API和文档(假设大家有谁找到一定给我来一份。就当我骂自己了)。

热心的网友已经找到了:http://docs.unity3d.com/Manual/PluginsForAndroid.html。。


在关联Android时。想拿到UnityPlay以及相关类的jar包能够从以下的地址找到:Unity安装路径EditorDataPlaybackEnginesandroidplayerin在bin目录下有一个classes.jar的jar文件,它就是我们想要的。

而在bin同文件夹下有一个src文件,点击到最后有3个类,各自是UnityPlayerActivity.java,UnityPlayerProxyActivity.java,UnityPlayerNativeActivity.java。

前两个打开个后仅仅有一行代码,说的是UnityPlayerActivity和UnityPlayerProxyActivity都继承自UnityPlayerNativeActivity。而打开UnityPlayerNativeActivity中竟然有代码,并且我预计这应该是UnityPlayerNativeActivity的源代码。

因为关于UnityPlay的资料我仅仅找到这么一个。所以我把UnityPlayerNativeActivity中的代码都贴出来,假设我注解有不正确的地方希望大家指正。

/**
 * UnityPlayerActivity,UnityPlayerProxyActivity都继承自UnityPlayerNativeActivity
 * 而UnityPlayerNativeActivity继承自NativeActivity
 * 在该类里定义了一些和ANDROID生命周期同样的回调方法,留给自己定义的Activity子类重写。

*/ public class UnityPlayerNativeActivity extends NativeActivity { //UnityPlayer的引用,而且我们不能改变这个引用变量的名字。它被native code所引用 protected UnityPlayer mUnityPlayer; 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); // 创建一个UnityPlayer对象。并赋值给全局的引用变量 mUnityPlayer = new UnityPlayer(this); //为UnityPlayer设置一些參数 if (mUnityPlayer.getSettings ().getBoolean ("hide_status_bar", true)) getWindow ().setFlags (WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); int glesMode = mUnityPlayer.getSettings().getInt("gles_mode", 1); boolean trueColor8888 = false; // UnityPlayer.init()方法须要在将view附加到layout之前调用。它将会调用native code mUnityPlayer.init(glesMode, trueColor8888); // 从UnityPlayer中获取到Unity的View视图 View playerView = mUnityPlayer.getView(); // 将Unity视图载入到根视图上 setContentView(playerView); // 使Unity视图获取焦点 playerView.requestFocus(); } protected void onDestroy () { // 当Activity结束的时候调用UnityPlayer.quit()方法,它会卸载之前调用的native code mUnityPlayer.quit(); super.onDestroy(); } // 以下几个方法都是ANDROID相关回调方法。确保在ANDROID运行对应方法时UnityPlayer也需调用对应方法 protected void onPause() { super.onPause(); mUnityPlayer.pause(); } protected void onResume() { super.onResume(); mUnityPlayer.resume(); } public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); mUnityPlayer.configurationChanged(newConfig); } public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); mUnityPlayer.windowFocusChanged(hasFocus); } public boolean dispatchKeyEvent(KeyEvent event) { if (event.getAction() == KeyEvent.ACTION_MULTIPLE) return mUnityPlayer.onKeyMultiple(event.getKeyCode(), event.getRepeatCount(), event); return super.dispatchKeyEvent(event); } }

看完这个类后就知道了为什么在自己定义的Activity中继承了UnityPlayerActivity等类以后。仅仅要重写了onCreate并调用super.onCreate()方法后不须要不论什么其它的代码就会自己主动的显示出Unity3D的视图。

由于初始化Unity视图的代码都在UnityPlayerNativeActivity父类中实现了。

ANDROID端代码:

在写ANDROID代码的时候,一定要导入Unity3D提供给我们的jar包。jar包的位置我在上面说了。引入jar包增加到buildpath中这些最主要的我就不多说了。

要想和Unity交互,我们就不能继承ANDROID提供给我们的Activity,我们须要继承刚才jar包中引入的Unity提供的Activity类,一共同拥有这么3个:

UnityPlayerActivity。UnityPlayerProxyActivity,UnityPlayerNativeActivity。详细差别不知道,由于没有文档,没有API。没有源代码(这里再次歧视一下)。

刚才我们看过UnityPlayerNativeActivity的代码(尽管非常短,但我认为这个就是源代码)。知道UnityPlayerActivity,UnityPlayerProxyActivity都是它的子类,并且终于父类为NativeActivity。所以我们继承Unity提供的最外层的子类是最好的选择。我这里选择的是UnityPlayerActivity,由于名字最简单,认为该封装的都应该封装好了。

public class MainActivity extends UnityPlayerActivity {
	
	private Button topButton;
	private Button bottomButton;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		
		// 设置test为我们的根布局
		setContentView(R.layout.test);
		
		// 通过刚才的源代码分析,知道mUnityPlayer为一个全局的引用变量,并且已经在父类中设置好了,所以直接拿来用就能够了
		View playerView = mUnityPlayer.getView();
		// 将Unity的视图加入到我们为其准备的父容器中
		LinearLayout ll = (LinearLayout) findViewById(R.id.unityViewLyaout);
		ll.addView(playerView);
		
		// 上面的button设置监听器
		topButton = (Button) findViewById(R.id.topButton);
		topButton.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {
				//发送消息给Unity端,该函数第一个參数为接受消息的类对象。第二个该类对象用接受消息的方法,第三个參数为传递的消息
				//所以以下的意思就为:调用Main Camera以下的Previous方法,传送的消息为空
				UnityPlayer.UnitySendMessage("Main Camera","Previous",""); 
			}
		});
		
		// 为以下的button设置监听器
		bottomButton = (Button) findViewById(R.id.bottomBtn);
		bottomButton.setOnClickListener(new View.OnClickListener() {
			
			@Override
			public void onClick(View v) {
				//调用Main Camera以下的Next方法,传送的消息为空
				UnityPlayer.UnitySendMessage("Main Camera","Next","");   
			}
		});
	}
}

最后看一下Android端的布局文件,布局非常easy,上下各有一个buttonbutton,两个button中间是Unity的视图。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <Button
        android:id="@+id/topButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:text="PREVIOUS" />

    <LinearLayout
        android:id="@+id/unityViewLyaout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/bottomBtn"
        android:layout_below="@+id/topButton"
        android:orientation="horizontal" >
    </LinearLayout>

    <Button
        android:id="@+id/bottomBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:text="NEXT" />

</RelativeLayout>
Android端的代码就介绍完了,非常easy。唯一的难点就是UnityPlayerActivity和UnityPlayer的使用,就这两个破玩意花了我好几天的时间,非常easy的东西不知道为什么官方不给个文档或者API(也可能我太挫没找到。。。



Unity3D端代码:

先看一下我的项目结构:


JavaScript存放的是脚本

Models存放的是我在Assert Store中下载的免费的一些模型文件

Plugins下是我的Androidproject。详细做法參考网上教程(这里推荐雨松大神的第17篇)

Prefab我是调整模型后定义的预制体

在场景中。我仅仅有一个摄像机。和一个直射光。将脚本绑定到摄像机上。然后将之前调整好的5个预设模型加入到脚本的对应对象中。


以下是脚本的代码,关于模型的旋转缩放是直接用了雨松MOMO的一篇文章中的代码。然后再加上了本例中的一些逻辑而组成的。

#pragma strict

//5个模型,从外部传入
var car : GameObject;
var helicopter : GameObject;
var suv : GameObject;
var plane : GameObject;
var tank : GameObject;

//模型数组下标
private var index : int;
//模型数组
private var models : GameObject[];
//当前模型对象
private var mCurrentGameObject : GameObject;

/******************************************/
/*切割线之下的变量用于触摸手势镜头控制旋转和缩放*/
/******************************************/

//缩放系数
var distance = 10.0;
//左右滑动移动速度
var xSpeed = 250.0;
var ySpeed = 120.0;
//缩放限制系数
var yMinLimit = -20;
var yMaxLimit = 80;
//摄像头的位置
var x = 0.0;
var y = 0.0;
//记录上一次手机触摸位置推断用户是在左放大还是缩小手势
private var oldPosition1 : Vector2;
private var oldPosition2 : Vector2;


function Start () {
	//初始化模型数组
	index = 0;
	models = new GameObject[5];
	models[0] = car;
	models[1] = helicopter;
	models[2] = suv;
	models[3] = plane;
	models[4] = tank;
	//克隆一个初始模型对象
	mCurrentGameObject = Instantiate(models[index], Vector3(0,0,0), Quaternion.Euler(-20,0,0));	
	
	//初始化镜头參数
	var angles = transform.eulerAngles;
    x = angles.y;
    y = angles.x;
}

function Update () {

	//推断触摸数量为单点触摸
    if(Input.touchCount == 1)
    {
        //触摸类型为移动触摸
        if(Input.GetTouch(0).phase==TouchPhase.Moved)
        {
            //依据触摸点计算X与Y位置
            x += Input.GetAxis("Mouse X") * xSpeed * 0.02;
            y -= Input.GetAxis("Mouse Y") * ySpeed * 0.02;
 
        }
    }
 
    //推断触摸数量为多点触摸
    if(Input.touchCount > 1 )
    {
        //前两仅仅手指触摸类型都为移动触摸
        if(Input.GetTouch(0).phase==TouchPhase.Moved||Input.GetTouch(1).phase==TouchPhase.Moved)
        {
                //计算出当前两点触摸点的位置
                    var tempPosition1 = Input.GetTouch(0).position;
                var tempPosition2 = Input.GetTouch(1).position;
                //函数返回真为放大。返回假为缩小
                if(isEnlarge(oldPosition1,oldPosition2,tempPosition1,tempPosition2))
                {
                    //放大系数超过3以后不同意继续放大
                    //这里的数据是依据我项目中的模型而调节的。大家能够自己随意改动
                       if(distance > 3)
                       {
                           distance -= 0.5;
                       }
                   }else
                {
                    //缩小洗漱返回18.5后不同意继续缩小
                    //这里的数据是依据我项目中的模型而调节的,大家能够自己随意改动
                    if(distance < 18.5)
                    {
                        distance += 0.5;
                    }
                }
            //备份上一次触摸点的位置,用于对照
            oldPosition1=tempPosition1;
            oldPosition2=tempPosition2;
        }
    }
}

//函数返回真为放大,返回假为缩小
function isEnlarge(oP1 : Vector2,oP2 : Vector2,nP1 : Vector2,nP2 : Vector2) : boolean
{
    //函数传入上一次触摸两点的位置与本次触摸两点的位置计算出用户的手势
    var leng1 =Mathf.Sqrt((oP1.x-oP2.x)*(oP1.x-oP2.x)+(oP1.y-oP2.y)*(oP1.y-oP2.y));
    var leng2 =Mathf.Sqrt((nP1.x-nP2.x)*(nP1.x-nP2.x)+(nP1.y-nP2.y)*(nP1.y-nP2.y));
    if(leng1 < leng2)
    {
         //放大手势
         return true;
    }else
    {
        //缩小手势
        return false;
    }
}
 
//Update方法一旦调用结束以后进入这里算出重置摄像机的位置
function LateUpdate () {
 
    //mCurrentGameObject为我们当前模型对象,缩放旋转的參照物
    if (mCurrentGameObject.transform) {        
 
        //重置摄像机的位置
         y = ClampAngle(y, yMinLimit, yMaxLimit);
        var rotation = Quaternion.Euler(y, x, 0);
        var position = rotation * Vector3(0.0, 0.0, -distance) + mCurrentGameObject.transform.position;
 
        transform.rotation = rotation;
        transform.position = position;
    }
}
 
static function ClampAngle (angle : float, min : float, max : float) {
    if (angle < -360)
        angle += 360;
    if (angle > 360)
        angle -= 360;
    return Mathf.Clamp (angle, min, max);
}

// 当android中按下next,显示下一个模型
function Next () {
	index = index+1;
	if (index > models.Length-1) {
		index = 0;
	}
	Debug.Log("next");
	// 摧毁当前对象
	Destroy(mCurrentGameObject);
	// 建立新的模型对象
	mCurrentGameObject = Instantiate(models[index]);
}

// 当android中按下previous,显示上一个模型
function Previous () {
	index = index-1;
	if (index < 0) {
		index = models.Length-1;
	}
	Debug.Log("previous");
	// 摧毁当前对象
	Destroy(mCurrentGameObject);
	// 建立新的模型对象
	mCurrentGameObject = Instantiate(models[index]);
}

最后就是在Unity3D中将projectBuild成APK文件,然后再手机或模拟器中执行(假设手机或模拟器连着Eclipse则能够打出log方便调试找错)。

最后附上代码Demo:

Unity端代码太大了,所以我就把Android端和Unity端代码还有而apk文件上传到百度云了。

代码点击下载


结束语:

我在刚上大学的时候就梦想着毕业以后去做游戏,可是报志愿的时候选的是java相关,后来才知道java并不适合做游戏,后来自学c++。openGL,再后来又看Unity3D但全都没有坚持下来,并且大学时候天天和同学打wow,结果毕业发现事实上游戏相关的东西根本等于一点也没学会,并且大连没有游戏开发的工作,尽管非常想去北京试试,可是当时去北京实习太麻烦。并且不一定能找到,所以就在大连直接找了一个Android开发就这么把当初的梦想放弃了。可是我还是想明年去北京试试能不能找到游戏开发的工作,这中间一年我还是会主要学习Android和Java。剩余时间看看图形学和数学知识。不会像大学的时候看到什么热就学什么,由于如今懂得渐渐多了后发现编程基本都是通用的,所以还是先把一个东西学透学明确吧。




版权声明:本文博客原创文章。博客,未经同意,不得转载。

原文地址:https://www.cnblogs.com/blfshiye/p/4726750.html