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

效果展示:


  

                 

准备工作:

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

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
< font color = "#000" > < font face = "Arial" > / * *
  * 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 trueColor 8888 = false ;
                 / / UnityPlayer.init ( ) 方法需要在将 view 附加到layout之前调用。它将会调用native code
                 mUnityPlayer.init ( glesMode , trueColor 8888 ) ;
                 
                 / / 从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 ) ;
         }
}
< / 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
< font color = "#000" > < font face = "Arial" > 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" , "" ) ;  
                         }
                 } ) ;
         }
} < / 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
< font color = "#000" > < font face = "Arial" > < ?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 > < / 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
< font color = "#000" > < font face = "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 oldPosition 1 : Vector 2 ;
private var oldPosition 2 : Vector 2 ;
 
 
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 ] , Vector 3 ( 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.0 2 ;
             y - = Input.GetAxis ( "Mouse Y" ) * ySpeed * 0.0 2 ;
  
         }
     }
  
     / / 判断触摸数量为多点触摸
     if ( Input.touchCount > 1 )
     {
         / / 前两只手指触摸类型都为移动触摸
         if ( Input.GetTouch ( 0 ) .phase = = TouchPhase.Moved||Input.GetTouch ( 1 ) .phase = = TouchPhase.Moved )
         {
                 / / 计算出当前两点触摸点的位置
                     var tempPosition 1 = Input.GetTouch ( 0 ) . position ;
                 var tempPosition 2 = Input.GetTouch ( 1 ) . position ;
                 / / 函数返回真为放大,返回假为缩小
                 if ( isEnlarge ( oldPosition 1 , oldPosition 2 , tempPosition 1 , tempPosition 2 ) )
                 {
                     / / 放大系数超过 3 以后不允许继续放大
                     / / 这里的数据是根据我项目中的模型而调节的,大家可以自己任意修改
                        if ( distance > 3 )
                        {
                            distance - = 0.5 ;
                        }
                    } else
                 {
                     / / 缩小洗漱返回 18.5 后不允许继续缩小
                     / / 这里的数据是根据我项目中的模型而调节的,大家可以自己任意修改
                     if ( distance < 18.5 )
                     {
                         distance + = 0.5 ;
                     }
                 }
             / / 备份上一次触摸点的位置,用于对比
             oldPosition 1 = tempPosition 1 ;
             oldPosition 2 = tempPosition 2 ;
         }
     }
}
 
/ / 函数返回真为放大,返回假为缩小
function isEnlarge ( oP 1 : Vector 2 , oP 2 : Vector 2 , nP 1 : Vector 2 , nP 2 : Vector 2 ) : boolean
{
     / / 函数传入上一次触摸两点的位置与本次触摸两点的位置计算出用户的手势
     var leng 1 = Mathf.Sqrt ( ( oP 1. x - oP 2. x ) * ( oP 1. x - oP 2. x ) + ( oP 1. y - oP 2. y ) * ( oP 1. y - oP 2. y ) ) ;
     var leng 2 = Mathf.Sqrt ( ( nP 1. x - nP 2. x ) * ( nP 1. x - nP 2. x ) + ( nP 1. y - nP 2. y ) * ( nP 1. y - nP 2. y ) ) ;
     if ( leng 1 < leng 2 )
     {
          / / 放大手势
          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 * Vector 3 ( 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 ] ) ;
}
< / font > < / font >

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

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


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值