效果展示:

 

            

准备工作:

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

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端和端代码前,有必要先了解一下可以使两部分交互的类UnityPlay。

个人理解UnityPlay是个Unity提供给外部交互的一个接口类。为什么是“个人理解”?这我不得不爆粗口了,TMD官网

根本就没有相关的API和文档(如果大家有谁找到一定给我来一份,就当我骂自己了)。

在关联Android时,想拿到UnityPlay以及相关类的jar包可以从下面的地址找到:Unity安装路径\Editor\Data\PlaybackEngines\androidplayer\bin在bin文件夹下有一个classes.jar的jar文件,它就是我们想要的。

而在bin同目录下有一个src文件,点击到最后有3个类,分别是UnityPlayerActivity.java,UnityPlayerProxyActivity.java,UnityPlayerNativeActivity.java。前两个打开个后只有一行代码,说的是UnityPlayerActivity和UnityPlayerProxyActivity

都继承自UnityPlayerNativeActivity。而打开UnityPlayerNativeActivity中居然有代码,而且我估计这应该是

UnityPlayerNativeActivity的源码。由于关于UnityPlay的资料我只找到这么一个,所以我把UnityPlayerNativeActivity中

的代码都贴出来,如果我注解有不对的地方希望大家指正。

[AppleScript] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<fontcolor="#000"><fontface="Arial">/**
 *UnityPlayerActivity,UnityPlayerProxyActivity都继承自UnityPlayerNativeActivity
 *而UnityPlayerNativeActivity继承自NativeActivity
 *在该类里定义了一些和ANDROID生命周期相同的回调方法,留给自定义的Activity子类重写。
 */
publicclassUnityPlayerNativeActivity 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=newUnityPlayer(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);
                booleantrueColor8888=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(booleanhasFocus)
        {
                super.onWindowFocusChanged(hasFocus);
                mUnityPlayer.windowFocusChanged(hasFocus);
        }
        publicbooleandispatchKeyEvent(KeyEventevent)
        {
                if(event.getAction()==KeyEvent.ACTION_MULTIPLE)
                        returnmUnityPlayer.onKeyMultiple(event.getKeyCode(),event.getRepeatCount(),event);
                returnsuper.dispatchKeyEvent(event);
        }
}
</font></font>

看完这个类后就知道了为什么在自定义的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,因为名字最简单,觉得该封装的都应

该封装好了。

[AppleScript] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<fontcolor="#000"><fontface="Arial">publicclassMainActivity 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(newView.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(newView.OnClickListener(){
                         
                        @Override
                        public void onClick(View v){
                                //调用Main Camera下面的Next方法,传送的消息为空
                                UnityPlayer.UnitySendMessage("Main Camera","Next","");  
                        }
                });
        }
}</font></font>

最后看一下Android端的布局文件,布局很简单,上下各有一个button按钮,两个按钮中间是Unity的视图。

[AppleScript] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<fontcolor="#000"><fontface="Arial"><?xmlversion="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></font></font>

Android端的代码就介绍完了,很简单。唯一的难点就是UnityPlayerActivity和UnityPlayer的使用,就这两个破

玩意花了我好几天的时间,很简单的东西不知道为什么官方不给个文档或者API(也可能我太挫没找到。。。)

Unity3D端代码:

先看一下我的项目结构:

JavaScript存放的是脚本

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

Plugins下是我的Android工程,具体做法参考网上教程(这里推荐雨松大神的第17篇)

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

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

加到脚本的相应对象中。

下面是脚本的代码,关于模型的旋转缩放是直接用了雨松MOMO的一篇文章中的代码,然后再加上了本例中的

一些逻辑而组成的。

[AppleScript] 纯文本查看 复制代码
?
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
<fontcolor="#000"><fontface="Arial">#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=newGameObject[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)
    {
         //放大手势
         returntrue;
    }else
    {
        //缩小手势
        returnfalse;
    }
}
  
//Update方法一旦调用结束以后进入这里算出重置摄像机的位置
function LateUpdate (){
  
    //mCurrentGameObject为我们当前模型对象,缩放旋转的参照物
    if(mCurrentGameObject.transform){       
  
        //重置摄像机的位置
         y=ClampAngle(y,yMinLimit,yMaxLimit);
        var rotation =Quaternion.Euler(y,x,0);
        varposition=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;
    returnMathf.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]);
}
</font></font>

最后就是在Unity3D中将工程Build成APK文件,然后再手机或模拟器中运行(如果手机或模拟器连着Eclipse则

可以打出log方便调试找错)。

Unity3D打包apk的主界面和android组件共同显示相关推荐

  1. android 仿微信demo————微信主界面实现

    android 仿微信demo----微信启动界面实现 android 仿微信demo----注册功能实现(移动端) android 仿微信demo----注册功能实现(服务端) android 仿微 ...

  2. Android Studio 使用教程(5)---打包apk

    Android Studio 使用教程(5)---打包apk 本章节讲述使用Android studio 打包apk的过程 1.Build -> Generate Signed APK...,打 ...

  3. 关于Mac Android 打包 APK

    目录 关于Mac Android 打包 APK 方式一:使用Android Studio生成 1.点击Build->Generate Signed apk,首次点击可能会提示输入操作系统密码 2 ...

  4. 【错误记录】Android Studio 打包 apk 文件报错 ( The destination folder does not exist or is not writeable )

    文章目录 一.报错信息 二.解决方案 一.报错信息 在 Android Studio 工程中 , 打包 APK 文件时 , 报错 ; Android Studio 打包 apk 文件报错 : The ...

  5. cocos2dx3.2打包apk

    原文地址:https://my.oschina.net/Jacedy/blog/350226 一.下载Android环境 搭建Android环境需要用到Android SDK.NDK.Ant和JDK: ...

  6. Android组件化方案

    版权声明:本文为博主原创文章,欢迎大家转载! 转载请标明出处: http://blog.csdn.net/guiying712/article/details/55213884 ,本文出自:[张华洋的 ...

  7. Android组件化方案最佳实践

    舞动着键盘和鼠标,我誓言要把这个世界写的明明白白 本文出自门心叼龙的博客,属于原创类容,转载请注明出处.https://blog.csdn.net/geduo_83/article/details/8 ...

  8. Android 组件化方案,从入门到精通

    Android组件化项目地址:Android组件化项目AndroidModulePattern Android组件化之终极方案地址:http://blog.csdn.net/guiying712/ar ...

  9. Unity3D应用--打造Android车载3D车模桌面(车载主界面开发)

    前言 Android中应用加载Unity3D来显示3D效果参考上一篇博文:Unity3D导出Android工程(Android中应用Unity3D) 透明背景实现参考另一篇博文: 在Android中U ...

  10. Android直接用手机打包apk!

    你没有看错,用手机浏览器访问Jenkins,就可以打包apk,并生成下载二维码,发送邮件通知测试人员下载,从此解放双手,告别打包测试.先上本人手机邮箱收到的打包成功通知效果图: 废话少说,开干! 本人 ...

最新文章

  1. 最新版,别的可以不用看了,zabbix 监控 esxi
  2. RealNVP与Glow:流模型的传承与升华
  3. 最明的int和Integer的区别
  4. WebRTC的现状和未来:专访W3C WebRTC Chair Bernard Aboba(上)
  5. html清除溢出,深入理解CSS overflow:hidden——溢出,坍塌,清除浮动
  6. 复习深入笔记01:对象/可变与不可变类型/字符编码/闭包
  7. python实现编辑距离,最长公共子序列,最长公共子串
  8. 单元格中指定内容标红_你一直都不知道,键盘上“F4”在office中的强大功能
  9. 网页版bpc电波对时_BPC电波对时app下载|BPC电波对时安卓版下载 v1.04 - 跑跑车安卓网...
  10. 群晖系统硬盘损毁的修复
  11. CSI.SAP2000.v19.1.1 Win32_64 2CD通用有限元软件
  12. haversine根据经纬度算距离
  13. vmware之centos7 删除分区
  14. USBKey使用openssl链接
  15. python syntactic suger
  16. 【美团面经】后台研发一面
  17. Android中Fragment的分屏显示处理横竖屏显示
  18. 使用muscle进行多序列比对
  19. 关于NdFeB样品的测量阶段总结
  20. Javascript+webdriverio App自动化demo

热门文章

  1. echarts地图迁徙图
  2. 【rmzt】阳光美女win7主题
  3. 现代控制理论课程实验二:利用状态观测器实现状态反馈的系统设计
  4. 条码打印软件如何制作二维码
  5. SEO优化_文章原创怎么写
  6. [破解]Shark恒破解基础教程笔记
  7. CUMT矿大----电路与数字系统实验一 一位全减器
  8. 最小生成树——Prim算法(详细图解)
  9. 2:0战胜Dota2世界冠军OG,OpenAI发起全网挑战,不服来战!
  10. CEFSharp崩溃分析及解决办法