Android 3D 游戏案例开发大全

978-7-115-31456-7
作者: 吴亚峰 于复兴 杜化美
译者:
编辑: 张涛

图书目录:

详情

本书内容共分为10章,涵盖了基于OpenGL ES平台开发的多种类型的3D游戏案例,详细地介绍了Android平台下3D游戏的开发流程。全书共分为两篇,第一篇为OpenGL ES 1.x篇,其中第2~6章为基于OpenGL ES 1.x平台开发的3D游戏案例;第二篇为OpenGL ES 2.0篇,其中第7-10章为基于OpenGL ES 2.0平台开发的3D游戏案例。

图书摘要

Android3D游戏案例开发大全

吴亚峰 于复兴 杜化美 编著

百纳科技 审校

人民邮电出版社

北京

为什么要写这样的一本书

Android正以前所未有的速度聚集着来自世界各地的开发者,越来越多的创意被应用到Android应用程序开发中,大有席卷整个手机产业的趋势。而手机游戏在所有应用中占据着很大的比重,3D游戏正逐渐成为游戏中的主流。

面对如此火爆的Android大潮,一些有关Android的技术书籍也开始在各地书店上架。但是,纵观这些本来就为数不多的 Android书籍,却没有一本专门讲解 Android平台下利用 OpenGL ES进行3D游戏开发的案例书籍,读者如何把学习的Android知识系统地应用到实际项目中是许多读者进入实战角色前必备的技能。

本书正是在这种情况下应运而生的,作为国内第一本讲解Android平台下3D游戏开发案例的专业书籍,作者为这本书倾注了很多的心血。书中的案例涉及技术全面,涵盖了各种类型的游戏开发技巧,详细讲解了这些游戏开发时的思路和策划方案等。本书能够快速地帮助读者提高在Android平台下进行实际项目和3D游戏开发的实战能力。

内容导读

本书内容共分为 10章,涵盖了基于OpenGL ES平台开发的多种类型的3D游戏案例,详细地介绍了Android平台下3D游戏的开发流程。其中第2章~第6章为基于OpenGL ES 1.x平台开发的 3D游戏案例,第7章~第 10章为基于OpenGL ES 2.0平台开发的 3D游戏案例。全书各章主要内容安排如下。

第1章 Android平台简介及开发环境的使用

向读者介绍了Android的来龙去脉,并介绍了Android应用程序的框架,然后讲解了Android开发环境的搭建和调试。

第2章 棋牌类3D游戏——国际象棋人机对弈

本章介绍了人机对弈国际象棋游戏的开发,讲解了棋牌类游戏的开发思路,以及对弈算法的基本开发流程。本游戏最大的亮点就是采用 OpenGL ES 渲染引擎,真正模拟现实场景。棋子采用 3d Max制作,塑造的棋子逼真细腻,并且玩家可以在 3D场景中随意转动观察视角,感觉上如同身临其境一样,使玩家更能在手机中体味到无穷的乐趣。

第3章 物理引擎3D游戏——疯狂的石头

本章所介绍的游戏利用了2D的物理引擎,但是采用了OpenGL ES渲染引擎,界面精美细腻。该游戏属于闯关益智类游戏,玩法新颖独特,只要将游戏中的石头通过每关所设置的道具运送到指定位置就可取得胜利,如果在中途掉下,则游戏失败。通过本章学习,读者可以掌握物理引擎的使用。

第4章 蓝牙对战游戏——3D坦克对战

该游戏与读者常见的坦克大战有所不同,其最大的特色就是利用了蓝牙,读者可以通过蓝牙和好友的设备连接进行对战。本游戏为3D画面,场景绚丽,玩法惊险刺激,只要将对方击毁就可取得胜利。通过本章学,读者可以详细了解蓝牙对战游戏的开发思路。

第5章 竞速类游戏——快乐小球

本章所介绍的游戏属于竞速类。玩法是一个小球在一个管道内急速前进,玩家需要做的就是通过控制小球的方向或者跳跃躲避各式各样的障碍物,其中有些障碍物还可以直接撞碎,游戏中还有一些道具可以使用,如清屏炸弹、回血球等,玩家玩的时间越长分数就越高。

第6章 益智类游戏——3D推箱子

本章所介绍的游戏是3D版的推箱子。本游戏中设计的箱子有所不同,是半透明的“果冻”状态,十分精美。玩家需要做的就是,通过控制游戏人物Android机器人将“果冻”箱子推到指定位置,然后“果冻”箱子才会呈现出真正箱子的状态,只有所有箱子都推到指定位置,本关才可以通过。

第7章 物理引擎类游戏——3D抽方块

本游戏是利用物理引擎开发的一款3D游戏。游戏场景中有一组用小方块搭起来的塔,玩家需要做的就是,根据自己的判断小心地抽出塔中的小方块,而不使塔倒下,抽出的方块越多,得分就越高。如果抽取不当而使塔倒下,则游戏结束。

第8章 休闲类游戏——炫动方块

本章介绍的游戏灵感来源于 2D版的打砖块,但本游戏是通过OpenGL ES 2.0渲染的3D版炫动方块。游戏场景置身于一个管道中,画面绚丽多彩,光影效果变化丰富。玩法是通过控制管道一端的挡板,反弹小方块,将管道另一端的方块打击掉即可,如果没有接住小方块,则游戏失败。

第9章 体育竞技类游戏——3D乒乓球锦标赛

本章所介绍的游戏是人机对战版的3D乒乓球竞赛。玩家首先选择一个自己喜欢的国家队,然后通过控制一个乒乓球拍,以闯关的模式和各个国家队进行比拼,关卡越高,难道越大。游戏场景均为3D,细腻逼真,感觉如同身临其境一样,玩家能在手机中体味到无穷的乐趣。

第10章 体育竞技类游戏——3D极品桌球

本游戏是3D版本的桌球游戏。该游戏将场景置于3D场景中,包括小球的移动、碰撞,以及小球在灯光下的投影都是很真实的。本游戏可以切换第一人称视角和自由视角,还有小地图的显示,增加了游戏的可玩性。本游戏是使用OpenGL ES 2.0进行图像渲染的,场景效果更加绚丽,让玩家获得更好的游戏体验。

本书特点

1.技术新颖,贴近实战

本书涵盖了现实中所有的流行技术,如2D、3D、传感器、OpenGL ES、蓝牙、物理引擎,通过实例演示可以让读者很快地了解这些技术的使用。

2.实例丰富,讲解详细

本书既包括单机游戏的开发,也有网络对战游戏;既有通过OpenGL ES 1.x渲染的游戏,也有通过OpenGL ES 2.0渲染的游戏。游戏类型涵盖了当下流行的棋牌类、竞速类、益智类、休闲类、体育竞技类游戏。讲解深入透彻,详细到位。

3.案例经典,含金量高

本书中的案例均是精心挑选的,不同类型的案例有着其独特的开发方式。以真实的项目开发为讲解背景,包括了当下流行的游戏类型,讲解了开发时的思路,真实项目的策划方案,能够让读者全面地掌握3D手机游戏的开发,具有很高的含金量,非常适合各类读者学习。

为了帮助读者更好地利用本书提高自己的知识技能,本书附赠光盘中包含了所有案例的源程序。

本书面向的读者

· Android初学者

对于Android的初学者,可以通过本书第1章的内容巩固Android的知识,并了解项目开发流程。然后以此为踏板学习本书后面的案例,这样可以全面地掌握Android平台下3D游戏项目开发的技术与技巧。

· 有Java基础的读者

Android平台下的开发基于Java语言,所以,对于有Java基础的读者来说,阅读本书将不会感觉到困难。读者可以通过第1章的基础学习,迅速熟悉Android平台下应用程序的框架和开发流程,然后通过案例提高自己在实战项目开发方面的能力。

· 在职开发人员

本书中的案例都是作者精心挑选的,其中涉及的与项目开发相关的知识均是作者积累的经验与心得体会。具有一定开发经验的在职开发人员,可以通过本书案例进一步提高开发水平,并迅速转职成为具有Android项目开发实战经验的高手。

本书作者

吴亚峰,毕业于北京邮电大学,后留学澳大利亚卧龙岗大学取得硕士学位。1998 年开始从事Java应用的开发,有10多年的 Java开发与培训经验。主要的研究方向为OpenGL ES、手机游戏、Java EE以及搜索引擎。同时为手机游戏、Java EE独立软件开发工程师,并兼任百纳科技 Java培训中心首席培训师。近10年来为多家著名企业培养了上千名高级软件开发人员,曾编写过《菜鸟成长之路——Java程序员职场全攻略》、《Android 3D游戏开发技术详解与典型案例》、《Android应用案例开发大全》、《Android游戏开发大全》、《Android 3D游戏开发技术宝典——OpenGL ES 2.0》、《Unity 3D游戏开发技术详解与典型案例》等多本畅销书籍。2008年初开始关注Android平台下的3D应用开发,并开发出一系列优秀的Android应用程序与3D游戏。

于复兴,北京科技大学硕士,从业于计算机软件领域10年,在软件开发和计算机教学方面有着丰富的经验。工作期间曾主持科研项目“PSP 流量可视化检测系统研究与实现”,主持研发了省市级项目多项,同时为多家单位设计开发了管理信息系统,并在各种科技刊物上发表多篇相关论文。2008年开始关注Android平台下的应用开发,参与开发了多款手机3D游戏应用。

杜化美,西安电子科技大学硕士,有多年的 Java 程序开发与培训经验。曾参与两项国家自然科学基金项目,在国内外刊物上发表论文10余篇。同时兼任嵌入式独立软件工程师,在软件领域有8年的从业经验,最近3年致力于Android嵌入式系统的研究。

本书在编写过程中得到了唐山百纳科技有限公司 Java 培训中心的大力支持,同时仇磊、郭小月、李雪晴、宋盼盼、郑培阳、陈伟、章雅卓、付鹏、白冰、刘文洲、张鑫、郑小林、王海宁、王海峰以及作者的家人为本书的编写提供了很多帮助,在此表示衷心的感谢!

由于笔者的水平和学识有限,且书中涉及的知识较多,难免有错误疏漏之处,敬请广大读者批评指正,并多提宝贵意见,反馈邮箱javase6_guide@qq.com。编辑联系邮箱为zhangtao@ptpress.com.cn。

编者

随着移动互联网时代的到来,移动手持设备功能日益强大,从而使曾经只能在个人电脑上运行的 3D 游戏逐渐移植到了日常使用的手机上。本章通过一款经典的益智类游戏——3D 版推箱子在Android平台上的设计与实现,使读者掌握此类游戏开发的全过程。

本节将对3D游戏开发进行简单介绍,通过本节的学习,读者可以对3D版推箱子游戏有初步的认识,并了解本章开发案例的游戏效果及具体功能。

推箱子是一款来自日本的古老游戏,目的是训练逻辑思考能力。要求在一个狭小的空间内把木箱放在指定的位置,稍有不小心就会出现箱子无法移动或者通道被堵的情况。

推箱子游戏是一款老少皆宜的益智类游戏,虽然游戏画面与玩法简单,但趣味无穷。

介绍详细的开发步骤之前,首先对整个游戏的功能和玩法进行一些简单介绍。使读者对该游戏有一个感性的认识,其具体步骤如下。

(1)运行本游戏,首先进入欢迎界面,其效果如图6-1和图6-2所示。

▲图6-1 欢迎界面1

▲图6-2 欢迎界面2

(2)当游戏的欢迎界面结束后,进入游戏的菜单界面,菜单界面的左面是机器人推箱子的动画;右面是菜单界面的不同选项,单击不同的选项会进入不同的界面,其效果如图6-3所示。

▲图6-3 菜单界面

▲图6-4 设置界面

(3)在菜单界面单击“设置”按钮则进入设置界面,其效果如图6-4所示。在设置界面玩家可以设置本游戏的音效,单击“返回”按钮或返回键返回到菜单界面。

▲图6-5 关于界面1

▲图6-6 关于界面2

(4)在菜单界面单击“关于”按钮则进入关于界面,关于界面的效果是动态的,其效果如图6-5和图6-6所示。在关于界面上按下返回键返回到菜单界面。

(5)在菜单界面单击“帮助”按钮进入游戏的帮助界面,该界面中介绍了本游戏的使用和玩法。单击向右的箭头进入下一个帮助页面;单击向左的箭头返回上一个帮助界面,其效果如图6-7所示。

▲图6-7 帮助界面

▲图6-8 选关界面

(6)在菜单界面单击“选关”按钮进入游戏的选关界面,其效果如图6-8所示。在选关界面玩家可按下已解锁的关卡按钮进入相应关卡的游戏界面,单击“返回”按钮或返回键可返回菜单界面。

(7)在菜单界面单击“开始游戏”按钮则进入游戏界面,首先是游戏资源加载的等待界面,其效果如图6-9所示;加载完成之后进入游戏界面,其效果如图6-10所示。

(8)单击游戏界面右上方的“切换视角”按钮,玩家可以实现第一视角和第三视角的切换功能,其效果如图6-11和图6-12所示。

▲图6-9 等待界面

▲图6-10 游戏界面

▲图6-11 第三视角游戏界面

▲图6-12 第一视角游戏界面

(9)在游戏界面滑动右下方的摇杆可控制机器人的旋转和移动,向左滑动摇杆后松开,机器人执行左转的动画,其效果如图6-13和图6-14所示。机器人右转、后转与左转相似。

▲图6-13 机器人左转动画1

▲图6-14 机器人左转动画2

说明

机器人右转、后转和左转相似,由于篇幅有限,不再一一赘述,有兴趣的读者可自行运行该程序加以体会。

(10)在游戏界面向上滑动摇杆,游戏界面中的机器人执行前进的动画,其效果如图6-15所示。

▲图6-15 机器人前进动画

▲图6-16 机器人推箱子前进动画

(11)当机器人遇到可推动的箱子时,向上滑动摇杆,游戏界面中的机器人执行推箱子前进的动画,其效果如图6-16所示;推完箱子之后,机器人会面向摄像机的方向敬礼,其效果如图6-17所示;机器人将箱子推到目的地后,箱子会变成与目的地一样的颜色,其效果如图6-18所示。

▲图6-17 机器人敬礼动画

▲图6-18 箱子到达目的地

(12)按下返回键时,会弹出提示对话框,其效果如图6-19所示,单击“确定”按钮返回菜单界面,按下“取消”按钮则继续游戏。

▲图6-19 提示界面

▲图6-20 本关胜利界面

(13)游戏胜利后会弹出提示胜利的对话框,其效果如图6-20所示;单击“返回”按钮则返回菜单界面;单击“下一关”按钮则进入下一关游戏界面,其效果如图6-21所示。

▲图6-21 下一关游戏界面

上一节介绍了本游戏的背景及功能,本节将介绍游戏的策划及开发前的准备工作,这些工作虽然略显枯燥,但是在游戏的开发过程中却起到很大的作用。在正式开发游戏前进行细致地策划能让开发人员在开发前对游戏有总体的把握,理清逻辑关系,使游戏开发过程更加顺利。

本小节将对游戏的策划进行简单介绍,在真实的游戏开发中,该步骤还需更细致、更具体、更全面,该游戏的策划如下所列。

· 游戏情节

本游戏属于休闲益智类游戏,情景设置非常简单,主要有机器人、箱子、目标点、水和桥等。

· 运行目标平台

游戏目标平台为Android 2.2或更高的版本。

· 操作方式

本游戏通过触摸屏幕进行操作,玩家可触控屏幕的虚拟摇杆完成机器人的前进、箱子前进、机器人左转弯、右转弯等动作,触控屏幕右上角的“视角切换”按钮来完成界面视角的切换。

· 呈现技术

本游戏采用OpenGL ES1.0渲染引擎,使得游戏画面更加流畅、真实。游戏在第三视角下可随意拖动屏幕变换视角,并可在第一视角和第三视角之间切换。

· 音效设置

为了增强游戏的用户体验,本游戏添加了背景音乐及箱子到达目的地后的音效。

完成了游戏的策划之后,下面介绍游戏开发的准备工作,主要包括搜集和制作图片、声音,以及机器人的3D模型文件(obj文件)等资源,其详细步骤如下。

(1)首先介绍的是游戏中用到的图片资源,将图片资源都放在项目文件下的assets目录下,其详细情况如表6-1所列。

表6-1 游戏中的图片资源

续表

续表

(2)本游戏中添加了声音,这样使游戏更加真实。其中需要将声音资源放在项目目录中的res/raw文件夹下,其详细情况如表6-2所列。

表6-2 声音资源列表

(3)本游戏界面中用到的机器人是通过 3d Max生成 obj文件,然后加载的。生成的 obj文件需要放在项目目录中的 assets文件下。其详细情况如表 6-3 所列。

表6-3 模型文件清单

续表

本节主要介绍本游戏的类框架,通过本节的介绍,读者可以进一步了解本游戏的开发过程,为后面要开发的代码部分打下坚实的基础。

为了使读者可以更好地了解各个类的作用,本小节将本游戏中用到的所有类分为4部分进行介绍,而每个类的代码将在后面的小节中相继给出。

1.公共类

· Activity的实现类TXZActivity。

TXZActivity是本游戏中的主控制类,此类继承自Activity,是整个游戏的控制类,同时也是整个游戏的入口。

· 常量类Constant。

本类是常量类,该类主要是存储一些静态常量和静态方法。

2.辅助界面相关类

· 欢迎界面类TXZWelcomeView。

本类是欢迎界面类,该类是在游戏开始时进行调用。

· 主菜单界面类TXZMenuView。

本类是主菜单界面类,该类负责绘制菜单界面,以及通过单击按钮跳转到其他界面,例如,进入游戏、设置、选关、帮助、关于等,也可以通过单击返回键直接退出游戏。

· 主菜单界面机器人部件类MenuBodyPart。

本类是主菜单界面机器人部件类,该类的主要功能是提供给了组装机器人、机器人绘制,以及机器人运动的各种方法。

· 主菜单界面机器人动作执行类MenuDoActionThread。

· 本类是主菜单界面机器人动作的执行类,该类主要负责执行机器人的各个动作。

· 设置界面类TXZSetView。

本类是设置界面类,该类负责设置界面的绘制,该界面的主要功能是实现声音的设置,单击对应的按钮将进行相关的设置。

· 设置界面机器人部件类SetBodyPart。

本类是设置界面机器人部件类,该类的主要功能是提供给了组装机器人、机器人绘制,以及机器人运动的各种方法。

· 设置界面机器人动作执行类SetDoActionThread。

· 本类是设置界面机器人动作的执行类,该类主要负责执行机器人的各个动作。

· 选关界面类TXZSelectView。

本类是选关界面类,该类负责绘制选关界面,在该界面中玩家可以选择已经解锁的关卡从而进入相应的游戏界面。

· 选关界面机器人部件类SelectBodyPart。

本类是选关界面机器人部件类,该类的主要功能是提供给了组装机器人、机器人绘制,以及机器人运动的各种方法。

· 选关界面机器人动作执行类SelectDoActionThread。

· 本类是选关界面机器人动作的执行类,该类主要负责执行机器人的各个动作。

· 关于界面类TXTAboutView。

本类是游戏关于界面类,该类负责绘制游戏关于界面,该界面对游戏版权进行了说明,按下返回键可以返回到菜单界面。

· 帮助界面类TXZHelpView。

本类是游戏帮助界面类,该类负责绘制游戏帮助界面,该界面对游戏玩法进行了说明,单击向右方向键可查看下一条说明;单击向左方向键可查看上一条说明。在第一页单击向左方向键或在最后一页单机向右方向键返回到主菜单界面。

· 枚举类WhichView。

本类是枚举类,该类主要是存储一些界面的类型。

3.游戏界面相关类

· 游戏主界面类TXZGameSurfaceView

本类是游戏的主界面类,主要负责游戏界面中各个对象的创建及各个实体的绘制等工作。

· 游戏线程类TXZDoActionThread。

本类是游戏线程类,其主要负责监听与执行整个游戏过程中产生的动作。

· 摇杆动作线程类YGDoActionThread。

本类是摇杆动作线程类,其主要负责的是监听与执行摇杆的动作。

· 机器人类Robot。

本类是机器人类,该类主要负责机器人的组装与绘制,其中包括各个界面中机器人的组装和绘制。

· 游戏中动作类Action

本类是游戏中动作类,其主要提供了创建动作对象的构造方法,该类对象有动作数据与动作类型。

· 动作种类枚举类ActionType。

本类是动作种类枚举类,主要负责提供游戏过程中用到的动作的类型,即摄像机的动作、机器人的动作、视角的转换动作、摇杆的移动动作和抬起动作。

· 游戏界面机器人部件类BodyPart

本类是游戏界面中机器人的部件类,该类的主要功能是提供给了组装机器人、机器人绘制,以及机器人运动的各种方法。

· 部件的数据类BodyPartData

本类是机器人某个部件的数据类,其中包括机器人子骨骼在初始坐标系中的不动点、子骨骼在父骨骼坐标系中的平移、旋转,以及旋转的辅助平移。

· 机器人的动作数据类ActionGenerator

本类是机器人的动作数据类,其中包括机器人各种动作的数据。

· 游戏中动态数据类GameData。

本类是游戏中动态数据类,其主要负责提供游戏中用到的动态数据,以及修改数据的方法。

· 游戏中静态数据类GameStaticData。

本类是游戏中静态数据类,其主要负责提供游戏中用到的静态数据。

· 游戏中的关卡类GuanQiaData

本类是游戏中的关卡类,主要负责提供关卡的数据,以及游戏界面中需要的一些数据和方法。

· 顶点数据管理类 VertexDataManager。

本类是顶点数据管理类,该类负责加载界面所需物体的obj文件与初始化物体顶点位置坐标数据和顶点纹理坐标数据。

· 加载图片类 PicDataManager。

本类是加载图片的类,该类主要提供了将图片数据加载进内存的方法。

· 绘制物体类VertexTexture3DObjectForDraw。

本类是绘制物体类,该类负责对物体进行普通的绘制。

· 绘制物体类VertexTextureNormal3DObjectForDraw。

本类是绘制物体类,该类负责对加载物体进行带光照的绘制。

4.相关工具类

· 箱子排序类CompareDis。

本类是箱子的排序类,该类实现了接口Comparable,其是根据摄像机与箱子的距离排序。

· 模型加载类LoadUtil。

本类是模型加载类,该类从obj文件中将模型的相关信息加载进来,然后生成顶点位置坐标数据、顶点纹理坐标数据和法向量数据,最后构造对应的模型对象。

· 法向量类Normal。

本类是法向量类,该类主要提供了计算平均法向量的方法。

· 屏幕自适应工具类ScreenScaleUtil。

本类是屏幕自适应工具类,该类主要提供了计算缩放情况的方法。

· 屏幕自适应工具类ScreenScaleResult。

本类是为ScreenScaleUtil服务,两者共同完成缩放工作,实现游戏全屏显示功能,即实现游戏屏幕的自适应。

· 记录关卡工具类SharedPreferencesUtil

本类是记录关卡工具类,该类的作用是记录玩家最近一次的关卡数,若是第一次进入游戏,默认为第一关。

· 声音加载类SoundUtil。

本类是声音加载工具类,该类主要提供了声音缓冲池初始化的方法及声音的播放方法。

· 法向量的计算类VectorUtil

本类是计算法向量的工具类,其中有向量叉积的计算方法和向量规格化的方法。

在前一小节中介绍了游戏中用到的所有类,读者可能对每个类的理解并不深刻,本小结将从游戏的整体架构来介绍,如图6-22所示。

▲图6-22 游戏整体架构

从图 6-22 所示中可以看到,本游戏的类很多,这里不一一介绍,接下来按照程序运行的顺序介绍部分类的作用及游戏的整体框架,具体步骤如下。

(1)打开本游戏,首先进入游戏的主控制类 TXZActivity,在该类中首先跳转到欢迎界面TXZWelcomeView。

(2)欢迎界面TXZWelcomeView结束后跳转到主菜单界面TXZMenuView。

(3)在主菜单界面TXZMenuView中单击“设置”按钮进入设置界面TXZSetView,可以对游戏进行设置;单击“选关”按钮进入选关界面 TXZSelectView;单击“关于”按钮进入关于界面TXTAboutView,可以查看该游戏版权所属的相关信息;单击“帮助”按钮进入帮助界面TXZHelpView,可以查看游戏的帮助信息。

(4)在主菜单界面TXZMenuView中单击“开始游戏”按钮进入游戏界面TXZGameSurfaceView。

Android程序中,Activity是最重要的类之一。在本游戏中,TXZActivity继承自Activity类,为本游戏的主控制类,本节将具体介绍TXZActivity的开发,其具体步骤如下。

(1)主控制类代码非常多,首先搭建主控制类的框架,以帮助读者理解,主控制类框架代码如下。

代码位置:见随书光盘中源代码/第6章/TXZ/src/com/bn/txz目录下的TXZActivity.java。

1package com.bn.txz;         //声明包

2……//此处省略了本类中导入类的代码,读者可以自行查阅随书光盘中的源代码

3public class TXZActivity extends Activity {

4 TXZGameSurfaceView gv;       //游戏View引用

5 public SharedPreferencesUtil sharedUtil;  //声明SharedPreferencesUtil引用

6 AudioManager audio;       //游戏中控制音量工具对象

7 public MediaPlayer beijingyinyue;    //播放器

8 public SharedPreferences sp;     //声明SharedPreferences引用

9 public boolean yinXiao=true;     //是否播放游戏音效标志位

10  @Override

11  public void onCreate(Bundle savedInstanceState) {

12   super.onCreate(savedInstanceState);

13   requestWindowFeature(Window.FEATURE_NO_TITLE);

14   //设置为全屏显示

15   getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN ,

16      WindowManager.LayoutParams.FLAG_FULLSCREEN);

17   //设置为横屏模式

18   setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

19   DisplayMetrics dm=new DisplayMetrics();

20   getWindowManager().getDefaultDisplay().getMetrics(dm);

21   Constant.screenWidth=dm.widthPixels; //获取屏幕横向分辨率

22   Constant.screenHeight=dm.heightPixels;

23   Constant.ScaleSR();     //调用Constant类中计算缩放比例的方法

24   Constant.ratio=screenWidthStandard/screenHeightStandard; //宽、高、比

25   sp= this.getSharedPreferences("sysz", Context.MODE_PRIVATE);//SharedPreferences对象

26   yinXiao=sp.getBoolean("youxiyinxiao", true); //获得boolean值yinXiao

27   //初始化SharedPreferencesUtil对象

28   sharedUtil=new SharedPreferencesUtil(TXZActivity.this);

29   sharedUtil.getPassNum();      //设置了初始的关卡数

30   new Thread(){        //加载的线程

31   public void run(){

32    synchronized(Constant.initLock) {

33      //加载图片数据进内存

34     PicDataManager.loadPicData(TXZActivity.this);

35     //加载物体顶点位置、纹理坐标数据进内存缓冲

36     VertexDataManager.initVertexData(TXZActivity.this.getResources());

37     SoundUtil.initSounds(TXZActivity.this); //加载游戏音效

38   }} }.start();

39   gotoWelcomeView();        //到欢迎界面

40  }

41 ……//此处省略了本类中消息处理Handler代码,将在下面介绍

42 ……//此处省略了跳转到游戏中部分界面的方法,将在下面进行介绍

43 ……//此处省略了显示对话框的方法,读者可自行查阅随书光盘中源代码

44 ……//此处省略了本类中按键监听方法,将在下面进行介绍

45 ……//此处省略了本类中播放背景音乐方法,将在下面进行介绍

46 ……//此处省略了本类中停止播放背景音乐方法,将在下面进行介绍

47 ……//此处省略了重写的onResume与onPause方法,读者可以自行查阅随书光盘中的源代码

48 }

· 第13-18行是设置屏幕的显示模式,设置为全屏、横屏模式。第19-22行为根据设备的具体参数获取屏幕分辨率及宽高比,并记录下来,使游戏的界面符合设置的横屏模式。

· 第 30-38 行是图片、物体的顶点位置及纹理坐标和游戏音效的加载,初始化游戏资源。第39行为进入欢迎界面。

(2)游戏中部分界面的跳转和信息的显示依靠消息传递机制。在Activity中的消息接收者通过接收的消息编号执行相应的操作,来实现界面跳转及信息显示的功能,其代码如下。

代码位置:见随书光盘中源代码/第6章/TXZ/src/com/bn/txz目录下的TXZActivity.java。

1 public Handler handler = new Handler(){   //接收消息的handler

2 @Override

3 public void handleMessage(Message msg) {

4   super.handleMessage(msg);

5  switch (msg.what) {

6     case COMMAND_GOTO_GAME_VIEW:  //返回游戏界面

7      gotoGameView();

8     break;

9     case COMMAND_GOTO_MENU_VIEW:  //返回菜单界面

10       gotoMenuView();

11     break;

12     case COMMAND_GOTO_GUAN_VIEW: //返回选关界面

13       gotoSelectView();

14       break;

15     case COMMAND_GOTO_SET_VIEW: //返回设置界面

16       gotoSetView();

17       break;

18  }}

19 };

说明

第1-3行为初始化Handler并重写handleMessage方法,接收传来的消息。第5-17行为根据收到的消息进行判断,跳转到不同的界面。

(3)由于本类的跳转界面比较多,不可能对所用的界面跳转进行一一介绍,所以,这里只介绍部分具有代表性的界面跳转代码的开发,其代码如下。

代码位置:见随书光盘中源代码/第6章/TXZ/src/com/bn/txz目录下的TXZActivity.java。

1 public void gotoWelcomeView(){    //返回欢迎界面去的方法

2 TXZWelcomeView wv=new TXZWelcomeView(this);

3  setContentView(wv);      //返回欢迎界面

4 }

5 public void gotoMenuView(){     //返回菜单界面去的方法

6 TXZMenuView mv=new TXZMenuView(this);

7  setContentView(mv);      //返回菜单界面

8  currView=WhichView.MENU_VIEW;   //更新当前View到菜单View

9  if(IS_BEIJINGYINYUE) {  //如果IS_BEIJINGYINYUE为true,则播放背景音乐

10   if(beijingyinyue==null) {

11    playBeiJingYinYue();   //调用播放背景音乐的方法

12   }}

13  }

14  public void gotoGameView( {   //返回游戏界面去的方法

15  synchronized(Constant.initLock) {

16   gv=new TXZGameSurfaceView(this);

17   setContentView(gv);    //返回游戏界面

18    gv.requestFocus();    //获取焦点

19    gv.setFocusableInTouchMode(true); //设置为可触控

20    currView=WhichView.GAME_VIEW; //更新当前View到游戏View

21  }}

· 第1-4行是返回欢迎界面去的方法。第5-13行为返回菜单界面的方法,在方法中主要是创建界面的对象,并调用setContentView方法跳转到菜单界面,同时根据是否播放背景音乐的标志位进行背景音乐的播放。

· 第14-21行是返回游戏界面的方法,在方法中主要是创建界面的对象,并调用setContentView方法跳转到菜单界面,同时设置为可触控。

(4)接下来开发onKeyDown方法,该方法为继承Activity后重写的方法,实现对手机按键的监听,玩家单击后做出相应的处理,其代码如下。

代码位置:见随书光盘中源代码/第6章/TXZ/src/com/bn/txz目录下的TXZActivity.java。

1@Override

2public boolean onKeyDown(int keyCode, KeyEvent event) { //监听返回键方法

3 if(keyCode!=4) {         //如果不是返回键则不做处理

4  return false;

5 }

6 if(keyCode==4) {         //当前是返回键

7  if(currView!=null) {

8   switch(currView) {

9    case SET_VIEW:       //当前在设置界面

10     SET_IS_WHILE=false;

11     Constant.set_flag=false;

12     gotoMenuView();     //到菜单界面

13     break;

14     case MENU_VIEW:     //当前在菜单界面

15     MENU_IS_WHILE=false;    //菜单界面是否循环工作标志位

16     Constant.menu_flag=false;

17     stopBeiJingYinYue();    //停止播放背景音乐

18     System.exit(0);     //退出程序

19     break;

20     case GAME_VIEW:     //当前在游戏界面

21     backdialog();      //显示提示对话框

22     break;

23     case ABOUT_VIEW:     //当前在关于界面

24     Constant.about_flag=false;  //关于界面是否循环工作标志位

25     gotoMenuView();     //返回菜单界面

26     break;

27     case HELP_VIEW:     //当前在帮助界面

28     gotoMenuView();     //返回菜单界面

29     break;

30     case GUAN_VIEW:     //当前在选关界面

31     Constant.select_flag=false;  //选关界面是否循环工作标志位

32     Constant.SELECT_IS_WHILE=false; //选关界面是否循环工作标志位

33     gotoMenuView();     //返回菜单界面

34     break;

35    } }}

36  return false;

37  }

· 第3-5行是判断当前按下的键值,如果键值表示的是按下返回键,则在本方法中处理,否则不在本方法中处理。

· 第7-35行表示的是若当前界面不为空,则根据当前的界面做出不同的操作,即返回某个界面或显示对话框、或退出游戏。

(5)接下来为读者介绍的是播放背景音乐与停止播放背景音乐方法的开发,其代码如下。

代码位置:见随书光盘中源代码/第6章/TXZ/src/com/bn/txz目录下的TXZActivity.java。

1public void playBeiJingYinYue(){      //播放背景音乐的方法

2 beijingyinyue=MediaPlayer.create(this,R.raw.mainmp3);

3 beijingyinyue.setLooping(true);      //设置为循环模式

4 beijingyinyue.start();        //开始播放音乐

5}

6public void stopBeiJingYinYue(){      //停止播放背景音乐的方法

7 if(beijingyinyue!=null) {

8  beijingyinyue.stop();       //停止播放音乐

9  beijingyinyue=null;

10  }

11  SharedPreferences.Editor editor=sp.edit();

12  editor.clear();          //清空sp中存储的所有内容

13  editor.commit();         //提交

14  }

15 }

说明

这两种方法的开发非常简单,其实现的主要功能是对MediaPlayer的控制,同时在停止播放背景音乐时要清空SharedPreferences对象sp中存储的内容。

前一小节介绍了主控制类 TXZActivity,本小节将对该游戏的辅助界面相关类进行介绍,该游戏的辅助界面主要是欢迎界面 TXZWelcomeView 类、菜单界面 TXZMenuView 类、设置界面TXZSetView类、帮助界面TXZHelpView类、选关界面TXZSelectView类,以及关于界面TXTAboutView类,下面就对这些类的开发进行详细介绍。

欢迎界面是进入游戏的第一个界面。此界面包括两幅图片的闪屏,闪屏的开发是为了加载资源时游戏不至于出现黑屏的情况。

(1)下面先介绍欢迎界面主体框架的开发,开发人员需要根据界面的功能来设计欢迎界面类的方法,其代码如下。

代码位置:见随书光盘中源代码/第6章/TXZ/src/com/bn/txz目录下的TXZWelcomeView.java。

1package com.bn.txz;          //声明包

2……//此处省略了本类中导入类的代码,读者可以自行查阅随书光盘中的源代码

3public class TXZWelcomeView extends SurfaceView implements SurfaceHolder.Callback{

4 static Bitmap[] logos;        //Logo图片数组

5 static boolean loadFlag=false;      //是否加载图片的标志位

6 TXZActivity activity;        //activity的引用

7 Bitmap currentLogo;         //当前Logo图片引用

8 Paint paint;          //画笔

9 int currentAlpha=0;         //当前的不透明值

10  int sleepSpan=100;        //动画的时延(ms)

11  float currentX;        //图片位置

12  float currentY;

13  ScreenScaleResult ssr;

14  public TXZWelcomeView(TXZActivity activity) {

15   super(activity);

16   this.activity = activity;

17   ssr=ScreenScaleUtil.calScale(screenWidth, screenHeight);

18   this.loadBitmap();       //加载图片

19   this.getHolder().addCallback(this);  //设置生命周期回调接口的实现者

20   paint = new Paint();      //创建画笔

21   paint.setAntiAlias(true);     //打开抗锯齿

22  }

23  public void loadBitmap(){    //将图片加载进内存的方法

24   if(loadFlag) {     //若加载图片标志位为true,则直接return

25    return;

26   }

27   loadFlag=true;

28   logos=new Bitmap[2];

29   //加载图片

30   logos[0]=BitmapFactory.decodeResource(this.getResources(),R.drawable.bnkj);

31   logos[1]=BitmapFactory.decodeResource(this.getResources(),R.drawable.welcome);

32  }

33 ……//此处省略了绘制方法,将在下面进行介绍

34  public void surfaceChanged(SurfaceHolder arg0,

35    int arg1, int arg2, int arg3){}    //画布改变时调用

36  public void surfaceCreated(SurfaceHolder holder){  //创建时被调用

37  ……//此处省略了创建时的具体代码,将在下面进行介绍

38  }

39  public void surfaceDestroyed(SurfaceHolder arg0){}  //销毁时被调用

40 }

· 第4-13行是此类中各种成员变量引用的声明。第14-22行为此类的构造器,其中,创建了主控制类对象,加载了图片,设置了生命周期回调接口实现者,创建了画笔并对其进行了设置。

· 第23-32行是将图片加载进内存的方法。第34-39行为重写界面创建、改变,以及摧毁时执行的方法。

(2)欢迎界面中的绘制方法非常简单,先绘制了黑色填充矩形的背景,之后绘制当前的 Logo图片,其代码如下。

代码位置:见随书光盘中源代码/第6章/TXZ/src/com/bn/txz目录下的TXZWelcomeView.java。

1 public void onDraw(Canvas canvas){       //重写onDraw方法

2  canvas.save();           //保存画布

3  canvas.translate(ssr.lucX,ssr.lucY);     //移动画布

4  canvas.scale(ssr.ratio,ssr.ratio);      //缩放画布

5  paint.setColor(Color.BLACK);       //设置画笔颜色

6  paint.setAlpha(255);

7  //绘制黑填充矩形清背景

8  canvas.drawRect(0, 0, screenWidthStandard, screenHeightStandard, paint);

9  if(currentLogo==null)return;

10   paint.setAlpha(currentAlpha);      //设置当前不透明度

11   canvas.drawBitmap(currentLogo,currentX,currentY,paint);//绘制当前的Logo

12   canvas.restore();         //恢复画布

13  }

说明

第3-4行是设置画布。第5-6设置了画笔的颜色和透明度。第7-8行绘制了黑色填充矩形的背景。第 9-11 行根据当前的透明度设置了画笔,当前 Logo 不为空时绘制当前Logo,方法中绘制之前需要保存画布,绘制之后恢复画布。

(3)欢迎界面中界面创建时调用的方法非常重要,其中开启了实时更改图片的透明度,并根据此刻的透明度绘制图片的线程,其代码如下。

代码位置:见随书光盘中源代码/第6章/TXZ/src/com/bn/txz目录下的TXZWelcomeView.java。

1public void surfaceCreated(SurfaceHolder holder) { //创建时被调用

2 new Thread(){

3  public void run(){

4   for(int j=0;j<logos.length;j++){  //循环每个闪屏的图片

5     Bitmap bm=logos[j];    //闪屏图片为logos中的第j个

6     currentLogo=bm;     //当前闪屏图片赋值

7     currentX=screenWidthStandard/2-bm.getWidth()/2; //计算图片位置

8     currentY=screenHeightStandard/2-bm.getHeight()/2;

9     for(int i=255;i>-10;i=i-10) {  //动态更改图片的透明度值并不断重绘

10       currentAlpha=i;

11       if(currentAlpha<0) {  //当前透明值小于零时

12        currentAlpha=0;  //设置当前透明值为0

13     }

14     SurfaceHolder myholder=TXZWelcomeView.this.getHolder();

15     Canvas canvas = myholder.lockCanvas(); //获取画布

16     try{

17        synchronized(myholder){

18         onDraw(canvas);   //绘制

19        }

20     }catch(Exception e){

21        e.printStackTrace();

22     }finally{

23      if(canvas != null){

24         myholder.unlockCanvasAndPost(canvas); //解锁

25        }

26       }

27       try{

28        if(i==255) {     //若是新图片,多等待一会

29         Thread.sleep(1000);

30        }

31        Thread.sleep(sleepSpan);  //线程休眠指定的时间

32       }catch(Exception e) {

33        e.printStackTrace();

34       }}

35    }

36    //闪屏结束则向Activity发送消息

37    activity.handler.sendEmptyMessage(Constant.COMMAND_GOTO_MENU_VIEW);

38   }}.start();         //启动线程

39 }

说明

第5-8行是设置当前闪屏的图片及图片的位置。第28-31行是设置图片透明度更改间隔的时间。第36-37行是闪屏结束时向Activity发送消息进入菜单界面。

菜单界面是欢迎界面结束后进入的界面,其中包括了开始游戏、设置、选关、关于、帮助和退出6个按钮,按下相应按钮之后进入不同的界面。

(1)首先介绍菜单界面主体框架的开发,开发人员需要根据界面的功能来设计菜单界面类的方法,其代码如下。

代码位置:见随书光盘中源代码/第6章/TXZ/src/com/bn/txz目录下的TXZMenuView.java。

1package com.bn.txz;         //声明包

2……//此处省略了本类中导入类的代码,读者可以自行查阅随书光盘中的源代码

3public class TXZMenuView extends GLSurfaceView{

4 TXZActivity activity;       //Activity引用

5 TXZMenuView menu;        //菜单界面View引用

6 private SceneRenderer mRenderer;    //场景渲染器

7 public GameData gdMain=new GameData();   //主数据

8 GameData gdDraw=new GameData();    //绘制数据

9 GameData gdTemp=new GameData();    //临时数据

10  boolean flagn=true;

11  float anglet=0;

12  float anglex=25;

13  boolean flagx=false;      //改变角度标志位

14  boolean color=false;      //改变颜色标志位

15  public TXZMenuView(Context context) {

16   super(context);

17   this.activity=(TXZActivity)context;

18   Constant.MENU_IS_WHILE=true;

19   mRenderer = new SceneRenderer();  //创建场景渲染器

20   setRenderer(mRenderer);    //设置渲染器

21   //设置渲染模式为主动渲染

22   setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);

23  }

24 ……//此处省略了触控方法onTouchEvent,将在下面进行介绍

25  private class SceneRenderer implements GLSurfaceView.Renderer {

26   VertexTexture3DObjectForDraw button;   //按钮

27   VertexTexture3DObjectForDraw box;    //正方体

28   VertexTexture3DObjectForDraw bigCelestial;  //星空

29   VertexTexture3DObjectForDraw smallCelestial; //星空

30   float yAngle=0;

31   int boxId;

32  ……//此处省略了类似的声明纹理id的声明代码,读者可以自行查阅随书光盘中的源代码

33   VertexTextureNormal3DObjectForDraw[] lovntArray=new

34       VertexTextureNormal3DObjectForDraw[6];

35   Robot robot;        //机器人引用

36   MenuDoActionThread dat;     //菜单界面执行机器人动画线程引用

37  ……//此处省略了绘制方法,将在下面进行介绍

38   @Override

39   public void onSurfaceChanged(GL10 gl, int width, int height) {

40    gl.glViewport(

41       Constant.screenScaleResult.lucX,&nbsp;Constant.screenScaleResult.lucY,

42       (int)(Constant.screenWidthStandard*Constant.screenScale-Result.ratio),

43       (int)(Constant.screenHeightStandard*Constant.screenScale-Result.ratio)

44    );

45    Constant.ratio=Constant.screenWidthStandard/Constant.screenHeight-Standard;

46    gl.glEnable(GL10.GL_CULL_FACE);   //设置为打开背面剪裁

47   }

48 ……//此处省略了onSurfaceCreated方法,将在下面进行介绍

49 ……//该处省略了本类中初始化纹理的方法,需要的读者请自行查阅随书光盘中的源代码

50 }

· 第4-14行是各种引用的声明与标志位的创建。第15-23行是类构造器,其中创建了主控制类对象,创建并设置了渲染器。

· 第 26-37 行是声明并初始化开发过程中用到的物体对象及纹理 id。第 38-47 行是回调方法onSurfaceChanged,其在画布改变时调用。

(2)接下来为读者介绍的是绘制方法onDrawFrame,其代码如下

代码位置:见随书光盘中源代码/第6章/TXZ/src/com/bn/txz目录下的TXZMenuView.java。

1public void onDrawFrame(GL10 gl) {

2 //清除深度缓存与颜色缓存

3 gl.glClear(GL10.GL_DEPTH_BUFFER_BIT|GL10.GL_COLOR_BUFFER_BIT);

4 gl.glMatrixMode(GL10.GL_PROJECTION);   //设置当前矩阵为投影矩阵

5 gl.glLoadIdentity();       //设置当前矩阵为单位矩阵

6 gl.glFrustumf(-ratio, ratio, -1, 1, 1, 100); //调用此方法计算产生透视投影矩阵

7 gl.glMatrixMode(GL10.GL_MODELVIEW);   //设置当前矩阵为模式矩阵

8 gl.glLoadIdentity();       //设置当前矩阵为单位矩阵

9 GLU.gluLookAt (        //可能变形的视角——大视角

10     gl, 0f, 5f, 5f,    //人眼位置

11     0, 5f, 0,      //人眼球看的点

12     0, 1, 0 );     //up向量

13  synchronized(gdDraw.dataLock) {   //将绘制数据复制到临时数据

14   gdDraw.copyTo(gdTemp);

15  }

16  gl.glPushMatrix();

17  gl.glRotatef(yAngle, 0, 1, 0);

18  bigCelestial.drawSelf(gl);     //绘制星空

19  smallCelestial.drawSelf(gl);

20  gl.glPopMatrix();

21  gl.glPushMatrix();

22  gl.glTranslatef(Constant.robotXstar, Constant.robotYstar, Constant.robotZstar);

23  robot.drawSelfAnother(gl);     //绘制机器人

24  gl.glPopMatrix();

25  gl.glPushMatrix();

26  gl.glTranslatef(Constant.xOffset, 2, -8.5f);

27  gl.glScalef(4, 4, 4);

28  box.drawSelf(gl, boxId);     //绘制箱子

29  gl.glPopMatrix();

30  gl.glMatrixMode(GL10.GL_PROJECTION);  //设置投影矩阵

31  gl.glLoadIdentity();      //设置当前矩阵为单位矩阵

32  gl.glOrthof(-ratio, ratio, bottom, top, near, far);//调用此方法计算产生正交投影矩阵

33  GLU.gluLookAt(         //设置摄像机

34    gl, 0,0,10,

35    0,0,0,

36    0,1,0 );

37  gl.glMatrixMode(GL10.GL_MODELVIEW);  //设置模式矩阵

38  gl.glLoadIdentity();      //设置当前矩阵为单位矩阵

39  if(color) {

40    gl.glPushMatrix();

41    gl.glTranslatef(1.3f, 0.8f, 0.1f);

42    gl.glRotatef(-anglet, 0, 1, 0);

43    gl.glRotatef(-anglex, 1, 0, 0);

44    button.drawSelf(gl, start);  //开始游戏

45    gl.glPopMatrix();

46   ……//此处省略了相似的绘制代码,读者可以自行查阅随书光盘中的源代码

47    gl.glPushMatrix();

48    gl.glTranslatef(1.3f, -0.825f, 0.1f);

49    gl.glRotatef(-anglet, 0, 1, 0);

50    gl.glRotatef(-anglex, 1, 0, 0);

51    button.drawSelf(gl,exit);   //退出

52    gl.glPopMatrix();

53  }else{

54  ……//此处省略了与if语句块中相似的绘制代码,读者可以自行查阅随书光盘中的源代码

55 }}

· 第3-5行清除深度缓存与颜色缓存、设置当前矩阵为投影矩阵并将当前矩阵设置为单位矩阵。

· 第 6-8 行是设置为透视投影、设置当前矩阵为模式矩阵并将当前矩阵设置为单位矩阵。第9-12行是设置摄像机的位置。

· 第13-15行是将绘制数据复制到临时数据。第16-29行是绘制星空、机器人和箱子。

· 第30-38行是设置为正交投影、设置摄像机、设置当前矩阵为模式矩阵,并将当前矩阵设置为单位矩阵。第39-54行是根据颜色的不同绘制不同纹理id的按钮。

(3)接下来介绍的是方法onSurfaceCreated的开发,其在画布创建时被调用,其代码如下。

代码位置:见随书光盘中源代码/第6章/TXZ/src/com/bn/txz目录下的TXZMenuView.java。

1@Override

2public void onSurfaceCreated(GL10 gl, EGLConfig config) {

3  gl.glDisable(GL10.GL_DITHER);    //关闭抗抖动

4  gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,

5   GL10.GL_FASTEST);      //设置特定Hint项目的模式

6  gl.glClearColor(0, 0, 0, 0);    //设置屏幕背景色黑色RGBA

7  gl.glEnable(GL10.GL_DEPTH_TEST);   //打开深度检测

8  gl.glDisable(GL10.GL_CULL_FACE);   //设置为打开背面剪裁

9  gl.glShadeModel(GL10.GL_SMOOTH);   //设置着色模型为平滑着色

10   Constant.menu_flag=true;    //置menu_flag为true

11   Constant.boxFlag=false;    //置boxFlag为false

12   Constant.xOffset=-9f;     //初始化箱子的位置

13   Constant.robotXstar=-15.5f;   //初始化机器人的位置

14   Constant.robotYstar=0;

15   Constant.robotZstar=-8.5f;

16   armTexId=initTexture(gl, PicDataManager.picDataArray[4]);//机器人其他部分纹理id

17   headTexId=initTexture(gl, PicDataManager.picDataArray[3]); //机器人头纹理id

18   lovntArray[0]=new VertexTextureNormal3DObjectForDraw( //身体

19     VertexDataManager.vertexPositionArray[17],  //顶点位置数据

20     VertexDataManager.vertexTextrueArray[17],  //顶点纹理数据

21     VertexDataManager.vertexNormalArray[17],  //顶点法向量数据

22     VertexDataManager.vCount[17],     //顶点数

23     armTexId          //纹理id

24   );

25  ……//此处省略了类似的初始化lovntArray[1]到lovntArray[5]代码,

26    //需要的读者可自行查阅本书随书光盘中源代码

27  robot=new Robot(lovntArray,TXZMenuView.this);   //初始化机器人

28  dat=new MenuDoActionThread(robot,TXZMenuView.this); //创建线程

29  MenuDoActionThread.currActionIndex=0;   //置currActionIndex为0

30  MenuDoActionThread.currStep=0;     //置currStep为0

31  dat.start();         //启动线程

32  boxId=initTexture(gl,PicDataManager.picDataArray[11]); //初始化箱子纹理id

33 ……//此处省略了类似的初始化纹理的代码,需要的读者可自行查阅本书随书光盘中源代码

34  helpl=initTexture(gl, PicDataManager.picDataArray[49]); //退出纹理id

35  button=new VertexTexture3DObjectForDraw(    //按钮

36     VertexDataManager.vertexPositionArray[15], //顶点坐标数据

37     VertexDataManager.vertexTextrueArray[15], //纹理坐标

38     VertexDataManager.vCount[15]     //顶点数

39   );

40  ……//此处省略了类似的箱子与大小星空对象初始化的代码,需要的读者

41   //可自行查阅本书随书光盘中源代码

42  new Thread(){

43   public void run(){

44    while(Constant.menu_flag) {

45     if(flagn) {     //若flagn为true则更改anglet值

46       anglet+=1f;    //若anglet小于25则加1

47       if(anglet>=25) {

48         flagn=false;  //置flagn为false

49       }

50      }else{      //若anglet大于25则减1

51       anglet-=1f;

52       if(anglet<=-25) {  //置flagn为true

53        flagn=true;

54     } }

55  ……//此处省略了类似的anglex改变的代码,需要的读者可自行查阅本书随书光盘中源代码

56     try {

57        Thread.sleep(90); //线程休眠指定时间

58     } catch (InterruptedException e) {

59        e.printStackTrace();

60   }}}}.start();       //启动线程

61   new Thread(){

62    public void run(){

63    while(Constant.menu_flag) {

64     color=!color;       //更改color值

65     try{

66       Thread.sleep(500);    //线程休眠指定时间

67     }catch(Exception e) {

68       e.printStackTrace();

69   }}}}.start();

70   new Thread(){

71    public void run(){

72     while(Constant.menu_flag) {

73       yAngle+=0.5f;      //更改yAngle的值

74       if(yAngle>=360) {

75        yAngle=0;

76       }

77       try{

78        Thread.sleep(100);

79       }catch(InterruptedException e) {

80        e.printStackTrace();

81   }}}}.start();

82 }}

· 第3-6行是关闭抗抖动、设置特定Hint项目的模式,以及设置屏幕背景色黑色RGBA。第7-9行是打开深度检测、打开背面剪裁,以及设置着色模式为平滑着色。

· 第10-15行是设置箱子运动标志位为false、设置机器人运动标志位为true,同时设置了箱子,以及机器人的初始位置。

· 第16-41行是初始化了开发过程中用到的纹理id及物体对象,同时创建并启动了菜单界面机器人运动的线程。

· 第42-60行是根据标志位及anglet值进行对anglet值的改变。第61-69行是根据标志位的不同更改颜色标志位。第70-81行是根据标志位更改yAngle的值。

(4)下面为读者介绍的是监听触控的方法 onTouchEvent 的开发,其是根据触控的位置判断是否按下按钮,其代码如下。

代码位置:见随书光盘中源代码/第6章/TXZ/src/com/bn/txz目录下的TXZMenuView.java。

1public boolean onTouchEvent(MotionEvent e) {

2  int x=(int)e.getX();

3  int y=(int)e.getY();

4  switch(e.getAction()){

5  case MotionEvent.ACTION_DOWN:

6   if(x>=(Constant.Menu_Start_l+Constant.screenScaleResult.lucX)*

7      Constant.screenScaleResult.ratio&&

8      x<=(Constant.Menu_Start_r+Constant.screenScaleResult.lucX)*

9      Constant.screenScaleResult.ratio&&

10       y>=(Constant.Menu_Start_u+Constant.screenScaleResult.lucY)*

11       Constant.screenScaleResult.ratio&&

12       y<=(Constant.Menu_Start_d+Constant.screenScaleResult.lucY)*

13       Constant.screenScaleResult.ratio) {//按下开始游戏按钮

14     activity.gotoGameView();

15     Constant.menu_flag=true;

16     Constant.boxFlag=false;

17     Constant.MENU_IS_WHILE=false;

18    }

19  ……//此处省略了类似的单击设置、选关、关于、帮助按钮的处理代码,

20     //需要的读者可自行查阅本书随书光盘中源代码

21    else if(x>=(Constant.Menu_Quit_l+Constant.screenScaleResult.lucX)*

22       Constant.screenScaleResult.ratio&&

23       x<=(Constant.Menu_Quit_r+Constant.screenScaleResult.lucX)*

24       Constant.screenScaleResult.ratio&&

25       y>=(Constant.Menu_Quit_u+Constant.screenScaleResult.lucY)*

26       Constant.screenScaleResult.ratio&&

27       y<=(Constant.Menu_Quit_d+Constant.screenScaleResult.lucY)*

28       Constant.screenScaleResult.ratio) {//单击退出游戏按钮

29     activity.stopBeiJingYinYue();

30     Constant.menu_flag=false;

31     Constant.boxFlag=false;

32     Constant.MENU_IS_WHILE=false;

33    System.exit(0);

34    }

35    break;

36   case MotionEvent.ACTION_MOVE:

37    break;

38   case MotionEvent.ACTION_UP:

39    break;

40   }

41   return true;

42  }

说明

该方法的开发非常简单,其实现的功能是根据触控点的位置判断其按下的位置是否为指定的按钮位置,若是该按钮则实现相应按下按钮之后的功能。

(5)接下来为读者介绍的是该类用到的机器人的部件类MenuBodyPart,其代码如下。

代码位置:见随书光盘中源代码/第6章/TXZ/src/com/bn/txz目录下的MenuBodyPart.java。

1package com.bn.txz;            //声明包

2……//此处省略了本类中导入类的代码,读者可以自行查阅随书光盘中的源代码

3public class MenuBodyPart {

4 VertexTextureNormal3DObjectForDraw lovnt;     //绘制者

5 int index;             //部件索引

6 TXZMenuView menu;

7 ArrayList<MenuBodyPart> childs=new ArrayList<MenuBodyPart>(); //子骨头列表

8 MenuBodyPart father;           //指向父骨骼的引用

9 //构造器的入口参数为子骨骼不动点在父坐标系中的坐标

10  public MenuBodyPart(float fx,float fy,float fz,VertexTextureNormal3DObject-ForDraw

11  lovnt,int index,TXZMenuView menu) {

12  this.index=index;

13  this.menu=menu;

14  //为子骨骼在初始坐标系中的不动点赋值

15  menu.gdMain.dataArray[index].bdd=new float[]{fx, fy, fz};

16  this.lovnt=lovnt;

17  }

18  public void drawSelf(GL10 gl) {

19   gl.glPushMatrix();

20   gl.glTranslatef (

21    menu.gdTemp.dataArray[index].py[0], //子骨骼在父骨骼坐标系中的平移

22    menu.gdTemp.dataArray[index].py[1],

23    menu.gdTemp.dataArray[index].py[2]

24   );

25   gl.glTranslatef (     //子骨骼在父骨骼坐标系中的旋转的辅助平移

26    menu.gdTemp.dataArray[index].pyfz[0],

27    menu.gdTemp.dataArray[index].pyfz[1],

28    menu.gdTemp.dataArray[index].pyfz[2]

29   );

30   gl.glRotatef (

31    menu.gdTemp.dataArray[index].xz[0], //子骨骼在父骨骼坐标系中的旋转

32    menu.gdTemp.dataArray[index].xz[1],

33    menu.gdTemp.dataArray[index].xz[2],

34    menu.gdTemp.dataArray[index].xz[3]

35   );

36   if(this.lovnt!=null) {

37    this.lovnt.drawSelf(gl);

38   }

39   for(MenuBodyPart bc:childs) {    //然后更新自己的所有孩子

40    bc.drawSelf(gl);

41   }

42   gl.glPopMatrix();

43  }

44  //在父坐标系中平移自己

45  public void transtate(float x,float y,float z) { //设置沿x、y、z轴移动

46   menu.gdMain.dataArray[index].py[0]=x;  //沿x轴移动距离

47   menu.gdMain.dataArray[index].py[1]=y;  //沿y轴移动距离

48   menu.gdMain.dataArray[index].py[2]=z;  //沿z轴移动距离

49  }

50  //在父坐标系中旋转自己

51  public void rotate(float angle,float x,float y,float z) { //设置绕x、y、z轴转动

52   menu.gdMain.dataArray[index].xz[0]=angle;   //旋转角度

53   menu.gdMain.dataArray[index].xz[1]=x;

54   menu.gdMain.dataArray[index].xz[2]=y;

55   menu.gdMain.dataArray[index].xz[3]=z;

56   float[] dot={          //不动点

57    menu.gdMain.dataArray[index].bdd[0],

58    menu.gdMain.dataArray[index].bdd[1],

59    menu.gdMain.dataArray[index].bdd[2],

60    1

61  };

62   float[] dotr=new float[4];

63   float[] mtemp=new float[16];

64   Matrix.setIdentityM(mtemp, 0);      //计算不动点位置后折返

65   Matrix.rotateM(mtemp, 0, angle, x, y, z);

66   Matrix.multiplyMV(dotr, 0, mtemp, 0, dot, 0);

67   menu.gdMain.dataArray[index].pyfz[0]=-dotr[0]+dot[0];

68   menu.gdMain.dataArray[index].pyfz[1]=-dotr[1]+dot[1];

69   menu.gdMain.dataArray[index].pyfz[2]=-dotr[2]+dot[2];

70  }

71  public void setFather(MenuBodyPart f) {    //设置本关节的父关节

72   this.father=f;

73  }

74  public MenuBodyPart getFather(){      //获得本关节的父关节

75   return father;

76  }

77  public void addChild(MenuBodyPart child) {   //添加本关节的子关节

78   childs.add(child);

79  }

80  public MenuBodyPart getChild(int index) {   //获得本关节的子关节

81   return childs.get(index);

82  }

83 }

· 第4-8行是声明开发过程中用到的对象引用和基本数据类型引用。第9-17行是该类的构造方法,在其他类创建该类对象时被调用。

· 第18-43行是绘制机器人方法,其实现的功能是先绘制父关节再绘制子关节。第44-49行是在父坐标系中平移自己。第50-70行是在父坐标系中旋转自己。

· 第50-70行是在父坐标系中旋转自己。第71-73行是设置本关节的父关节。第74-76行是获得本关节的父关节。第77-79行是设置本关节的子关节。第80-82行是获得本关节的子关节。

(6)最后为读者介绍的是本类中执行动作的线程类MenuDoActionThread,其代码如下。

代码位置:见随书光盘中源代码/第6章/TXZ/src/com/bn/txz目录下的MenuDoActionThread.java。

1package com.bn.txz;           //声明包

2……//此处省略了本类中导入类的代码,读者可以自行查阅随书光盘中的源代码

3public class MenuDoActionThread extends Thread{    //机器人动画

4 public static int currActionIndex=0;      //当前动作索引

5 public static int currStep=0;       //当前步骤

6 Action currAction;          //当前动作

7 Robot robot;            //机器人引用

8 TXZMenuView menu;          //菜单界面引用

9 public MenuDoActionThread(Robot robot,TXZMenuView menu) {

10  this.robot=robot;

11  this.menu=menu;

12  }

13  public void run(){         //重写run方法

14   try {

15    Thread.sleep(50);       //线程休眠指定的时间

16   } catch (InterruptedException e) {

17    e.printStackTrace();

18   }

19   currAction=ActionGenerator.acrobortArray[currActionIndex];//获得当前的动作

20   while(Constant.MENU_IS_WHILE) {

21    if(currStep>=currAction.totalStep) {

22     currActionIndex=(currActionIndex+1)% //更新currActionIndex

23     ActionGenerator.acrobortArray.length;

24     currAction=ActionGenerator.acrobortArray[currActionIndex];//更新currAction

25     currStep=0;        //更新currStep

26    }

27    if(currActionIndex==13||currActionIndex==25) { //更新箱子运动标志位

28     Constant.boxFlag=true;

29    }else{

30     Constant.boxFlag=false;

31    }

32    for(float[] ad:currAction.Robotdata) {   //修改主数据

33     int partIndex=(int) ad[0];     //部件索引

34     int aType=(int)ad[1];      //动作类型

35     if(aType==0) {        //平移

36       float xStart=ad[2];     //起始点x坐标

37       float yStart=ad[3];     //起始点y坐标

38       float zStart=ad[4];     //起始点z坐标

39       float xEnd=ad[5];      //终点x坐标

40       float yEnd=ad[6];      //终点y坐标

41       float zEnd=ad[7];      //终点z坐标

42      float currX=xStart+(xEnd-xStart)*currStep/currAction.totalStep;//当前x坐标

43       float currY=yStart+(yEnd-yStart)*currStep/currAction.totalStep;//当前y坐标

44      float currZ=zStart+(zEnd-zStart)*currStep/currAction.totalStep;//当前z坐标

45       //更新子骨骼在父骨骼坐标系中的平移

46       robot.bpArrayl[partIndex].transtate(currX, currY, currZ);

47       if(currActionIndex==13||currActionIndex==25) {

48       if(Constant.boxFlag) {

49        if(xEnd-xStart>0) {

50          Constant.xOffset=Constant.xOffset+10f/

51           currAction.totalStep;

52         }else if(xEnd-xStart<0) {

53         Constant.xOffset=Constant.xOffset-10f/

54           currAction.totalStep;

55     }}}}else if(aType==1) {      //旋转

56       float startAngle=ad[2];    //起始角度

57       float endAngle=ad[3];     //终止角度

58       float currAngle=startAngle+(endAngle-startAngle)*//当前角度

59          currStep/currAction.totalStep;

60       float x=ad[4];         //旋转轴

61       float y=ad[5];

62       float z=ad[6];

63       //更新子骨骼在父骨骼坐标系中的旋转

64       robot.bpArrayl[partIndex].rotate(currAngle, x, y, z); }

65    }

66    currStep++;        //当前步骤加1

67    synchronized(menu.gdDraw.dataLock) {  //将主数据复制进绘制数据

68     menu.gdMain.copyTo(menu.gdDraw);

69    }

70    try {

71     Thread.sleep(20);     //线程休眠指定的时间

72    } catch (InterruptedException e) {

73     e.printStackTrace();

74 }}} }

· 第4-8行是声明开发过程中用到的对象引用和基本数据类型引用。第9-12行是该类构造方法,其在其他类创建该类对象时被调用。

· 第19-31行是为机器人动画时用到的当前动作、当前步骤,以及标志位等赋值。第33-34行是获得部件索引和动作类型。

· 第35-54行是平移动作,其实现的功能是将机器人沿着指定的坐标轴进行平移。第55-65行是旋转动作,其实现的功能是将机器人绕着指定旋转轴旋转指定的角度。

设置界面是在菜单界面中按下“设置”按钮后进入的界面,在设置界面,玩家可以设置是否使用背景音乐与游戏音效。

(1)首先介绍的是设置界面主体框架的开发,开发人员需要根据界面的功能来设计设置界面类的方法,其代码如下。

代码位置:见随书光盘中源代码/第6章/TXZ/src/com/bn/txz目录下的TXZSetView.java。

1package com.bn.txz;           //声明包

2……//此处省略了本类中导入类的代码,读者可以自行查阅随书光盘中的源代码

3public class TXZSetView extends GLSurfaceView{

4 TXZActivity activity;         //Activity引用

5 private SceneRenderer mRenderer;      //场景渲染器

6 public GameData gdMain=new GameData();     //主数据

7 GameData gdDraw=new GameData();       //绘制数据

8 GameData gdTemp=new GameData();       //临时数据

9 boolean flagn=true;

10  float anglet=0;

11  float anglex=25;

12  boolean flagx=false;        //改变角度标志位

13  boolean color=false;        //颜色标志位

14  public TXZSetView(Context context) {

15   super(context);

16   this.activity=(TXZActivity)context;

17   mRenderer = new SceneRenderer();    //创建场景渲染器

18   setRenderer(mRenderer);       //设置渲染器

19   setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);

20  }

21 ……//此处省略了触控方法onTouchEvent,将在下面进行介绍

22  private class SceneRenderer implements GLSurfaceView.Renderer {

23   VertexTexture3DObjectForDraw button;   //按钮

24   int headTexId;         //头部纹理ID

25  ……//此处省略了类似的声明纹理id的声明代码,读者可自行查阅随书光盘中的源代码

26   float yAngle=0;

27   VertexTextureNormal3DObjectForDraw[] lovntArray=new //3D物体的引用

28   VertexTextureNormal3DObjectForDraw[6];

29   VertexTexture3DObjectForDraw bigCelestial;   //大星空

30   VertexTexture3DObjectForDraw smallCelestial;  //小星空

31   Robot robot;          //机器人引用

32   SetDoActionThread dat;        //执行动作线程引用

33 ……//此处省略了绘制方法,将在下面进行介绍

34 ……//此处省略了与菜单界面相似的onSurfaceChanged方法,读者可自行查看随书光盘中源代码

48 ……//此处省略了onSurfaceCreated方法,将在下面进行介绍

49 ……//该处省略了本类中初始化纹理的方法,需要的读者请自行查阅随书光盘中的源代码

50 }

说明

第4-13行是各种引用的声明。第14-20行是此类的构造器,其在其他类创建该类对象时调用,该构造器中创建并设置了渲染器。

(2)接下来为读者介绍的是方法onSurfaceCreated,其在画布创建时调用,其代码如下。

代码位置:见随书光盘中源代码/第6章/TXZ/src/com/bn/txz目录下的TXZSetView.java。

1@Override

2public void onSurfaceCreated(GL10 gl, EGLConfig config) {

3 gl.glDisable(GL10.GL_DITHER);      //关闭抗抖动

4 gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,

5   GL10.GL_FASTEST);       //设置为使用快速模式

6 gl.glClearColor(0, 0, 0, 0);      //设置屏幕背景色黑色RGBA

7 gl.glEnable(GL10.GL_DEPTH_TEST);     //打开深度检测

8 gl.glDisable(GL10.GL_CULL_FACE);     //设置为打开背面剪裁

9 gl.glShadeModel(GL10.GL_SMOOTH);     //设置着色模型为平滑着色

10  Constant.set_flag=true;

11  Constant.SET_IS_WHILE=true;

12  armTexId=initTexture(gl, PicDataManager.picDataArray[4]); //其他部分纹理id

13  headTexId=initTexture(gl, PicDataManager.picDataArray[3]); //机器人头纹理id

14 ……//此处省略了与菜单界面类似的初始化lovntArray[0]到lovntArray[5]代码,

15     //需要的读者可自行查阅本书随书光盘中源代码

16  robot=new Robot(lovntArray,TXZSetView.this);

17  dat=new SetDoActionThread(robot,TXZSetView.this);

18  dat.start();

19  back=initTexture(gl,PicDataManager.picDataArray[29]);

20 ……//此处省略了类似的初始化纹理的代码,需要的读者可自行查阅本书随书光盘中源代码

21  button=new VertexTexture3DObjectForDraw(   //输赢界面按钮

22    VertexDataManager.vertexPositionArray[15], //房子的顶点坐标数据

23    VertexDataManager.vertexTextrueArray[15], //房间纹理坐标

24    VertexDataManager.vCount[15]    //顶点数

25  );

26 ……//此处省略了与菜单界面相似的线程代码,需要的读者可自行查阅本书随书光盘中源代码

27 ……//此处省略了与菜单界面相似的大小星空初始化代码,读者可自行查阅本书随书光盘中源代码

28 ……//此处省略了与菜单界面相似的线程代码,需要的读者可自行查阅本书随书光盘中源代码

29 }}

· 第4-7行是设置了使用快捷方式、设置了屏幕背景颜色,同时打开深度检测。第8-9行是设置打开背面剪裁并设置着色模型为平滑着色。

· 第10-11行是设置相应的标志位为true。第12-25行是初始化开发过程中用到的纹理id和物体对象。

(3)接下来介绍的是界面的绘制方法onDrawFrame的开发,其代码如下。

代码位置:见随书光盘中源代码/第6章/TXZ/src/com/bn/txz目录下的TXZSetView.java。

1public void onDrawFrame(GL10 gl) {      //重写onDrawFrame方法

2 //清除深度缓存与颜色缓存

3 gl.glClear(GL10.GL_DEPTH_BUFFER_BIT|GL10.GL_COLOR_BUFFER_BIT);

4 gl.glMatrixMode(GL10.GL_PROJECTION);    //设置当前矩阵为投影矩阵

5 gl.glLoadIdentity();        //设置当前矩阵为单位矩阵

6 gl.glFrustumf(-ratio, ratio, -1, 1, 1, 100);  //调用此方法计算产生透视投影矩阵

7 gl.glMatrixMode(GL10.GL_MODELVIEW);     //设置当前矩阵为模式矩阵

8 gl.glLoadIdentity();        //设置当前矩阵为单位矩阵

9 GLU.gluLookAt (          //可能变形的视角——大视角

10    gl, 0f, 5f, 5f,      //人眼位置

11    0, 5f, 0,       //人眼球看的点

12    0, 1, 0        //up向量

20  );

21  synchronized(gdDraw.dataLock) {    //将绘制数据复制进临时数据

22   gdDraw.copyTo(gdTemp);

23  }

24  gl.glPushMatrix();

25  gl.glRotatef(yAngle, 0, 1, 0);

26  bigCelestial.drawSelf(gl);      //绘制星空

27  smallCelestial.drawSelf(gl);

28  gl.glPopMatrix();

29  gl.glPushMatrix();

30  gl.glTranslatef(-5f, 0, -2.5f);

31  robot.drawSelfSet(gl);       //绘制物体

32  gl.glPopMatrix();

33  gl.glMatrixMode(GL10.GL_PROJECTION);   //设置投影矩阵

34  gl.glLoadIdentity();       //设置当前矩阵为单位矩阵

35  gl.glOrthof(-ratio, ratio, bottom, top, near, far);//调用此方法计算产生正交投影矩阵

36  GLU.gluLookAt(         //设置摄像机

37    gl, 0,0,10,

39    0,0,0,

40    0,1,0

41  );

42  gl.glMatrixMode(GL10.GL_MODELVIEW);   //设置模式矩阵

43  gl.glLoadIdentity();       //设置当前矩阵为单位矩阵

44  if(color) {

45   gl.glPushMatrix();

46   gl.glTranslatef(0.6f, 0.6f, 0.1f);

47   gl.glRotatef(-anglet, 0, 1, 0);

48   gl.glRotatef(-anglex, 1, 0, 0);

49   button.drawSelf(gl, bgmusic);

50   gl.glPopMatrix();

51   if(Constant.IS_BEIJINGYINYUE) {   //如果使用背景音乐

52     gl.glPushMatrix();

53     gl.glTranslatef(1.4f, 0.6f, 0.1f);

54     gl.glRotatef(-anglet, 0, 1, 0);

55    gl.glRotatef(-anglex, 1, 0, 0);

56     gl.glScalef(0.5f, 1f, 0.5f);

57     button.drawSelf(gl, off);    //绘制按钮

58     gl.glPopMatrix();

59   }else {

60  ……//此处省略了相似的绘制代码,读者可以自行查阅随书光盘中的源代码

61   }

62   gl.glPushMatrix();

63   gl.glTranslatef(0.6f, 0.2f, 0.1f);

64   gl.glRotatef(-anglet, 0, 1, 0);

65   gl.glRotatef(-anglex, 1, 0, 0);

66   button.drawSelf(gl, bgyouxi);

67   gl.glPopMatrix();

68   if(Constant.IS_YINXIAO) {     //如果使用音效

69     gl.glPushMatrix();

70     gl.glTranslatef(1.4f, 0.2f, 0.1f);

71     gl.glRotatef(-anglet, 0, 1, 0);

72     gl.glRotatef(-anglex, 1, 0, 0);

73     gl.glScalef(0.5f, 1f, 0.5f);

74     button.drawSelf(gl, off);

75     gl.glPopMatrix();

76   } else {

77  ……//此处省略了相似的绘制代码,读者可以自行查阅随书光盘中的源代码

78   }

79   gl.glPushMatrix();

80   gl.glTranslatef(1.2f, -0.5f, 0.1f);

81   gl.glRotatef(-anglet, 0, 1, 0);

82   gl.glRotatef(-anglex, 1, 0, 0);

83   button.drawSelf(gl, back);    //绘制返回按钮

84   gl.glPopMatrix();

85   }else{

86  ……//此处省略了相似的绘制代码,读者可以自行查阅随书光盘中的源代码

87 }}

· 第2-5行是设置清除深度缓存与颜色缓存,并设置当前矩阵为投影矩阵和模式矩阵。第6-20行是设置为透视投影、设置当前矩阵为投影矩阵和模式矩阵,并设置了摄像机。

· 第21-23行是将绘制数据复制进临时数据。第24-32行是在透视投影下绘制3D物体。第44-86行是在正交投影下绘制按钮。

(4)接下来介绍的是此类中监听触控的方法 onTouchEvent 的开发,此方法中根据触控位置的不同,判断是否按下了按钮,其代码如下。

代码位置:见随书光盘中源代码/第6章/TXZ/src/com/bn/txz目录下的TXZSetView.java。

1public boolean onTouchEvent(MotionEvent e) {

2 int x=(int)e.getX();         //记录触控点的x坐标

3 int y=(int)e.getY();         //记录触控点的y坐标

4 switch(e.getAction()){

5  case MotionEvent.ACTION_DOWN:       //按下动作

6   if(x>=(Constant.Set_kai_1_l+Constant.screenScaleResult.lucX)*

7       Constant.screenScaleResult.ratio&& //按下背景音乐的开关按钮

8      x<=(Constant.Set_kai_1_r+Constant.screenScaleResult.lucX)*

9       Constant.screenScaleResult.ratio&&

10       y>=(Constant.Set_kai_1_u+Constant.screenScaleResult.lucY)*

11        Constant.screenScaleResult.ratio&&

12       y<=(Constant.Set_kai_1_d+Constant.screenScaleResult.lucY)*

13        Constant.screenScaleResult.ratio) {

14     Constant.IS_BEIJINGYINYUE=!Constant.IS_BEIJINGYINYUE;

15     if(Constant.IS_BEIJINGYINYUE) {

16       activity.playBeiJingYinYue();  //播放音乐

17     }else{

18       activity.stopBeiJingYinYue();  //关闭音乐

19    }}else if(x>=(Constant.Set_kai_2_l+Constant.screenScaleResult.lucX)*

20       Constant.screenScaleResult.ratio&& //按下游戏音效的开关按钮

21       x<=(Constant.Set_kai_2_r+Constant.screenScaleResult.lucX)*

22       Constant.screenScaleResult.ratio&&

23       y>=(Constant.Set_kai_2_u+Constant.screenScaleResult.lucY)*

24       Constant.screenScaleResult.ratio&&

25       y<=(Constant.Set_kai_2_d+Constant.screenScaleResult.lucY)*

26       Constant.screenScaleResult.ratio) {

27     Constant.IS_YINXIAO=!Constant.IS_YINXIAO;

28    }else if(x>=(Constant.Set_back_l+Constant.screenScaleResult.lucX)*

29       Constant.screenScaleResult.ratio&& //按下返回按钮

30       x<=(Constant.Set_back_r+Constant.screenScaleResult.lucX)*

31       Constant.screenScaleResult.ratio&&

32       y>=(Constant.Set_back_u+Constant.screenScaleResult.lucY)*

33       Constant.screenScaleResult.ratio&&

34       y<=(Constant.Set_back_d+Constant.screenScaleResult.lucY)*

35       Constant.screenScaleResult.ratio) {

36     activity.gotoMenuView();

37     Constant.SET_IS_WHILE=false;  //置SET_IS_WHILE为false

38     Constant.set_flag=false;   //置set_flag为false

39    }

40    break;

41  case MotionEvent.ACTION_MOVE:

42    break;

43  case MotionEvent.ACTION_UP:

44    break;

45   }

46   return true;

47 }

说明

该方法的开发非常简单,其实现的功能是判断是否按下指定的按钮,若是则执行相应的功能代码。由于 SetBodyPart 和 SetDoActionThread 与菜单界面的MenuBodyPart和MenuDoActionThread非常相似,因此笔者不再一一赘述,需要的读者请自行查阅本书随书光盘中的源代码。

选关界面是在菜单界面中按下“选关”按钮后进入的界面,选关界面绘制了本游戏中所有的关卡,若关卡已解锁,按下相应的关卡则进入相应关的游戏。

(1)首先为读者介绍的是选关界面主体框架的开发,开发人员需要根据界面的功能来设计菜单界面类的方法,其代码如下。

代码位置:见随书光盘中源代码/第6章/TXZ/src/com/bn/txz目录下的TXZSelectView.java。

1package com.bn.txz;            //声明包

2……//此处省略了本类中导入类的代码,读者可以自行查阅随书光盘中的源代码

3public class TXZSelectView extends GLSurfaceView{

4 public SceneRenderer mRenderer;

5 float x,y;             //触控点的x与y坐标

6 boolean flagn=true;

7 float anglet=0;

8 float anglex=25;

9 boolean flagx=false;

10  boolean color=false;

11  public GameData gdMain=new GameData();     //主数据

12  GameData gdDraw=new GameData();       //绘制数据

13  GameData gdTemp=new GameData();       //临时数据

14  int count=0;

15  TXZActivity activity;

16  public TXZSelectView(TXZActivity activity) {

17   super(activity);

18   this.activity=activity;

19   Constant.SET_IS_WHILE=true;

20   this.count=activity.sharedUtil.getPassNum();

21   mRenderer = new SceneRenderer();

22   setRenderer(mRenderer);        //设置渲染器

23   setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);//设置为主动渲染

24  }

25 ……//此处省略了触控方法onTouchEvent,将在下面进行介绍

26  private class SceneRenderer implements GLSurfaceView.Renderer{

27   VertexTexture3DObjectForDraw bgButton;    //按钮

28   VertexTexture3DObjectForDraw bigCelestial;   //星空

29   VertexTexture3DObjectForDraw smallCelestial;  //星空

30   Robot robot;

31   //从obj文件中加载的3D物体的引用

32   VertexTextureNormal3DObjectForDraw[] lovntArray=new

33     VertexTextureNormal3DObjectForDraw[6];

34   SelectDoActionThread dat;

35   float yAngle=0;

36   int bgButtonId[]=new int[9];      //关卡按钮纹理id

37   int bgButtonBack;         //返回按钮纹理id

38   int bgButtonBackl;

39   int suoId;

40   int headTexId;          //头部纹理id

41   int armTexId;          //其他部位纹理id

42  ……//此处省略了绘制方法,将在下面进行介绍

43   @Override

44   public void onSurfaceChanged(GL10 gl, int width, int height) {

45    gl.glViewport(

46       Constant.screenScaleResult.lucX,Constant.screenScaleResult.lucY,

47(int)(Constant.screenWidthStandard*Constant.screenScaleResult.ratio),

48(int)(Constant.screenHeightStandard*Constant.screenScaleResult.ratio)

49    );

50Constant.ratio=Constant.screenWidthStandard/Constant.screenHeightStandard;

51   gl.glEnable(GL10.GL_CULL_FACE);    //设置为打开背面剪裁

52   }

53  ……//此处省略了onSurfaceCreated方法,将在下面进行介绍

54   public void initTexId(GL10 gl) {    //初始化纹理id

55    bgButtonId[0]=initTexture(gl, PicDataManager.picDataArray[21]);//关卡1按钮纹理id

56   ……//该处省略了类似的初始化纹理id的代码,需要的读者请自行查

57     //阅随书光盘中的源代码

58   }

59  ……//该处省略了本类中初始化纹理的方法,需要的读者请自行查阅随书光盘中的源代码

60 }}

· 第4-15行是声明或初始化开发过程中用到的各种引用。第16-24行是该类的构造方法,其在其他类创建该类对象时被调用。

· 第27-41行是声明3D界面用到的物体对象引用和开发过程中用到的纹理id的引用。第43-52行是重写方法onSurfaceChanged,在该方法中设置了视口大小并计算了宽高比。第54-58行是初始化纹理id的方法。

(2)接下来为读者介绍的是绘制方法onDrawFrame的开发,其代码如下。

代码位置:见随书光盘中源代码/第6章/TXZ/src/com/bn/txz目录下的TXZSelectView.java。

1@Override

2public void onDrawFrame(GL10 gl) {     //重写onDrawFrame方法

3 //清除深度缓存与颜色缓存

4 gl.glClear(GL10.GL_DEPTH_BUFFER_BIT|GL10.GL_COLOR_BUFFER_BIT);

5 gl.glMatrixMode(GL10.GL_PROJECTION);    //设置当前矩阵为投影矩阵

6 gl.glLoadIdentity();        //设置当前矩阵为单位矩阵

7 gl.glFrustumf(-ratio, ratio, bottom, top, near, far); //调用此方法计算产生透视投影矩阵

8 gl.glMatrixMode(GL10.GL_MODELVIEW);     //设置当前矩阵为模式矩阵

9 gl.glLoadIdentity();        //设置当前矩阵为单位矩阵

10  GLU.gluLookAt(         //设置摄像机

11     gl,

12     0f, 5f, 5f,      //人眼位置

13     0, 5f, 0,       //人眼球看的点

14     0, 1, 0       //up向量

15  );

16  synchronized(gdDraw.dataLock) {    //将绘制数据复制进临时数据

17    gdDraw.copyTo(gdTemp);

18  }

19  gl.glPushMatrix();

20  gl.glRotatef(yAngle, 0, 1, 0);

21  bigCelestial.drawSelf(gl);      //绘制星空

22  smallCelestial.drawSelf(gl);

23  gl.glPopMatrix();

24  gl.glPushMatrix();

25  gl.glTranslatef(-4f, 0, -5.5f);

26  robot.drawSelfSelect(gl);      //绘制物体

27  gl.glPopMatrix();

28  gl.glMatrixMode(GL10.GL_PROJECTION);   //设置投影矩阵

29  gl.glLoadIdentity();       //设置当前矩阵为单位矩阵

30  gl.glOrthof(-ratio, ratio, bottom, top, near, far);//调用此方法计算产生正交投影矩阵

31  GLU.gluLookAt (        //设置摄像机

32    gl, 0,0,10,

33    0,0,0,

34    0,1,0

35  );

36  gl.glMatrixMode(GL10.GL_MODELVIEW);    //设置模式矩阵

37  gl.glLoadIdentity();       //设置当前矩阵为单位矩阵

38  gl.glEnable(GL10.GL_BLEND);      //开启混合

39  //设置源混合因子与目标混合因子

40  gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);

41  for(int i=0;i<9;i++){

42   if(i<count) {

43    if(i==0){

44     gl.glPushMatrix();

45    gl.glTranslatef(0.12f, 0.6f, 0.1f);

46     bgButton.drawSelf(gl,bgButtonId[i] );

47    gl.glPopMatrix();

48    }

49  ……//此处省略了相似的处理i==1到i==8时的代码,读者可自行查阅随书光盘中的源代码

50   gl.glDisable(GL10.GL_BLEND);     //开启混合

51   if(color) {

52   gl.glPushMatrix();

53   gl.glTranslatef(1.2f, -0.65f, 0.1f);

54   gl.glRotatef(-anglet, 0, 1, 0);

55   gl.glRotatef(-anglex, 1, 0, 0);

56   bgButton.drawSelf(gl, bgButtonBack);

57   gl.glPopMatrix();

58   }else{

59  ……//此处省略了相似的绘制代码,读者可以自行查阅随书光盘中的源代码

60 }}

· 第3-6行是清除深度缓存颜色缓存并设置当前矩阵为模式矩阵和单位矩阵。第7-9行是设置透视投影矩阵,并设置当前矩阵为模式矩阵和单位矩阵。第10-15行是设置摄像机。

· 第16-18行是将绘制数据复制进临时数据。第19-27行是绘制3D场景中的物体。第28-30行设置当前矩阵为模式矩阵和单位矩阵并且设置为正交投影。

· 第31-35行是设置摄像机。第38-40行是设置为允许混合,并设置源混合因子和目标混合因子。第41-59行是绘制选关界面中的关卡及返回按钮。

(3)下面为读者介绍的是方法onSurfaceCreated的开发,该方法在画布创建时被调用,其代码如下。

代码位置:见随书光盘中源代码/第6章/TXZ/src/com/bn/txz目录下的TXZSelectView.java。

1@Override

2public void onSurfaceCreated(GL10 gl, EGLConfig config) {

3 gl.glDisable(GL10.GL_DITHER);     //关闭抗抖动

4 gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,

5   GL10.GL_FASTEST);      //设置为使用快速模式

6 gl.glClearColor(0, 0, 0, 0);     //设置屏幕背景色黑色RGBA

7 gl.glEnable(GL10.GL_DEPTH_TEST);    //打开深度检测

8 gl.glDisable(GL10.GL_CULL_FACE);    //设置为打开背面剪裁

9 gl.glShadeModel(GL10.GL_SMOOTH);    //设置着色模型为平滑着色

10  initTexId(gl);        //初始化纹理

11  Constant.select_flag=true;

12  Constant.SELECT_IS_WHILE=true;

13  bgButton=new VertexTexture3DObjectForDraw(   //输赢界面按钮

14    VertexDataManager.vertexPositionArray[15], //房子的顶点坐标数据

15    VertexDataManager.vertexTextrueArray[15], //房间纹理坐标

16    VertexDataManager.vCount[15]    //顶点数

17  );

18 ……//此处省略了相似的星空与机器人部件对象初始化代码,读者可自行查阅随书光盘中的源代码

20 }

说明

该方法的开发非常简单,其实现的主要功能是,设置了画布的基本情况,并初始化了开发过程中用到的物体对象和纹理id。

(4)最后为读者介绍的是监听触控的方法 onTouchEvent 的开发,其是根据触控的位置判断是否按下按钮,然后执行相应的操作,其代码如下。

代码位置:见随书光盘中源代码/第6章/TXZ/src/com/bn/txz目录下的TXZSelectView.java。

1@Override

2public boolean onTouchEvent(MotionEvent event) {

3 x=event.getX();           //记录触控点的x坐标

4 y=event.getY();           //记录触控点的y坐标

5 switch(event.getAction()){

6  case MotionEvent.ACTION_DOWN:

7   if(count>=1&&x>=(Constant.Select_1_l+Constant.screenScaleResult.lucX)*

8      Constant.screenScaleResult.ratio&&

9      x<=(Constant.Select_1_r+Constant.screenScaleResult.lucX)*

10       Constant.screenScaleResult.ratio&&

11       y>=(Constant.Select_1_u+Constant.screenScaleResult.lucY)*

12       Constant.screenScaleResult.ratio

13       &&y<=(Constant.Select_1_d+Constant.screenScaleResult.lucY)*

14       Constant.screenScaleResult.ratio) {//按下第一关

15     com.bn.txz.game.GameData.level=1; //置关卡数为1

16     Constant.SELECT_IS_WHILE=false; //置SELECT_IS_WHILE为false

17     Constant.select_flag=false;  //置select_flag为false

18     activity.gotoGameView();   //返回游戏界面

19    }

20  ……//该处省略了相似的按下第2-9关的处理代码,读者可自行查阅随书光盘中的源代码

21    else if(x>=(Constant.Select_back_l+Constant.screenScaleResult.lucX)*

22       Constant.screenScaleResult.ratio&&

23       x<=(Constant.Select_back_r+Constant.screenScaleResult.lucX)*

24       Constant.screenScaleResult.ratio&&

25       y>=(Constant.Select_back_u+Constant.screenScaleResult.lucY)*

26       Constant.screenScaleResult.ratio&&

27       y<=(Constant.Select_back_d+Constant.screenScaleResult.lucY)*

28       Constant.screenScaleResult.ratio) { //单击返回按钮

29     Constant.SELECT_IS_WHILE=false; //置SELECT_IS_WHILE为false

30     Constant.select_flag=false;  //置select_flag为false

31     activity.gotoMenuView();   //返回到菜单界面

32    }

33   break;

34   case MotionEvent.ACTION_MOVE:

35   break;

36   case MotionEvent.ACTION_UP:

37   break;

38  }

39  return true;

40 }

说明

该方法的开发相当简单,其实现的功能是,根据按下区域的不同判断是否单击某个按钮,并且根据单击按钮的不同实现相应选关与返回的功能。

帮助界面是在菜单界面中单击“帮助”按钮后进入的界面,帮助界面绘制了本游戏的一些简单操作,可以帮助玩家更好地了解本游戏的操作。

(1)下面先介绍帮助界面主体框架的开发,开发人员需要根据界面的功能来设计菜单界面类的方法,其代码如下。

代码位置:见随书光盘中源代码/第6章/TXZ/src/com/bn/txz目录下的TXZHelpView.java。

1package com.bn.txz;           //声明包

2……//此处省略了本类中导入类的代码,读者可以自行查阅随书光盘中的源代码

3public class TXZHelpView extends GLSurfaceView{

4 TXZActivity activity;         //Activity引用

5 private SceneRenderer mRenderer;      //场景渲染器

6 boolean isLoadedOk=false;        //是否加载完成标志位

7 boolean inLoadView=true;        //是否在加载界面标志位

8 private int load_step=0;        //进度条步数

9 VertexTexture3DObjectForDraw laodBack;     //加载界面背景图

10  VertexTexture3DObjectForDraw processBar;   //加载界面中的进度条矩形

11  VertexTexture3DObjectForDraw loading;    //加载界面中文字矩形

12  public TXZHelpView(TXZActivity activity) {

13   super(activity);

14   this.activity=activity;

15   mRenderer = new SceneRenderer();    //创建场景渲染器

16   setRenderer(mRenderer);

17   setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);//渲染模式为主动渲染

18  }

19 ……//此处省略了触控方法onTouchEvent,将在下面进行介绍

20  private class SceneRenderer implements GLSurfaceView.Renderer {

21   VertexTexture3DObjectForDraw helpback;

22   VertexTexture3DObjectForDraw button;

23   int helpId[]=new int[10];

24   int prepageId;

25   int nextpageId;

26   int loadBackId;       //加载界面背景矩形纹理

27   int processBeijing;      //加载界面进度条矩形背景

28   int tex_processId;       //进度条

29   int loadId;

30   private boolean isFirstFrame=true;

31   @Override

32   public void onDrawFrame(GL10 gl) {

33    //清除深度缓存与颜色缓存

34    gl.glClear(GL10.GL_DEPTH_BUFFER_BIT|GL10.GL_COLOR_BUFFER_BIT);

35    if(!isLoadedOk) {      //如果加载未完成

36     inLoadView=true;

37     drawLoadingView(gl);

38    }else{        //如果加载已完成

39     inLoadView=false;

40     drawHelpView(gl);

41   }}

42   @Override

43   public void onSurfaceChanged(GL10 gl, int width, int height) {

44    gl.glViewport(

45       Constant.screenScaleResult.lucX, Constant.screenScaleResult.lucY,

46       (int)(Constant.screenWidthStandard*Constant.screenScale-Result.ratio),

47       (int)(Constant.screenHeightStandard*Constant.screenScale-Result.ratio)

48    );

49    Constant.ratio=Constant.screenWidthStandard/Constant.screenHeight-Standard;

50    gl.glEnable(GL10.GL_CULL_FACE);   //设置为打开背面剪裁

51   }

52  ……//此处省略了onSurfaceCreated方法,将在下面进行介绍

53  ……//该处省略了本类中初始化纹理的方法,需要的读者请自行查阅随书光盘中的源代码

54  ……//此处省略了drawLoadingView方法,需要的读者请自行查阅随书光盘中的源代码

55  ……//此处省略了loadResource方法,需要的读者请自行查阅随书光盘中的源代码

56  ……//此处省略了drawHelpView方法,将在下面进行介绍

57  }

58 }

· 第4-11行是声明开发过程中用到的各个对象的引用。第12-18行为此类的构造器,其中创建了渲染器,并设置了渲染模式。第21-29行是声明开发过程中用到的3D物体对象的引用和纹理id。

· 第32-41行是重写的onDrawFrame方法,在该方法中根据资源是否加载完成的标志位来绘制不同的界面。第42-51行是重写的onSurfaceChanged方法,在该方法中设置了视口的大小,并计算了宽高比。

(2)接下来介绍监听触控的方法onTouchEvent,其代码如下。

代码位置:见随书光盘中源代码/第6章/TXZ/src/com/bn/txz目录下的TXZHelpView.java。

1@Override

2public boolean onTouchEvent(MotionEvent event) {   //重写onTouchEvent方法

3 float x=event.getX();         //记录触控点的x坐标

4 float y=event.getY();         //记录触控点的y坐标

5 switch(event.getAction()){

6  case MotionEvent.ACTION_DOWN:

7  if(x<(Constant.Help_pre_r+Constant.screenScaleResult.lucX)*

8     Constant.screenScaleResult.ratio

9     &&x>(Constant.Help_pre_l+Constant.screenScaleResult.lucX)*

10     Constant.screenScaleResult.ratio

11     &&y<(Constant.Help_pre_b+Constant.screenScaleResult.lucY)*

12     Constant.screenScaleResult.ratio

13     &&y>(Constant.Help_pre_t+Constant.screenScaleResult.lucY)*

14     Constant.screenScaleResult.ratio) { //单击左边按钮

15    if(idKey==0) {

16     activity.gotoMenuView();    //返回菜单界面

17     idKey=0;        //如果是第一页

18    }else{

19     idKey=idKey-1;      // idKey为idKey减1

20   }}

21   if(x<(Constant.Help_next_r+Constant.screenScaleResult.lucX)*

22     Constant.screenScaleResult.ratio

23     &&x>(Constant.Help_next_l+Constant.screenScaleResult.lucX)*

24     Constant.screenScaleResult.ratio

25     &&y<(Constant.Help_next_b+Constant.screenScaleResult.lucY)*

26     Constant.screenScaleResult.ratio

27     &&y>(Constant.Help_next_t+Constant.screenScaleResult.lucY)*

28     Constant.screenScaleResult.ratio) { //单击右边按钮

29    if(idKey==9) {        //如果是最后一页

30     activity.gotoMenuView();    //返回菜单界面

31     idKey=0;        //置idKey为0

32    }else{

33     idKey=idKey+1;      // idKey为idKey加1

34   }}

35   break;

36  }

37  return true;

38 }

说明

该方法的开发非常简单,其实现的功能是:根据获取的触控位置判断单击的是否是按钮的位置,单击的若是按钮的位置则根据单击按钮的不同来更改相应的idKey值,若是idKey等于0时按下上一张;或idKey等于9时按下下一张,则界面跳转到菜单界面。

(3)接下来介绍监听触控的方法绘制界面的方法onSurfaceCreated,其代码如下。

代码位置:见随书光盘中源代码/第6章/TXZ/src/com/bn/txz目录下的TXZHelpView.java。

1@Override

2public void onSurfaceCreated(GL10 gl, EGLConfig config) {

3  gl.glDisable(GL10.GL_DITHER);     //关闭抗抖动

4  gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,

5     GL10.GL_FASTEST);      //设置为使用快速模式

6  gl.glClearColor(0, 0, 0, 0);     //设置屏幕背景色黑色RGBA

7  gl.glEnable(GL10.GL_DEPTH_TEST);    //打开深度检测

8  gl.glDisable(GL10.GL_CULL_FACE);    //设置为打开背面剪裁

9  gl.glShadeModel(GL10.GL_SMOOTH);    //设置着色模型为平滑着色

10   loadBackId=initTexture(gl, PicDataManager.picDataArray[12]);//背景纹理id

11   processBeijing=initTexture(gl, PicDataManager.picDataArray[13]);//进度条背景纹理id

12   tex_processId=initTexture(gl, PicDataManager.picDataArray[14]);//进度条纹理id

13   loadId=initTexture(gl, PicDataManager.picDataArray[15]); //背景纹理id

14   laodBack=new VertexTexture3DObjectForDraw(    //背景矩形

15     VertexDataManager.vertexPositionArray[22],  //顶点坐标数据

16     VertexDataManager.vertexTextrueArray[22],  //纹理坐标

17     VertexDataManager.vCount[22]     //顶点数

18   );

19   processBar=new VertexTexture3DObjectForDraw(  //加载界面背景矩形

20     VertexDataManager.vertexPositionArray[11], //顶点坐标数据

21     VertexDataManager.vertexTextrueArray[11], //纹理坐标

22     VertexDataManager.vCount[11]    //顶点数

23   );

24   loading=new VertexTexture3DObjectForDraw(   //加载界面文字矩形

25     VertexDataManager.vertexPositionArray[12], //顶点坐标数据

26     VertexDataManager.vertexTextrueArray[12], //纹理坐标

27     VertexDataManager.vCount[12]    //顶点数

28  ); }

说明

该方法的开发非常简单,其实现的主要功能是设置了画布的基本情况,并初始化了开发过程中用到的物体对象和纹理id。

(4)最后为读者介绍的是绘制帮助界面的方法drawHelpView,其代码如下。

代码位置:见随书光盘中源代码/第6章/TXZ/src/com/bn/txz目录下的TXZHelpView.java。

1public void drawHelpView(GL10 gl) {

2 gl.glMatrixMode(GL10.GL_PROJECTION);    //设置投影矩阵

3 gl.glLoadIdentity();        //设置当前矩阵为单位矩阵

4 gl.glOrthof(-ratio, ratio, bottom, top, near, far); //调用此方法计算产生正交投影矩阵

5 GLU.gluLookAt (          //设置摄像机

6     gl,

7     0,0,10,

8     0,0,0,

9     0,1,0

10  );

11  gl.glMatrixMode(GL10.GL_MODELVIEW);   //设置模式矩阵

12  gl.glLoadIdentity();       //设置当前矩阵为单位矩阵

13  helpback.drawSelf(gl, helpId[idKey]);   //绘制背景图

14  gl.glEnable(GL10.GL_BLEND);     //开启混合

15  //设置源混合因子与目标混合因子

16  gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);

17  gl.glPushMatrix();

18  gl.glTranslatef(-1.3f, -0.7f, 0.1f);

19  button.drawSelf(gl, prepageId);    //绘制向左的箭头

20  gl.glPopMatrix();

21  gl.glPushMatrix();

22  gl.glTranslatef(1.3f, -0.7f, 0.1f);

23  button.drawSelf(gl, nextpageId);    //绘制向右的箭头

24  gl.glPopMatrix();

25  gl.glDisable(GL10.GL_BLEND);     //关闭混合

26 }

说明

该方法的开发相当简单,其实现的功能是在正交投影下绘制帮助界面,帮助界面主要由一个大的背景图和两个图片按钮组成。

至此欢迎界面、菜单界面、选关界面和帮助界面介绍完毕,但是由于关于界面的开发与前面其他界面相比非常简单,因此,笔者不再一一赘述,需要的读者请自行查阅随书光盘中的源代码。

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

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

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

代码位置:见随书光盘中源代码/第6章/ TXZ/src/com/bn/txz/manager目录下的VertexDataManager.java。

1package com.bn.txz.manager;        //声明包

2……//此处省略了本类中导入类的代码,读者可以自行查阅随书光盘中的源代码

3public 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)完成本类主体框架的开发后,接下来为读者介绍的是初始化房间顶点数据的方法,其代码如下。

代码位置:见随书光盘中源代码/第6章/ TXZ/src/com/bn/txz/manager目录下的VertexDataManager.java。

1public 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)接下来介绍的是大星空顶点数据的初始化方法,星空是由一些随机生成的点组成的,其代码如下。

代码位置:见随书光盘中源代码/第6章/ TXZ/src/com/bn/txz/manager目录下的VertexDataManager.java。

1public 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)接下来为读者介绍的是游戏界面中软体箱子顶点数据的初始化方法,其代码如下。

代码位置:见随书光盘中源代码/第6章/ TXZ/src/com/bn/txz/manager目录下的VertexDataManager.java。

1public 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)和z'=xsin(currAngle)+zcos(currAngle),其中x和z为最底排顶点坐标中的x轴坐标和z轴坐标。

· 第53-62行是将顶点坐标数组和顶点纹理坐标数组中的数据分别存入相应的缓冲。

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

代码位置:见随书光盘中源代码/第6章/ TXZ /src/com/bn/txz/manager目录下的PicDataManager.java。

1package com.bn.txz.manager;     //声明包

2……//此处省略了本类中导入类的代码,读者可以自行查阅随书光盘中的源代码

3public 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中加载一幅纹理图。

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

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

代码位置:见随书光盘中源代码/第6章/ TXZ /src/com/bn/txz/game目录下的TXZGameSurfaceView.java。

1package com.bn.txz.game;       //声明包

2……//此处省略了本类中导入类的代码,读者可以自行查阅随书光盘中的源代码

3public 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)介绍完本类的主要框架后,接下来为读者介绍的是本类中的触控方法,首先介绍触控方法的框架,其代码如下。

代码位置:见随书光盘中源代码/第6章/ TXZ /src/com/bn/txz/game目录下的TXZGameSurfaceView.java。

1@Override

2public 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)介绍完触控方法的框架之后,接下来介绍的是上一步中省略的按下操作的代码,其代码如下。

代码位置:见随书光盘中源代码/第6章/ TXZ /src/com/bn/txz/game目录下的TXZGameSurfaceView.java。

1case 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)前面介绍了按下动作的处理代码,接下来介绍的是移动动作的处理代码的开发,其代码如下。

代码位置:见随书光盘中源代码/第6章/ TXZ /src/com/bn/txz/game目录下的TXZGameSurfaceView.java。

1case 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)介绍完移动动作处理代码的开发后,下面为读者介绍的是前面省略的抬起动作时处理代码的开发,其代码如下。

代码位置:见随书光盘中源代码/第6章/ TXZ /src/com/bn/txz/game目录下的TXZGameSurfaceView.java。

1case 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,首先介绍其中的重写方法,其代码如下。

代码位置:见随书光盘中源代码/第6章/ TXZ /src/com/bn/txz/game目录下的TXZGameSurfaceView.java。

1private 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)接下来为读者介绍的是游戏界面的绘制方法,首先介绍倒影的绘制,其代码如下。

代码位置:见随书光盘中源代码/第6章/ TXZ /src/com/bn/txz/game目录下的TXZGameSurfaceView.java。

1public 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)介绍完倒影的绘制后,接下来为读者介绍的是实体、摇杆和转换视角虚拟按钮的绘制,其代码如下。

代码位置:见随书光盘中源代码/第6章/ TXZ /src/com/bn/txz/game目录下的TXZGameSurfaceView.java。

1public 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行是调用方法判断游戏是否结束,并绘制结束界面的标志,由于篇幅所限,这里不再介绍,读者可查看随书光盘中的源代码。

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

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

代码位置:见随书光盘中源代码/第6章/ TXZ/src/com/bn/txz/game目录下的TXZDoActionThread.java。

1package com.bn.txz.game;       //声明包

2……//此处省略了本类中导入类的代码,读者可以自行查阅随书光盘中的源代码

3public 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)接下来为读者介绍的是上一步骤中省略的机器人前进动作的相应操作,其代码如下。

代码位置:见随书光盘中源代码/第6章/ TXZ/src/com/bn/txz/game目录下的TXZDoActionThread.java。

1case 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行是遇到的是箱子时的操作,判断箱子前面是什么,是否能走。

提示

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

为了实现程序中的功能,需要很多工具辅助方法,在开发时将相关的方法组织到一起就构成了一个个的工具类。工具类自己并不能实现什么具体的效果,但它是程序后台的支撑。

本小节为读者介绍的是箱子排序类,该类通过比较箱子与摄像机之间距离来确定箱子的绘制顺序,其代码如下。

代码位置:见随书光盘中源代码/第6章/ TXZ/src/com/bn/txz/manager目录下的CompareDis.java。

1package com.bn.txz.manager;       //声明包

2public class CompareDis implements Comparable<CompareDis>{

3 public float dis;

4 public int row;

5 public int col;

6 public CompareDis(float dis,int row,int col){ //创建CompareDis方法构造器

7  this.dis=dis;

8  this.row=row;

9  this.col=col;

10  }

11  @Override

12  public int compareTo(CompareDis another) { //需要从大到小排列,所以返回值相反

13   if(dis<another.dis)     //-1表示小于

14    return 1;

15   if(dis>another.dis)     //1表示大于

16    return -1;

17   return 0;

18  }}

说明

第3-5行是声明该类中的成员变量。第6-10行是该类的构造器,其在其他类创建该类对象时被调用。第11-18行是重写 ComparaTo方法,其实现的功能是根据比较的大小返回代表大于、小于或等于的代表数据。

上一小节为读者介绍了箱子排序类 CompareDis,本小节为读者介绍的是记录关卡工具类,其代码如下。

代码位置:见随书光盘中源代码/第6章/ TXZ/src/com/bn/txz/manager目录下的SharedPreferencesUtil.&nbsp;java。

1package com.bn.txz.manager;        //声明包

2……//此处省略了本类中导入类的代码,读者可以自行查阅随书光盘中的源代码

3public class SharedPreferencesUtil {

4 private SharedPreferences sp;      //声明成员变量

5 public SharedPreferencesUtil(TXZActivity activity) { //构造器

6  sp=activity.getSharedPreferences("playerPrefers", Context.MODE_PRIVATE);

7 }

8 public int getPassNum() {       //获取通过关卡方法

9  return sp.getInt("passNum", 1);

10  }

11  public void putPassNum(int value){    //存储通过关卡方法

12   Editor editor=sp.edit();     //创建对象

13   editor.putInt("passNum", value);

14   editor.commit();       //提交数据

15  }}

说明

第4行是声明该类中的成员变量。第5-7行是该类的构造方法。第8-10行是获取通过关卡方法。第11-15行是存储通过关卡方法。

本小节为读者介绍的是声音加载工具类,本类提供了将声音加载进内存的初始化声音的方法initSounds和播放声音的方法playSounds,其代码如下。

代码位置:见随书光盘中源代码/第6章/ TXZ/src/com/bn/txz/manager目录下的SoundUtil.java。

1package com.bn.txz.manager;         //声明包

2……//此处省略了本类中导入类的代码,读者可以自行查阅随书光盘中的源代码

3public class SoundUtil {

4 public static final int XUANZHONG=1;     //音效编号

5 public static SoundPool soundPool;      //声音缓冲池

6 public static HashMap<Integer, Integer> soundPoolMap; //存放声音id的Map

7 public static void initSounds(Context context) {   //声音缓冲池的初始化

8  soundPool = new SoundPool (      //创建声音缓冲池

9    2,           //同时能最多播放的个数

10     AudioManager.STREAM_MUSIC,    //音频的类型

11     100          //声音的播放质量,目前无效

12   );

13   soundPoolMap = new HashMap<Integer, Integer>(); //创建声音资源Map

14   //将加载的声音资源id放进此Map

15   soundPoolMap.put(XUANZHONG,soundPool.load(context,R.raw.zhuangsui,1));

16  }

17  public static void playSounds(int key, int loop,Context context){//播放声音的方法

18   AudioManager mgr = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);

19   float streamVolumeCurrent = mgr.    //当前音量

20    getStreamVolume(AudioManager.STREAM_MUSIC);

21   float streamVolumeMax = mgr.     //最大音量

22    getStreamMaxVolume(AudioManager.STREAM_MUSIC);

23   float volume = streamVolumeCurrent / streamVolumeMax;

24   soundPool.play(

25     soundPoolMap.get(key),    //声音id

26     volume,        //左声道

27     volume,        //右声道

28     1,         //优先级

29     loop,         //是否循环

30     0.5f         //rate

31     );

32  }}

说明

第4-6行分别声明了音效编号、声音缓冲池和存放声音id的Map。第7-16行为声音缓冲池的初始化方法,在该方法中创建了声音缓冲池和声音资源 Map,同时将加载的声音资源存到Map。第17-32行是播放指定id号声音的方法。

至此,本游戏的开发介绍完毕,虽然在开发过程中已经对游戏进行了很多的优化,但游戏中还是有一些不足,笔者在这里仅列出了几个方面。

· 游戏美工的改进

在游戏开发时,笔者已经对美工做了一些工作,让整体界面比较美观,但还是可以改进的,通过对游戏中的图案美化和布局,让游戏更具吸引力。

· 游戏玩法的改进

本游戏玩家可以选择想玩的关卡,但是由于本游戏的关卡并没有那么多、那么难,因此,读者可以根据自己的理解开发出不同挑战难度的游戏。

· 游戏的进一步优化。

虽然笔者已经对本游戏做了优化,但是在一些机型上运行速度还是比较慢,因此,读者可以对该游戏做进一步的优化,使游戏速度进一步提升。

相关图书

Android App开发入门与实战
Android App开发入门与实战
Kotlin入门与实战
Kotlin入门与实战
Android 并发开发
Android 并发开发
Android APP开发实战——从规划到上线全程详解
Android APP开发实战——从规划到上线全程详解
Android应用案例开发大全( 第4版)
Android应用案例开发大全( 第4版)
深入理解Android内核设计思想(第2版)(上下册)
深入理解Android内核设计思想(第2版)(上下册)

相关文章

相关课程