《Android 3D 游戏案例开发大全》——6.6节游戏界面相关类

简介:

本节书摘来自异步社区《Android 3D 游戏案例开发大全》一书中的第6章,第6.6节游戏界面相关类,作者 吴亚峰 , 于复兴 , 杜化美,更多章节内容可以访问云栖社区“异步社区”公众号查看

6.6 游戏界面相关类
Android 3D 游戏案例开发大全
前一小节为读者介绍了辅助界面相关类,本小节将对游戏界面相关类进行介绍,首先介绍游戏界面的各个组成部分,然后介绍游戏整体界面的开发,逐步完成对游戏界面的开发,下面就对这些类的开发进行详细介绍。

6.6.1 顶点数据管理者VertexDataManager
本小节为读者介绍的是整个游戏过程中所有物体顶点数据的管理者VertexDataManager,具体开发步骤如下。

(1)首先为读者介绍的是本类主要框架的开发,其代码如下。

1 package com.bn.txz.manager;        //声明包
2 ……//此处省略了本类中导入类的代码,读者可以自行查阅随书光盘中的源代码
3 public class VertexDataManager {
4  public static boolean isLoaded=false;
5  public static int count=33;       //物体数量
6  public static FloatBuffer[] vertexPositionArray=new FloatBuffer[count];                 //顶点坐标缓冲序列
7  public static FloatBuffer[] vertexTextrueArray=new FloatBuffer[count];                 //顶点纹理坐标缓冲序列
8  public static IntBuffer[] vertexColorArray=new IntBuffer[count];                   //顶点着色数据的缓冲序列(星空)
9  public static FloatBuffer[] vertexNormalArray=new FloatBuffer[count];                  //顶点法向量坐标缓冲序列
10  public static int[] vCount=new int[count];    //顶点数量数组
11  //加载物体顶点位置、纹理坐标数据进内存缓冲的方法
12  public static void initVertexData( Resources r) {
13   if(isLoaded)return;    //如果已经加载过,则直接返回
14   initProgressBackVertexData();  //调用初始化加载背景矩形顶点数据方法
15   ……//该处省略了相似的调用初始化顶点数据的代码,读者可自行查阅随书光盘中源代码
16   LoadUtil.loadFromFileVertexNormalTexture("head_l.obj", r, 16);              //加载头部模型
17   ……//该处省略了相似的加载其他机器人部件的代码,读者可自行查阅随书光盘中的源代码
18   isLoaded=true;
19  }
20  //加载物体顶点位置、纹理坐标数据进内存缓冲的方法
21  public static void initVertexData( Resources r,int index) {
22   isLoaded=false;
23   switch(index) {
24   case 1:        //加载机器人头部模型
25    LoadUtil.loadFromFileVertexNormalTexture("head.obj", r, 1); break;
26   case 2:        //加载机器人身体模型
27    LoadUtil.loadFromFileVertexNormalTexture("body.obj", r, 2); break;
28   case 3: break;
29   case 4:        //加载机器人左臂模型
30    LoadUtil.loadFromFileVertexNormalTexture("left_top.obj", r, 3);
31    LoadUtil.loadFromFileVertexNormalTexture("left_bottom.obj", r, 4);      break;
32   case 5:        //加载机器人右臂模型
33    LoadUtil.loadFromFileVertexNormalTexture("right_top.obj", r, 5);
34    LoadUtil.loadFromFileVertexNormalTexture("right_bottom.obj", r, 6); break;
35   case 6: 
36    initRoomVertexData();   //调用初始化房间顶点数据方法
37    initSky();     //调用初始化天空顶点数据方法
38    initWaterVertexData();  //调用初始化水面顶点数据方法
39    initWinVertexData();break;  //调用初始化输赢界面对话框顶点数据方法
40   case 7:
41   case 8: break;
42   }
43   isLoaded=true;
44  }
45  ……//该处省略了初始化各个物体顶点数据的方法,下面将会介绍。
46 }

第4-10行是初始化该类的成员变量。第12-19行是进入菜单界面时加载物体顶点位置、纹理坐标数据进内存缓冲的方法。第21-44行是进入游戏界面时加载物体顶点位置、纹理坐标数据进内存缓冲的方法。
(2)完成本类主体框架的开发后,接下来为读者介绍的是初始化房间顶点数据的方法,其代码如下。

1 public static void initRoomVertexData(){     //初始化房间顶点数据
2  float UNIT_SIZE=4f; 
3  float vertice[]=new float[]{         //顶点坐标数据
4          2*UNIT_SIZE,2*UNIT_SIZE, -3.464f*UNIT_SIZE,   //侧面
5          1*UNIT_SIZE,-0*UNIT_SIZE,-1.732f*UNIT_SIZE,
6          3*UNIT_SIZE,2*UNIT_SIZE,-1.732f*UNIT_SIZE,  
7           ……//该处省略了相似的给出顶点坐标点的代码,需要的读者
8                      //请自行查阅随书光盘中的源代码
9           1*UNIT_SIZE,-0*UNIT_SIZE,-1.732f*UNIT_SIZE, 
10           2*UNIT_SIZE,2*UNIT_SIZE,-3.464f*UNIT_SIZE,
11           0*UNIT_SIZE,2*UNIT_SIZE,-3.464f*UNIT_SIZE
12      };
13  vCount[0]=54;            //房间的顶点数
14  ByteBuffer vbb=ByteBuffer.allocateDirect(vertice.length*4); //创建数据缓冲区
15  vbb.order(ByteOrder.nativeOrder());    //设置字节顺序
16  vertexPositionArray[0]=vbb.asFloatBuffer();   //转成float型缓冲
17  vertexPositionArray[0].put(vertice);    //向缓冲区放入顶点坐标数据
18  vertexPositionArray[0].position(0);    //设置缓冲区起始位置
19  float Texturecood[]=new float[]{     //房间的顶点纹理坐标
20       0,0.5f,
21       0.25f,1,
22       0.5f,0.5f,
23       ……//该处省略了相似的给出顶点纹理坐标点的代码,读者可自行查阅随书光盘中的源代码
24       0.5f,0.25f,
25       0f,0.25f,
26       0.25f,0.5f
27  };
28  ByteBuffer cbb=ByteBuffer.allocateDirect(Texturecood.length*4);//创建数据缓冲区
29  cbb.order(ByteOrder.nativeOrder());       //设置字节顺序
30  vertexTextrueArray[0]=cbb.asFloatBuffer();   //转成float型缓冲
31  vertexTextrueArray[0].put(Texturecood);    //向缓冲区放入顶点坐标数据
32  vertexTextrueArray[0].position(0);     //设置缓冲区起始位置
33 }

第3-18行是房间的顶点坐标数据的初始化。第13行是房间的顶点数。第14-33行是房间的顶点纹理坐标的初始化。本类中一些方法与房间的顶点数据的初始化方法相似,由于篇幅所限这里不再一一介绍,需要的读者请自行查阅随书光盘中的源代码。
(3)接下来介绍的是大星空顶点数据的初始化方法,星空是由一些随机生成的点组成的,其代码如下。

1 public static void initBigCelestialVertexData(){
2  int count=60;
3  //顶点坐标数据的初始化
4     float vertice[]=new float[count*3];
5     for(int i=0;i<count;i++) {      //随机产生每个星星的_x_、_y_、_z_坐标
6      double angleTempJD=Math.PI*2*Math.random();
7      double angleTempWD=Math.PI/2*Math.random();
8      vertice[i*3]=(float)(Constant.UNIT_SIZE*  //随机产生的坐标存入顶点数组
9        Math.cos(angleTempWD)*Math.sin(angleTempJD));
10      vertice[i*3+1]=(float)(Constant.UNIT_SIZE*
11        Math.sin(angleTempWD));
12      vertice[i*3+2]=(float)(Constant.UNIT_SIZE*
13        Math.cos(angleTempWD)*Math.cos(angleTempJD));
14     } 
15     vCount[24]=3*count;        //星星的顶点数
16     ByteBuffer vbb=ByteBuffer.allocateDirect(vertice.length*4);//创建数据缓冲区
17  vbb.order(ByteOrder.nativeOrder());    //设置字节顺序
18  vertexPositionArray[24]=vbb.asFloatBuffer();  //转成float型缓冲
19  vertexPositionArray[24].put(vertice);    //向缓冲区放入顶点坐标数据
20  vertexPositionArray[24].position(0);    //设置缓冲区起始位置
21  //顶点着色数据的初始化
22     final int one = 65535;
23     int[] colors=new int[count*4];   //顶点颜色值数组,每个顶点4个色彩值RGBA
24     for(int i=0;i<count;i++) {
25      colors[i*4]=one;
26      colors[i*4+1]=one;
27      colors[i*4+2]=one;
28      colors[i*4+3]=0;
29     }
30     ByteBuffer cbb=ByteBuffer.allocateDirect(colors.length*4);//创建数据缓冲区
31  cbb.order(ByteOrder.nativeOrder());    //设置字节顺序
32  vertexColorArray[24]=cbb.asIntBuffer();    //转成int型缓冲
33  vertexColorArray[24].put(colors);     //向缓冲区放入顶点着色数据
34  vertexColorArray[24].position(0);     //设置缓冲区起始位置
35 }

第4-14行是星星顶点坐标数据的初始化,星星的顶点坐标是随机生成的。第16-34行是顶点的着色数据的初始化。小星空顶点数据的初始化和大星空顶点数据的初始化方法类似,这里不再赘述,有需要的读者请自行查阅随书光盘中的源代码。
(4)接下来为读者介绍的是游戏界面中软体箱子顶点数据的初始化方法,其代码如下。

1 public static void initSoftBoxVertexData(double Angle){   //初始化软体箱子顶点
2     final float Y_MAX=1f;         //箱子的高
3     final float Y_MIN=0f;         //箱子的底
4     final int FD=4;          //箱子高分的段数
5     final float hw=0.5f;         //箱子边长的一半
6     final float s=1;          //纹理坐标总长
7  //顶点坐标数据的初始化
8     vCount[26]=FD*4*6+2*6;  //6是每个面6个顶点   4是周围的4个面,2是上下两个面
9     float vertices[]=new float[vCount[26]*3];    //顶点坐标
10     float texCoor[]=new float[vCount[26]*2];    //顶点纹理坐标
11     float yStart=Y_MIN;
12     float ySpan=(Y_MAX-Y_MIN)/FD;      //这是每一份y的差值
13     float allySpan=Y_MAX-Y_MIN;       //箱子的总高度
14     int vCount=0;          //顶点坐标的索引
15     int tCount=0;          
16     float ySpant=s/FD;         
17     for(int i=0;i<FD;i++) {
18      double currAngle=i*ySpan/allySpan*Angle; 
19      float x1=(float)((-hw)*Math.cos(currAngle) -hw*Math.sin(currAngle));
20      float y1=yStart+i*ySpan;
21      float z1=(float)((-hw)*Math.sin(currAngle) +hw*Math.cos(currAngle));
22      ……//该处省略了坐标x2-x4的部分代码,需要的读者请自行查阅随书光盘中的源代码
23  float z4=(float)((-hw)*Math.sin(currAngle) +(-hw)*Math.cos(currAngle));
24  currAngle=(i+1)*ySpan/allySpan*Angle;
25   float x5=(float)((-hw)*Math.cos(currAngle) -hw*Math.sin(currAngle));
26  ……//该处省略了坐标x5-x7的部分代码,需要的读者请自行查阅随书光盘中的源代码
27      float x8=(float)((-hw)*Math.cos(currAngle) -(-hw)*Math.sin(currAngle));
28      float y8=yStart+(i+1)*ySpan;
29      float z8=(float)((-hw)*Math.sin(currAngle) +(-hw)*Math.cos(currAngle));
30      if(i==0){          //如果是第一层,要加上底面
31       vertices[vCount++]=x3;vertices[vCount++]=y3;vertices[vCount++]=z3;
32          ……//该处省略了相似的代码,需要的读者请自行查阅随书光盘中的源代码
33          vertices[vCount++]=x1;vertices[vCount++]=y1;vertices[vCount++]=z1;
34          texCoor[tCount++]=1;texCoor[tCount++]=1;
35          ……//该处省略了相似的代码,需要的读者请自行查阅随书光盘中的源代码
36          texCoor[tCount++]=0;texCoor[tCount++]=0;
37      }
38      vertices[vCount++]=x2;vertices[vCount++]=y2;vertices[vCount++]=z2;
39      ……//该处省略了相似的代码,需要的读者请自行查阅随书光盘中的源代码
40      vertices[vCount++]=x8;vertices[vCount++]=y8;vertices[vCount++]=z8;
41      texCoor[tCount++]=1;texCoor[tCount++]=1-i*ySpant;
42      ……//该处省略了相似的代码,需要的读者请自行查阅随书光盘中的源代码
43      texCoor[tCount++]=0;texCoor[tCount++]=1-(i+1)*ySpant;
44      if(i==(FD-1)) {//如果是最高层,要加上顶 
45       vertices[vCount++]=x6;vertices[vCount++]=y6;vertices[vCount++]=z6;
46          ……//该处省略了相似的代码,需要的读者请自行查阅随书光盘中的源代码
47          vertices[vCount++]=x8;vertices[vCount++]=y8;vertices[vCount++]=z8;
48       texCoor[tCount++]=1;texCoor[tCount++]=1;
49          ……//该处省略了相似的代码,需要的读者请自行查阅随书光盘中的源代码
50          texCoor[tCount++]=0;texCoor[tCount++]=0;
51      }
52     } 
53     ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length*4);//创建顶点坐标数据缓冲
54     vbb.order(ByteOrder.nativeOrder());    //设置字节顺序
55     vertexPositionArray[26] = vbb.asFloatBuffer(); //转换为float型缓冲
56     vertexPositionArray[26].put(vertices);   //向缓冲区中放入顶点坐标数据
57     vertexPositionArray[26].position(0);    //设置缓冲区起始位置 
58     ByteBuffer cbb = ByteBuffer.allocateDirect(texCoor.length*4);                   //创建顶点纹理坐标数据缓冲
59     cbb.order(ByteOrder.nativeOrder());    //设置字节顺序
60     vertexTextrueArray[26] = cbb.asFloatBuffer(); //转换为float型缓冲
61     vertexTextrueArray[26].put(texCoor);    //向缓冲区中放入顶点着色数据
62     vertexTextrueArray[26].position(0);    //设置缓冲区起始位置
63 }

第2-16行是各种变量的声明与初始化。第17-52行是顶点坐标数组与顶点纹理坐标数组的初始化,其原理为:箱子的高分成FD层,每层有8个顶点,循环到的层的顶点坐标由此8个顶点组成,其中第18-29行是每层的8个顶点坐标的计算。
第18和24行是本层上下两排顶点的旋转角度,根据当前层和传入的箱子的旋转的总角度计算,然后根据当前排的旋转角度计算顶点坐标,计算公式为:x'=xcos (currAngle)-zsin (currAngle)和text{z }!!'!!text{ }=xsin (currAngle)+zcos (text{currAngle}),其中x和z为最底排顶点坐标中的x轴坐标和z轴坐标。
第53-62行是将顶点坐标数组和顶点纹理坐标数组中的数据分别存入相应的缓冲。

6.6.2 纹理图数据管理者PicDataManager
上一小节为读者介绍了游戏中物体顶点数据的管理者,本小节为读者介绍的是游戏中纹理图数据的管理者PicDataManager,其代码如下。

1 package com.bn.txz.manager;     //声明包
2 ……//此处省略了本类中导入类的代码,读者可以自行查阅随书光盘中的源代码
3 public class PicDataManager {
4  public static boolean isLoaded=false;
5  public static boolean idLoadedOther=false;
6  public static byte[][] picDataArray=null; //声明图片数组
7  public static String[] texNameArray={
8    "room.jpg",      //房子图片        0
9    "wall.jpg",      //组成桥的纹理             1
10    ……//该处省略了其他图片的代码,需要的读者请自行查阅随书光盘中的源代码
11    "prepage.png",    //上一页的按钮    63
12    "nextpage.png"    //下一页的按钮    64
13  };
14  //加载图片数据进内存的方法,加载界面用到的纹理
15  public static void loadPicData(Context context) {
16   if(isLoaded)return;
17   picDataArray=new byte[texNameArray.length][];  //创建图片数组
18   for(int i=0;i<texNameArray.length;i++){  //循环从assets中加载部分图片
19    if(i==11||i>=12&&i<=15||i>=21&&i<=56) {
20     picDataArray[i]=loadFromAssets(context,texNameArray[i]);
21    }}
22   picDataArray[3]=loadFromAssets(context,texNameArray[3]);
23   ……//该处省略了部分从assets中加载图片的代码,需要的读者
24     //请自行查阅随书光盘中的源代码
25   picDataArray[64]=loadFromAssets(context,texNameArray[64]);
26   isLoaded=true;
27  } 
28  public static void loadPicData(Context context,int index) {                  //加载其他界面用到的纹理
29   idLoadedOther=false;
30   switch(index) {
31   case 1:
32    picDataArray[0]=loadFromAssets(context,texNameArray[0]);
33      ……//该处省略了部分从assets中加载图片的代码读者可自行查阅随书光盘中的源代码
34    picDataArray[58]=loadFromAssets(context,texNameArray[58]);
35    break;
36   case 2:  
37    picDataArray[17]=loadFromAssets(context,texNameArray[17]);
38    picDataArray[18]=loadFromAssets(context,texNameArray[18]);
39    picDataArray[19]=loadFromAssets(context,texNameArray[19]);
40    picDataArray[20]=loadFromAssets(context,texNameArray[20]);
41   case 3: case 4: case 5: case 6: case 7: case 8: break;
42   }
43   idLoadedOther=true;
44  }
45  //从Assets中加载一幅纹理的方法
46  public static byte[] loadFromAssets(Context context,String picName) {
47   byte[] result=null;
48   try{
49           InputStream in=context.getResources().getAssets().open(picName);
50       int ch=0;
51       ByteArrayOutputStream baos = new ByteArrayOutputStream();
52       while((ch=in.read())!=-1)  {
53          baos.write(ch);
54       }      
55       result=baos.toByteArray();
56       baos.close();
57       in.close(); 
58        }catch(Exception e) {
59         e.printStackTrace();
60        } 
61   return result;
62  }}

第4-6行是声明该类中成员变量。第7-13行是声明并初始化数组texNameArray,该数组中存放的是本游戏中用到的所有纹理图的名字。
第28-44行是两个方法loadPicData,两个方法实现的功能是将加载界面的图片数据和其他界面的图片数据加载进内存。
第46-62行是方法loadFromAssets,该方法实现的功能是从Assets中加载一幅纹理图。

6.6.3 游戏界面TXZGameSurfaceView
前面小节为读者介绍了游戏界面中用到的顶点数据与纹理数据,接下来为读者介绍的是游戏界面的整体类TXZGameSurfaceView,其是整个游戏的重要组成部分,其具体的开发步骤如下。

(1)介绍本类具体开发代码之前,首先为读者介绍的是本类的主要框架,其代码如下。

1 package com.bn.txz.game;       //声明包
2 ……//此处省略了本类中导入类的代码,读者可以自行查阅随书光盘中的源代码
3 public class TXZGameSurfaceView extends GLSurfaceView{
4  public SceneRenderer mRenderer; 
5  GameData gdMain=new GameData();    //主数据
6  GameData gdDraw=new GameData();    //绘制数据
7  GameData gdTemp=new GameData();    //临时数据
8  GuanQiaData gqdMain=new GuanQiaData();   //关卡类的主数据
9  GuanQiaData gqdDraw=new GuanQiaData();   //关卡类的绘制数据
10  GuanQiaData gqdTemp=new GuanQiaData();  //关卡类的临时数据
11  float mPreviousX,mPreviousY;
12  float startX,startY;
13  boolean isMove=false;      //触控时是否移动的标志
14  final float MOVE_THOLD=30;     //触控时判断是否是move动作的阈值
15  float x,y;        //触控点的x与y坐标
16  public Object aqLock=new Object();   //动作队列锁
17  public Queue<Action> aq=new LinkedList<Action>(); //动作队列
18  TXZDoActionThread dat;       //执行动作线程引用
19  public Object aqygLock=new Object();    //摇杆的动作队列锁
20  public Queue<Action> aqyg=new LinkedList<Action>(); //摇杆动作队列
21  YGDoActionThread ygdat;       //执行动作线程引用
22  VertexTexture3DObjectForDraw room;     //房间
23  VertexTexture3DObjectForDraw sky;     //天空
24  VertexTextureNormal3DObjectForDraw wall;   //桥
25  VertexTexture3DObjectForDraw water;    //水
26  VertexTextureNormal3DObjectForDraw[] lovntArray=
27   new VertexTextureNormal3DObjectForDraw[6];  //机器人各部分
28  Robot robot;          //机器人
29  VertexTexture3DObjectForDraw left;     //转换视角的虚拟按钮
30  VertexTexture3DObjectForDraw yaogan1;    //外层摇杆
31  ……//此处省略了类似的3D物体对象引用声明的代码,读者可自行查阅随书光盘中源代码
32  int num=16;
33  VertexTexture3DObjectForDraw texRect[]=
34   new VertexTexture3DObjectForDraw[num];   //纹理矩形
35  boolean waterflag=true;
36  private int load_step=0;       //进度条步数
37  public static float currDegree=0;
38  public static float currDegreeView=0;
39  public static float currX;       //机器人所在的位置
40  public static float currY;
41  public static float currZ;
42  public boolean isFirst=false;    //是否是第一次转换视角的标志
43  boolean viewFlag=false;     //摄像机视角标志位
44  boolean isLoadedOk=false;     //是否加载完成标志位
45  boolean inLoadView=true;     //是否在加载界面标志位
46  public boolean isInAnimation=false;  //是否在播放动画标志位
47  private boolean isWinOrLose=false;   //是否在输赢标志位
48  public boolean isDrawWinOrLose=false;  //是否绘制输赢界面的标志
49  public boolean temp=true;
50  static float offsetx=0;     //摇杆移动的_x_轴的偏移量
51  static float offsety=0;     //摇杆移动的_y_轴的偏移量
52  boolean isYaogan=false;     //第一次按下的点是否在摇杆内
53  static float vAngle=100;     //摇杆的偏转角度
54  boolean isGo=false;      //是否添加前进动作到队列的标志
55  boolean isGoFlag;       //更新isGo的线程是否工作的标志位
56  float skyAngle=0;       //天空旋转的角度
57  public static boolean isSkyAngle=false;  //天空旋转线程是否循环的标志位
58  TXZActivity activity;
59  public TXZGameSurfaceView(TXZActivity activity) {
60   super(activity);
61   this.activity=activity;
62   mRenderer = new SceneRenderer();
63   setRenderer(mRenderer);    //设置渲染器
64   //设置渲染模式为主动渲染
65   setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
66  }
67  ……//该处省略了本类中触控方法,将在下面进行介绍
68  ……//该处省略了本类中内部类SceneRenderer,将在下面介绍
69 }

第4-58行是成员变量的声明与创建。第59-66行是该类的构造器,其对相关成员变量进行了赋值,并创建渲染器、设置渲染器,同时设置渲染模式为主动模式。
(2)介绍完本类的主要框架后,接下来为读者介绍的是本类中的触控方法,首先介绍触控方法的框架,其代码如下。

1 @Override
2 public boolean onTouchEvent(MotionEvent event) {
3  if(inLoadView){
4   return false;
5  }
6  //外层摇杆的触控边缘位置
7  float YAOGAN_WAI_LEFT=(763.6f+Constant.screenScaleResult.lucX)*
8     Constant.screenScaleResult.ratio;     //边缘左侧
9  float YAOGAN_WAI_RIGHT=(938.2f+Constant.screenScaleResult.lucX)*
10     Constant.screenScaleResult.ratio;    //边缘右侧
11  float YAOGAN_WAI_TOP=(518.4f+Constant.screenScaleResult.lucY)*
12     Constant.screenScaleResult.ratio;    //边缘下侧
13  float YAOGAN_WAI_BOTTOM=(334.8f+Constant.screenScaleResult.lucY)*
14     Constant.screenScaleResult.ratio;    //边缘上侧
15  //摇杆的中心位置和摇杆的半径
16  Constant.YAOGAN_CENTER_X=YAOGAN_WAI_LEFT+
17      (YAOGAN_WAI_RIGHT-YAOGAN_WAI_LEFT)/2;
18  Constant.YAOGAN_CENTER_Y=YAOGAN_WAI_BOTTOM+
19      (YAOGAN_WAI_TOP-YAOGAN_WAI_BOTTOM)/2;
20  Constant.YAOGAN_R=91.8f*(screenWidth-Constant.screenScaleResult.lucX*
21    Constant.screenScaleResult.ratio*2)/Constant.screenWidthStandard;
22  x=event.getX();           //获得触控位置
23  y=event.getY();
24  switch(event.getAction()){
25   case MotionEvent.ACTION_DOWN:
26    ……//该处省略了按下时的代码,将在下面进行介绍
27   break;
28   case MotionEvent.ACTION_MOVE:
29    ……//该处省略了移动时的代码,将在下面进行介绍
30   break;
31   case MotionEvent.ACTION_UP:
32    ……//该处省略了抬起时的代码,将在下面进行介绍
33   break;
34  }
35  mPreviousX=x;           //更新上一次的位置
36  mPreviousY=y;
37  return true;
38 }

第7-14行是外层摇杆的触控边缘位置。第16-21行是根据外层摇杆的触控边缘位置计算出的摇杆中心点的位置和摇杆的半径。
第22-23行是获得触控位置。第24-34行表示的是判断是何种动作并作出相应操作。第35-36行是更新上一次的触控位置。
(3)介绍完触控方法的框架之后,接下来介绍的是上一步中省略的按下操作的代码,其代码如下。

1 case MotionEvent.ACTION_DOWN:
2  if(isInAnimation){
3   return false;
4  }
5    startX=x;           //起始位置为触控位置
6    startY=y;
7    isMove=false;          //是否移动置为false
8    currDegreeView=currDegree;
9    isYaogan=false;         //触控点是否在摇杆内
10    if(Math.sqrt((x-Constant.YAOGAN_CENTER_X)*(x-Constant.YAOGAN_CENTER_X)+
11      (y-Constant.YAOGAN_CENTER_Y)*(y-Constant.YAOGAN_CENTER_Y))
12        <Constant.YAOGAN_R){    //触控点在摇杆内
13     if(isWinOrLose){       //如果在输赢界面,直接返回false
14      return false;
15     }
16     isYaogan=true;
17    }
18    if(x>=(Constant.Game_View_l+Constant.screenScaleResult.lucX)*
19        Constant.screenScaleResult.ratio&&
20      x<=(Constant.Game_View_r+Constant.screenScaleResult.lucX)*
21       Constant.screenScaleResult.ratio&&
22      y>=(Constant.Game_View_u+Constant.screenScaleResult.lucY)*
23        Constant.screenScaleResult.ratio&&
24      y<=(Constant.Game_View_d+Constant.screenScaleResult.lucY)*
25        Constant.screenScaleResult.ratio) {//按下转换视角虚拟按钮 
26     if(isWinOrLose){       //如果在输赢界面,直接返回false
27      return false;
28     }
29     Action acTemp=new Action(     //转换视角的动作
30    ActionType.CONVERT     //动作类型
31     );
32   synchronized(aqLock){      //锁上动作队列
33    aq.offer(acTemp);      //将动作队列的队尾添加动作
34   }
35   isFirst=!isFirst;       //是否是第一次转换视角置反
36    }else if(x>=(Constant.Game_Win_First_l+Constant.screenScaleResult.lucX)*
37        Constant.screenScaleResult.ratio&&
38      x<=(Constant.Game_Win_First_r+Constant.screenScaleResult.lucX)*
39        Constant.screenScaleResult.ratio&&
40      y>=(Constant.Game_Win_First_u+Constant.screenScaleResult.lucY)*
41        Constant.screenScaleResult.ratio&&
42      y<=(Constant.Game_Win_First_d+Constant.screenScaleResult.lucY)*
43        Constant.screenScaleResult.ratio
44      &&isWinOrLose){      //在输赢界面按下第一个按钮
45     if(!isWinOrLose){       //如果在输赢界面,直接返回false
46      return false;
47     }
48     //返回到菜单界面
49     activity.handler.sendEmptyMessage(Constant.COMMAND_GOTO_MENU_VIEW);
50     isWinOrLose=false;
51     isDrawWinOrLose=false;
52    }else if(x>=(Constant.Game_Win_Two_l+Constant.screenScaleResult.lucX)*
53        Constant.screenScaleResult.ratio&&
54      x<=(Constant.Game_Win_Two_r+Constant.screenScaleResult.lucX)*
55        Constant.screenScaleResult.ratio&&
56      y>=(Constant.Game_Win_Two_u+Constant.screenScaleResult.lucY)*
57        Constant.screenScaleResult.ratio&&
58      y<=(Constant.Game_Win_Two_d+Constant.screenScaleResult.lucY)*
59        Constant.screenScaleResult.ratio)  {//在输赢界面按下第二个按钮
60     if(!isWinOrLose){        //如果在输赢界面,直接返回false
61      return false; }
62     if(gdMain.winFlag){       //赢界面按下下一关按钮 
63      if(GameData.level==9){ //如果当前为第9关,则进入第1关 
64       GameData.level=0; }
65      gdMain.loseFlag=false;     //输的标志位置为false
66      gdMain.winFlag=false;     //赢的标志位置为false
67      isWinOrLose=false;      //是否有输赢的标志位置为false
68      GameData.level=GameData.level+1;   //进入下一关
69      isDrawWinOrLose=false;
70      activity.handler.sendEmptyMessage(Constant.COMMAND_GOTO_GAME_VIEW);
71     }else if(gdMain.loseFlag){     //输界面按下重玩按钮
72      gdMain.loseFlag=false;      //输的标志位置为false
73      gdMain.winFlag=false;      //赢的标志位置为false
74      isWinOrLose=false;       //是否有输赢的标志位置为false
75      isDrawWinOrLose=false;
76      activity.handler.sendEmptyMessage(Constant.COMMAND_GOTO_GAME_VIEW);
77     } }
78 break;

第10-17行是判断触控点是否在摇杆内,如果在摇杆内则将触控点在摇杆内的标志位置为true。
第18-35行是判断触控点是否在转换视角的按钮范围内,是则做出相应的操作。
第36-51行是触控点在输赢界面的返回按钮上,则返回到菜单界面。
第52-77行是触控点在输赢界面的下一关或重玩按钮上,则进入下一关或重新进入本关。
(4)前面介绍了按下动作的处理代码,接下来介绍的是移动动作的处理代码的开发,其代码如下。

1 case MotionEvent.ACTION_MOVE:
2  float dxStart=Math.abs(x-startX);      //_x_方向的偏移量
3  float dyStart=Math.abs(y-startY);      //_y_方向的偏移量
4  //如果x与y移动的范围大于MOVE_THOLD,则将是否移动的标志位置为true
5  if(dxStart>MOVE_THOLD||dyStart>MOVE_THOLD){
6   isMove=true;
7  }
8  //移动标志位为true,并且初始按下的点不在摇杆内
9  if(!viewFlag&&isMove&&!isYaogan){ 
10   float dx=x-mPreviousX;      //_x_方向移动的长度
11   float dy=y-mPreviousY;      //_y_方向移动的长度
12   Action acTemp=new Action(      //改变摄像机的动作
13    ActionType.CHANGE_CAMERA,     //动作类型
14    new float[]{dx,dy}      //动作数据
15   );
16   synchronized(aqLock){       //锁上动作队列
17    aq.offer(acTemp);       //将动作队列的队尾添加动作
18   }
19  }
20  if(isMove&&isYaogan){    //移动标志位为true,并且初始按下的点在摇杆内
21   Action acTemp=new Action(      //改变摇杆的动作
22    ActionType.YAOGAN_MOVE,     //动作类型
23    new float[]{x,y}       //动作数据
24   );
25   synchronized(aqygLock){      //锁上动作队列
26    aqyg.offer(acTemp);      //将动作队列的队尾添加动作
27   }
28   if(vAngle>=-45&&vAngle<45&&isGo){    //前进
29    Action acTempl=new Action(     //机器人前进的动作
30     ActionType.ROBOT_UP     //动作类型
31       );
32    synchronized(aqLock){      //锁上动作队列
33     aq.offer(acTempl);     //将动作队列的队尾添加动作
34    }
35    isGo=false;
36   }}
37 break;

第2-7行是判断是否移动动作。第9-19行是如果不在第一视角内、移动动作标志位为true,并且按下的初始位置不在摇杆内,则将改变摄像机的动作添加到动作队列。
第20-27行是如果移动标志位为true,并且按下的初始位置在摇杆内,则将摇杆动作添加到动作队列。第28-36行是如果为前进动作,则将前进动作添加到动作队列,并将前进标志位置为false。
(5)介绍完移动动作处理代码的开发后,下面为读者介绍的是前面省略的抬起动作时处理代码的开发,其代码如下。

1 case MotionEvent.ACTION_UP:
2  Action actemp=new Action(
3   ActionType.ACTION_UP 
4  );
5  synchronized(aqygLock){     //锁上动作队列
6   aqyg.offer(actemp);     //将动作队列的队尾添加动作
7  }
8  if(isYaogan){
9   if(vAngle>=-135&&vAngle<-45){//右转
10    Action acTemp=new Action(  //机器人向右转的动作
11     ActionType.ROBOT_RIGHT //动作类型
12       );
13    synchronized(aqLock){   //锁上动作队列
14     aq.offer(acTemp);   //将动作队列的队尾添加动作
15    }
16   }
17   if((vAngle>=45&&vAngle<90)||(vAngle>=-270&&vAngle<-225)){//左转
18    Action acTemp=new Action(  //机器人向左转的动作
19     ActionType.ROBOT_LEFT  //动作类型
20       );
21    synchronized(aqLock){   //锁上动作队列
22     aq.offer(acTemp);   //将动作队列的队尾添加动作
23    }
24   }
25   if(vAngle>=-225&&vAngle<-135){  //后转
26    Action acTemp=new Action(  //机器人向后转的动作
27     ActionType.ROBOT_DOWN  //动作类型
28       );
29    synchronized(aqLock){   //锁上动作队列
30     aq.offer(acTemp);   //将动作队列的队尾添加动作
31    }
32   }}
33 break;

第2-7行是将抬起动作添加到动作队列。第8-32行是如果按下的初始位置在摇杆内,则根据摇杆的不同动作将机器人的不同动作添加到动作队列。
(6)介绍了本类的触控方法后,接下来介绍的是本类中的内部类SceneRenderer,首先介绍其中的重写方法,其代码如下。

1 private class SceneRenderer implements GLSurfaceView.Renderer{
2  int currentFlagindex=0;       //当前帧编号
3  private boolean isFirstFrame=true;
4  int roomId;          //房间纹理id
5  ……//此处省略了部分纹理id的声明,读者可以自行查阅随书光盘中的源代码
6  @Override
7  public void onDrawFrame(GL10 gl) {
8   //清除深度缓存与颜色缓存
9   gl.glClear(GL10.GL_DEPTH_BUFFER_BIT|GL10.GL_COLOR_BUFFER_BIT);
10     if(!isLoadedOk) {      //如果没有加载完成 
11     inLoadView=true;
12               drawOrthLoadingView(gl);     //绘制加载进度条界面 
13            } else {
14     inLoadView=false;
15     drawGameView(gl);     //绘制游戏界面
16     }
17  }
18  @Override
19  public void onSurfaceChanged(GL10 gl, int width, int height) {
20   gl.glViewport(       //设置视口
21     Constant.screenScaleResult.lucX, Constant.screenScaleResult.lucY, 
22     (int)(Constant.screenWidthStandard*Constant.screenScaleResult. ratio), 
23     (int)(Constant.screenHeightStandard*Constant.screenScaleResult. ratio)
24   );
25   Constant.ratio=Constant.screenWidthStandard/Constant.screenHeightStandard;  
26   //设置为打开背面剪裁
27   gl.glEnable(GL10.GL_CULL_FACE);
28  }
29  @Override
30  public void onSurfaceCreated(GL10 gl, EGLConfig config) {
31   gl.glDisable(GL10.GL_DITHER);     //关闭抗抖动 
32   //设置特定Hint项目的模式,这里设置为使用快速模式
33   gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);
34   gl.glClearColor(0, 0, 0, 0);    //设置屏幕背景色黑色RGBA
35   gl.glEnable(GL10.GL_DEPTH_TEST);   //打开深度检测
36   gl.glDisable(GL10.GL_CULL_FACE);   //设置为打开背面剪裁
37   gl.glShadeModel(GL10.GL_SMOOTH);   //设置着色模型为平滑着色   
38   Constant.CURR_DIRECTION=POSITIVE_MOVETO_Z; //初始朝向为z轴正方向
39   Robot.RobotFlag=true;
40   currDegree=0;
41   isWinOrLose=false;
42   Constant.IS_DRAW_WIN=false;
43   isSkyAngle=true;       //天空旋转线程循环标志为true
44   initTexId(gl);       //初始化纹理
45   laodBack=new VertexTexture3DObjectForDraw( //加载界面背景矩形
46    VertexDataManager.vertexPositionArray[22],//加载界面背景矩形的顶点坐标数据
47    VertexDataManager.vertexTextrueArray[22],//加载界面背景矩形纹理坐标
48    VertexDataManager.vCount[22]      //顶点数
49   );
50   ……//此处省略了加载界面背景和文字的代码,读者可以自行查阅随书光盘中的源代码
51         new Thread(){       //启动一个线程动态切换帧(软体箱子)
52          @Override
53          public void run(){
54           while(true) {      //循环切换帧
55            currentFlagindex=(currentFlagindex+1)%texRect.length;
56               try {
57       Thread.sleep(100); //休息100ms
58      } catch (InterruptedException e) {
59       e.printStackTrace();
60      }
61           }}}.start(); 
62         new Thread(){       //启动天空旋转的线程
63          public void run() {
64           while(isSkyAngle) {    //循环更换角度
65            skyAngle=(skyAngle+0.2f)%360;
66            try {
67       Thread.sleep(100); //休息100ms
68      } catch (InterruptedException e) {
69       e.printStackTrace();
70      }
71           }}}.start();
72        initLight(gl);      //初始化灯光
73   initMaterial(gl);      //初始化材质
74  }
75  ……//此处省略了内部类中的部分方法,后面将会介绍
76 }

第2-5行是各种成员变量的声明。第6-17行是本内部类中的绘制方法onDrawFrame。第18-28行为重写onSurfaceChanged方法,在该方法中设置了视口的大小和位置,计算了宽高比,同时打开了背面剪裁。
第31-37行设置了关闭抖动、使用快速模式、打开深度检测、打开背面剪裁,并设置为平滑着色。
第38-50进行了一些成员变量的初始化。第51-61行创建并开启了改变软体箱子动画帧的线程。第62-74行创建并开启了改变天空旋转角度的线程。
(7)接下来为读者介绍的是游戏界面的绘制方法,首先介绍倒影的绘制,其代码如下。

1 public void drawGameView(GL10 gl) {     //绘制游戏界面
2  gl.glMatrixMode(GL10.GL_PROJECTION);    //设置当前矩阵为投影矩阵
3  gl.glLoadIdentity();        //设置当前矩阵为单位矩阵
4  visualAngle(gl,ratio);       //调用此方法计算产生透视投影矩阵
5  gl.glMatrixMode(GL10.GL_MODELVIEW);    //设置当前矩阵为模式矩阵
6  gl.glLoadIdentity();        //设置当前矩阵为单位矩阵 
7  cameraPosition(gl);        //摄像机设置
8  synchronized(gdDraw.dataLock) {     //锁上绘制数据
9   gdDraw.copyTo(gdTemp);       //将绘制数据复制进临时变量
10  }
11  synchronized(gqdDraw.gqdataLock) {   //锁上绘制数据
12   gqdTemp.boxCount=gqdDraw.boxCount; 
13   for(int i=0;i<gqdTemp.boxCount;i++){   //将箱子的位置复制临时变量
14    gqdTemp.cdArray[i].row=gqdDraw.cdArray[i].row;
15    gqdTemp.cdArray[i].col=gqdDraw.cdArray[i].col;
16   } 
17   for(int i=0;i<gqdDraw.MAP[GameData.level-1].length;i++){                  //将当前关卡数组复制临时变量
18    for(int j=0;j<gqdDraw.MAP[GameData.level-1][0].length;j++){
19     gqdTemp.MAP[GameData.level-1][i][j]=gqdMain.MAP[GameData.level       -1][i][j];
20    }}
21  }
22  gl.glDisable(GL10.GL_CULL_FACE);     //关闭背面剪裁
23  for(int i=0;i<gqdMain.MAP[GameData.level-1].length;i++){
24   for(int j=0;j<gqdMain.MAP[GameData.level-1][0].length;j++){
25    float xOffset=GuanQiaData.XOffset[i][j]; //格子在_x_轴方向的偏移量
26    float zOffset=GuanQiaData.ZOffset[i][j]; //格子在_z_轴方向的偏移量
27       if(gqdTemp.MAP[GameData.level-1][i][j]==1||gqdTemp.MAP[GameData.level      -1][i][j]==2||
28         gqdTemp.MAP[GameData.level-1][i][j]==4) {
29        //如果地图中为桥、箱子或机器人,则绘制桥
30        gl.glPushMatrix();
31        gl.glScalef(1, -1, 1);
32        gl.glTranslatef(xOffset, -0.1f, zOffset);  //平移
33        wall.drawSelf(gl,wallId);      //绘制桥的倒影
34        gl.glPopMatrix();
35       }
36       ……//此处省略了相似的绘制倒影的相关代码,读者可自行查阅随书光盘中源代码
37  gl.glPushMatrix();
38  gl.glScalef(1, -1, 1);
39  gl.glTranslatef(0, -0.4f, 0);       //平移
40  gl.glRotatef(skyAngle, 0, 1, 0);      //旋转
41  sky.drawSelf(gl, roomId);        //绘制天空的倒影
42  gl.glPopMatrix();
43  gl.glEnable(GL10.GL_CULL_FACE);      //打开背面剪裁
44     gl.glEnable(GL10.GL_BLEND);        //开启混合
45      //设置源混合因子与目标混合因子
46     gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
47  water.drawSelf(gl, waterId);       //绘制水面
48  gl.glDisable(GL10.GL_BLEND);
49  ……//此处省略了实体的绘制,下面将会介绍
50 }

第7行是调用方法cameraPosition进行摄像机的设置,方法cameraPosition是根据当前为第一视角或第三视角设置了摄像机,其中还对软体箱子进行了排序,由于篇幅所限,这里不再详细介绍,有需要的读者可查看随书光盘中的源代码。
第8-21行是将绘制数据复制进临时变量。第22-43行是倒影的绘制。第44-48行是水面的绘制。
(8)介绍完倒影的绘制后,接下来为读者介绍的是实体、摇杆和转换视角虚拟按钮的绘制,其代码如下。

1 public void drawGameView(GL10 gl) {      //绘制游戏界面
2  ……//此处省略的是前面介绍的部分
3  gl.glPushMatrix(); 
4     gl.glEnable(GL10.GL_LIGHTING);        //允许光照
5  gl.glTranslatef(0, GameStaticData.FLOOR_Y+0.8f, 0);  //平移
6  robot.drawSelf(gl);         //绘制机器人 
7     gl.glDisable(GL10.GL_LIGHTING);       //禁止光照
8     gl.glPopMatrix(); 
9  room.drawSelf(gl, roomId);        //绘制房间
10  gl.glPushMatrix();
11  gl.glRotatef(skyAngle, 0, 1, 0);     //旋转
12  sky.drawSelf(gl, roomId);       //绘制天空
13  gl.glPopMatrix();
14  for(int i=0;i<gqdMain.MAP[GameData.level-1].length;i++){
15   for(int j=0;j<gqdMain.MAP[GameData.level-1][0].length;j++){
16    float xOffset=GuanQiaData.XOffset[i][j]; //格子在_x_轴方向的偏移量
17    float zOffset=GuanQiaData.ZOffset[i][j]; //格子在_z_轴方向的偏移量
18       if(gqdTemp.MAP[GameData.level-1][i][j]==1||gqdTemp.MAP[GameData.level-1]            [i][j]==2||
19         gqdTemp.MAP[GameData.level-1][i][j]==4) {
20        //如果地图中为桥、箱子或机器人,则绘制桥
21        gl.glPushMatrix();
22        gl.glTranslatef(xOffset, GameStaticData.FLOOR_Y, zOffset);
23        wall.drawSelf(gl,wallId);     //绘制桥
24        gl.glPopMatrix();
25       }
26       if(gqdTemp.MAP[GameData.level-1][i][j]==3||gqdTemp.MAP[GameData.level-1]       [i][j]==6) {
27        //如果地图中为目的地或人在的目的地,则绘制目的地
28        gl.glPushMatrix();
29        gl.glTranslatef(xOffset, GameStaticData.FLOOR_Y, zOffset);//平移
30        wall.drawSelf(gl,targetId);         //目的地
31        gl.glPopMatrix();
32       }
33       if(gqdTemp.MAP[GameData.level-1][i][j]==5) {
34        //如果是推好的箱子,目的地也要绘制
35        gl.glPushMatrix();
36        gl.glTranslatef(xOffset, GameStaticData.FLOOR_Y, zOffset);
37        wall.drawSelf(gl,targetId);    //目的地倒影
38        gl.glPopMatrix();
39        gl.glPushMatrix();
40        gl.glTranslatef(xOffset, GameStaticData.FLOOR_Y+1f, zOffset);
41        wall.drawSelf(gl,targetId);    //绘制推好的箱子的倒影
42        gl.glPopMatrix();
43       } } }
44  for(int i=0;i<gqdTemp.boxCount;i++)     {//绘制箱子
45   //格子在_x_轴方向的偏移量
46   float xOffset=GuanQiaData.XOffset[gqdTemp.cdArray[i].row][gqdTemp.cdArray     [i].col]; 
47   //格子在_z_轴方向的偏移量
48   float zOffset=GuanQiaData.ZOffset[gqdTemp.cdArray[i].row][gqdTemp.cdArray     [i].col]; 
49   gl.glEnable(GL10.GL_BLEND);
50   gl.glPushMatrix();
51   gl.glTranslatef(xOffset, GameStaticData.FLOOR_Y+1f, zOffset);
52   if(gqdTemp.cdArray[i].row==GuanQiaData.move_row&&
53      gqdTemp.cdArray[i].col==GuanQiaData.move_col) {
54    gl.glTranslatef(GuanQiaData.xoffset, 0, GuanQiaData.zoffset);
55   }
56   if(gqdTemp.cdArray[i].row!=0&&gqdTemp.cdArray[i].col!=0) {
57    texRect[currentFlagindex].drawSelf(gl,boxId); //绘制当前帧
58   }
59   gl.glPopMatrix();
60   gl.glDisable(GL10.GL_BLEND);
61  }
62   gl.glEnable(GL10.GL_BLEND);       //开启混合
63   //设置源混合因子与目标混合因子
64      gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
65   gl.glMatrixMode(GL10.GL_PROJECTION);    //设置投影矩阵
66   gl.glLoadIdentity();        //设置当前矩阵为单位矩阵
67   gl.glOrthof(-ratio, ratio, bottom, top, near, far);//调用此方法计算产生正交投影矩阵 
68   GLU.gluLookAt (          //设置摄像机
69    gl, 
70    0,0,10,          //摄像机的位置
71    0,0,0,          //目标点
72    0,1,0            //法向量
73   ); 
74      gl.glMatrixMode(GL10.GL_MODELVIEW);      //设置模式矩阵
75      gl.glLoadIdentity();          //设置当前矩阵为单位矩阵 
76      gl.glPushMatrix();
77      gl.glTranslatef(1.35f, -0.6f, -0.1f);
78      gl.glTranslatef(offsetx*0.17f, offsety*0.17f, 0f);
79      yaogan2.drawSelf(gl,yaogan2Id);       //绘制中间圆
80      gl.glPopMatrix();
81      gl.glPushMatrix();
82      gl.glTranslatef(1.35f, -0.6f, 0f);
83      yaogan1.drawSelf(gl, yaogan1Id);       //绘制摇杆背景
84      gl.glPopMatrix(); 
85      gl.glPushMatrix();
86      gl.glTranslatef(1.64f, 0.86f, 0f);
87      left.drawSelf(gl, convertId);       //绘制视角转换的虚拟按钮
88      gl.glPopMatrix(); 
89      gl.glDisable(GL10.GL_BLEND);       //关闭混合
90      judgeGoToLastViewOrGoToNext(gl);        //判断游戏是否结束
91 }

第4-13行是机器人、房间和天空的绘制。第14-43行是循环关卡数组绘制除软体箱子外的实体。第44-61行是软体箱子的绘制。
第62-89行是摇杆和转换视角的虚拟按钮的绘制。第90行是调用方法判断游戏是否结束,并绘制结束界面的标志,由于篇幅所限,这里不再介绍,读者可查看随书光盘中的源代码。

6.6.4 动作队列执行线程TXZDoActionThread
本游戏将除摇杆动作外的操控动作对象存储在了一个操控队列,本节为读者介绍的是从此操控队列中取出操控动作进行执行的线程,其具体的开发步骤如下。

(1)首先给出的是该类的架构,只有了解了架构才能更好地进行开发,其代码如下。

1 package com.bn.txz.game;       //声明包
2 ……//此处省略了本类中导入类的代码,读者可以自行查阅随书光盘中的源代码
3 public class TXZDoActionThread extends Thread{  //执行动作队列的线程
4     public boolean workFlag=true;     //线程是否循环工作标志位
5     TXZGameSurfaceView gsv;      //游戏View引用
6     Queue<Action> aq;        //动作队列
7     Robot robot;
8     int i;          //控制箱子走动动画的变量
9     int row=0;         //机器人所在的行列
10  int col=0; 
11     public TXZDoActionThread(TXZGameSurfaceView gsv) {
12      this.gsv=gsv;        //游戏View
13      this.aq=gsv.aq;       //动作队列
14      this.robot=gsv.robot;
15     } 
16     @Override
17     public void run(){
18      while(workFlag){
19       Action ac=null;       //动作引用
20       synchronized(gsv.aqLock) {    //动作队列锁
21        ac=aq.poll(); //从动作队列中取出一个动作,若队列中没有操控动作则取出null
22       } 
23       if(ac!=null) {    //若操控动作引用不是null,即有动作需要执行
24           switch(ac.at) {   //at为操控动作的类型,根据操控类型执行不同的工作
25              case CHANGE_CAMERA:       //改变摄像机动作
26              synchronized(gsv.gdMain.dataLock)  {   //将主数据锁上
27               //将操控动作携带的数据赋值给主数据
28               gsv.gdMain.calculCamare(ac.data[0], ac.data[1]); 
29               synchronized(gsv.gdDraw.dataLock)  { //将绘制数据锁上
30                //将主数据赋值给绘制数据
31                gsv.gdDraw.updateCameraData(gsv.gdMain);
32               } }
33              break;
34              case ROBOT_LEFT:       //机器人左转动作 
35               RobotTurnLeft(); 
36              break;
37           ……//此处省略了与左转相似的右转和后传的代码,读者可自行查阅随书光盘中的源代码
38              case ROBOT_UP:        //机器人前进动作
39               ……//此处省略了机器人前进动作的代码,后面将会介绍
40              break;
41              case CONVERT:        //改变视角动作
42               gsv.viewFlag=!gsv.viewFlag;
43              break;
44           }}
45       try {
46    Thread.sleep(10);
47   } catch (InterruptedException e) {
48    e.printStackTrace();
49   }} }
50     ……//此处省略了本类中的一些方法,后面将会介绍
51 }

第4-10行是成员变量的声明。第11-15行是该类的构造方法,其在其他类创建该类对象时被调用。
第19-22行是从动作队列的队首取出一个操控动作。第25-43行是判断是何种动作,然后做出相应的操作。
(2)接下来为读者介绍的是上一步骤中省略的机器人前进动作的相应操作,其代码如下。

1 case ROBOT_UP://机器人前进动作
2  gsv.islnAnimation =true;
3  synchronized(gsv.gqdMain.gqdataLock) {
4     if(currDegree==POSITIVE_MOVETO_Z) {    //如果是_z_轴正方向
5      row=robot.m;        //机器人当前的位置
6      col=robot.n;
7      switch(gsv.gqdMain.MAP[GameData.level-1][row+1][col]) 
8      {           //判断下一步的位置是什么
9      case 0:         //遇到水
10      case 5:        //遇到摆好的木箱
11       break;     //以上情况不能走,所以什么都不做
12      case 3:     //遇到目标,人走,人的下一个位置改为人在的目标
13       if(gsv.gqdMain.MAP[GameData.level-1][row][col]==6) {
14        RobotGo();      //机器人走
15        gsv.gqdMain.MAP[GameData.level-1][row][col]=3;
16        gsv.gqdMain.MAP[GameData.level-1][row+1][col]=6;
17       }
18       if(gsv.gqdMain.MAP[GameData.level-1][row][col]==4) {
19        RobotGo();      //机器人走
20        gsv.gqdMain.MAP[GameData.level-1][row][col]=1;
21        gsv.gqdMain.MAP[GameData.level-1][row+1][col]=6;
22       }break;
23      case 6: 
24       if(gsv.gqdMain.MAP[GameData.level-1][row][col]==4||//遇到人在目标点上
25         gsv.gqdMain.MAP[GameData.level-1][row][col]==6) {
26        RobotGo();           //机器人走
27        gsv.gqdMain.MAP[GameData.level-1][row][col]=3;
28        gsv.gqdMain.MAP[GameData.level-1][row+1][col]=4;
29       }break;
30      case 1:             //遇到桥 
31      case 4:             //遇到人
32       if(gsv.gqdMain.MAP[GameData.level-1][row][col]==4) {
33        RobotGo();           //机器人走
34        gsv.gqdMain.MAP[GameData.level-1][row][col]=1;
35        gsv.gqdMain.MAP[GameData.level-1][row+1][col]=4;
36       } 
37              if(gsv.gqdMain.MAP[GameData.level-1][row][col]==6) {
38             RobotGo();          //机器人走
39             gsv.gqdMain.MAP[GameData.level-1][row][col]=3;
40             gsv.gqdMain.MAP[GameData.level-1][row+1][col]=4;
41       } break;
42      case 2:             //遇到箱子
43       //判断箱子的前面是什么
44       if(gsv.gqdMain.MAP[GameData.level-1][row+2][col]==0||
45         gsv.gqdMain.MAP[GameData.level-1][row+2][col]==5||
46         gsv.gqdMain.MAP[GameData.level-1][row+2][col]==2)
47       {}   //箱子的前面是桥或摆好的木箱,推不动,不做任何动作
48       else if(gsv.gqdMain.MAP[GameData.level-1][row+2][col]==3) { 
49                 //箱子前面为目标
50        if(gsv.gqdMain.MAP[GameData.level-1][row][col]==4) {
51         RobotArmUp();      //机器人抬胳膊
52         GuanQiaData.move_flag=true; 
53       //机器人原来的位置为桥
54         gsv.gqdMain.MAP[GameData.level-1][row][col]=1; 
55         RobotGo();       //机器人走
56         if(Constant.IS_YINXIAO) {   //设置音效
57          SoundUtil.playSounds(SoundUtil.XUANZHONG, 0, gsv.             activity);
58         }
59         GuanQiaData.move_flag=false;
60                           //箱子原来的地方绘制人
61         gsv.gqdMain.MAP[GameData.level-1][row+1][col]=4; 
62                           //目的地绘制推好的箱子
63         gsv.gqdMain.MAP[GameData.level-1][row+2][col]=5; 
64         gsv.gqdMain.boxCount=gsv.gqdMain.boxCount-1;
65         RobotArmDown();     //机器人放下胳膊
66         Salute();       //敬礼
67        } 
68        if(gsv.gqdMain.MAP[GameData.level-1][row][col]==6) {
69        ……//此处省略了与前面相似的代码,读者可自行查阅随书光盘中的源代码
70        }
71        GuanQiaData.xoffset=0;
72        GuanQiaData.zoffset=0;
73       }else if(gsv.gqdMain.MAP[GameData.level-1][row+2][col]==1) { 
74    ……//此处省略了与前面相似的箱子前面为桥时的代码,读者可自行查阅随书光盘中的源代码
75       }
76       break;
77     }}else if(currDegree==POSITIVE_MOVETO_X) {   //如果是_x_轴正方向 
78      ……//此处省略了与前面相似的代码,读者可自行查阅随书光盘中的源代码
79     }else if(currDegree==NEGATIVE_MOVETO_Z) {    //如果是_z_轴负方向 
80      ……//此处省略了与前面相似的代码,读者可自行查阅随书光盘中的源代码
81     }else if(currDegree==NEGATIVE_MOVETO_X) {    //如果是_x_轴负方向 
82      ……//此处省略了与前面相似的代码,读者可自行查阅随书光盘中的源代码
83     }
84     synchronized(gsv.gqdDraw.gqdataLock) {    //修改绘制数据
85     gsv.gqdDraw.boxCount=gsv.gqdMain.boxCount;
86      for(int i=0;i<gsv.gqdDraw.MAP[GameData.level-1].length;i++){
87       for(int j=0;j<gsv.gqdDraw.MAP[GameData.level-1][0].length;j++) {
88        gsv.gqdDraw.MAP[GameData.level-1][i][j]=
89      gsv.gqdMain.MAP[GameData.level-1][i][j];
90       }}}
91     gsv.islnAnimation=false;
92  }
93    break;

第9-11行是机器人遇到的是水和摆好的木箱时不做任何动作。第12-22行是遇到的是目标时,机器人向前走。
第23-29行是遇到的是人在目标点上,机器人向前走。第30-41行是遇到桥或人时,机器人向前走。第42-72行是遇到的是箱子时的操作,判断箱子前面是什么,是否能走。
图像说明文字 提示

前面省略的机器人动作的方法十分简单,由于篇幅问题,这里不再一一赘述,需要的读者可自行查阅随书光盘中的源代码进行学习。

相关文章
|
4天前
|
Linux 编译器 Android开发
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
在Linux环境下,本文指导如何交叉编译x265的so库以适应Android。首先,需安装cmake和下载android-ndk-r21e。接着,下载x265源码,修改crosscompile.cmake的编译器设置。配置x265源码,使用指定的NDK路径,并在配置界面修改相关选项。随后,修改编译规则,编译并安装x265,调整pc描述文件并更新PKG_CONFIG_PATH。最后,修改FFmpeg配置脚本启用x265支持,编译安装FFmpeg,将生成的so文件导入Android工程,调整gradle配置以确保顺利运行。
24 1
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
|
26天前
|
Java Android开发
Android 开发获取通知栏权限时会出现两个应用图标
Android 开发获取通知栏权限时会出现两个应用图标
12 0
|
1月前
|
XML 缓存 Android开发
Android开发,使用kotlin学习多媒体功能(详细)
Android开发,使用kotlin学习多媒体功能(详细)
103 0
|
1月前
|
设计模式 人工智能 开发工具
安卓应用开发:构建未来移动体验
【2月更文挑战第17天】 随着智能手机的普及和移动互联网技术的不断进步,安卓应用开发已成为一个热门领域。本文将深入探讨安卓平台的应用开发流程、关键技术以及未来发展趋势。通过分析安卓系统的架构、开发工具和框架,本文旨在为开发者提供全面的技术指导,帮助他们构建高效、创新的移动应用,以满足不断变化的市场需求。
18 1
|
1月前
|
机器学习/深度学习 调度 Android开发
安卓应用开发:打造高效通知管理系统
【2月更文挑战第14天】 在移动操作系统中,通知管理是影响用户体验的关键因素之一。本文将探讨如何在安卓平台上构建一个高效的通知管理系统,包括服务、频道和通知的优化策略。我们将讨论最新的安卓开发工具和技术,以及如何通过这些工具提高通知的可见性和用户互动性,同时确保不会对用户造成干扰。
33 1
|
1天前
|
数据库 Android开发 开发者
安卓应用开发:构建高效用户界面的策略
【4月更文挑战第24天】 在竞争激烈的移动应用市场中,一个流畅且响应迅速的用户界面(UI)是吸引和保留用户的关键。针对安卓平台,开发者面临着多样化的设备和系统版本,这增加了构建高效UI的复杂性。本文将深入分析安卓平台上构建高效用户界面的最佳实践,包括布局优化、资源管理和绘制性能的考量,旨在为开发者提供实用的技术指南,帮助他们创建更流畅的用户体验。
|
1天前
android-agent-web中js-bridge案例
android-agent-web中js-bridge案例
9 2
|
18天前
|
XML 开发工具 Android开发
构建高效的安卓应用:使用Jetpack Compose优化UI开发
【4月更文挑战第7天】 随着Android开发不断进化,开发者面临着提高应用性能与简化UI构建流程的双重挑战。本文将探讨如何使用Jetpack Compose这一现代UI工具包来优化安卓应用的开发流程,并提升用户界面的流畅性与一致性。通过介绍Jetpack Compose的核心概念、与传统方法的区别以及实际集成步骤,我们旨在提供一种高效且可靠的解决方案,以帮助开发者构建响应迅速且用户体验优良的安卓应用。
|
20天前
|
监控 算法 Android开发
安卓应用开发:打造高效启动流程
【4月更文挑战第5天】 在移动应用的世界中,用户的第一印象至关重要。特别是对于安卓应用而言,启动时间是用户体验的关键指标之一。本文将深入探讨如何优化安卓应用的启动流程,从而减少启动时间,提升用户满意度。我们将从分析应用启动流程的各个阶段入手,提出一系列实用的技术策略,包括代码层面的优化、资源加载的管理以及异步初始化等,帮助开发者构建快速响应的安卓应用。
|
20天前
|
Java Android开发
Android开发之使用OpenGL实现翻书动画
本文讲述了如何使用OpenGL实现更平滑、逼真的电子书翻页动画,以解决传统贝塞尔曲线方法存在的卡顿和阴影问题。作者分享了一个改造后的外国代码示例,提供了从前往后和从后往前的翻页效果动图。文章附带了`GlTurnActivity`的Java代码片段,展示如何加载和显示书籍图片。完整工程代码可在作者的GitHub找到:https://github.com/aqi00/note/tree/master/ExmOpenGL。
22 1
Android开发之使用OpenGL实现翻书动画