书名:libGDX移动游戏开发从入门到精通
ISBN:978-7-115-40799-3
本书由人民邮电出版社发行数字版。版权所有,侵权必究。
您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。
我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。
如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。
◆ 著 黄俊东
责任编辑 陈冀康
◆ 人民邮电出版社出版发行 北京市丰台区成寿寺路11号
邮编 100164 电子邮件 315@ptpress.com.cn
◆ 读者服务热线:(010)81055410
反盗版热线:(010)81055315
黄俊东,大众点评 客户端研发工程师。南京邮电大学毕业,曾就任于创新工场等大型公司,CSDN的博客专家。具有丰富的开发经验,具有3年Android平台上的开发经验,熟悉Java Web开发、Android开发和游戏开发。开发并发布过多个APP和游戏,如《Fashion Girls》等Google Play上较为火热的游戏,尤其对libGDX开发有深入的研究。
随着移动互联网的快速发展,移动端的游戏开发已经成为发展迅速、市场潜力巨大、前景诱人的开发方向。作为一种优秀的游戏引擎,libGDX让更多梦想加入游戏开发的人圆梦。本书全面介绍了使用libGDX进行游戏开发时所需要用到的知识,其中包括libGDX的详细介绍、libGDX的UI编程、libGDX的配套开发工具、libGDX 中所遇到的一些问题的解决方案。
本书并不局限于介绍libGDX编程的各种理论知识,而是从“项目驱动”的角度来讲授理论,全书给出了丰富的实例,这些示范性的实例既可帮助读者更好地理解各知识点在实际开发中的应用,也可供读者在实际开发时作为参考。本书最后还提供了两个实用的案例:捡金币项目和2048项目,具有很高的参考价值。
本书实用性强,内容详细,不仅适合有一定经验的开发者,对于那些没有经验的开发者来说,也是一本很好的入门指南。
从创新工场-涂鸦移动离职以后,我一直想把自己所学到的东西总结一下,以帮助更多想走上或者准备走上游戏开发这条道路上的人。很庆幸地接到了出版社的约稿,便愉快地决定要把自己一路走来所学到的知识做一个系统的总结,并尝试用一种更容易理解的方式把它们讲出来,以让更多的读者少走一些弯路,可以在尽可能短的时间内学到尽可能多的知识。
当我刚开始学libGDX游戏引擎的时候,对于很多自学的人来说,我应该是很幸运的了。因为当时身边有很多经验很丰富的同事可以请教。虽然这样,依然感觉到libGDX的教程少之又少,学习起来困难重重。整个过程大部分都是以一个初学者的水平一点一点从那晦涩难懂的英文libGDX官方教程中学习。所以当自己有能力去做些什么的时候,我很乐意用自己微薄的能力去为后来的学习者做些什么。这就是我写这本书的初衷。
作为一个优秀的游戏引擎,libGDX不知不觉也走过了好几个年头。从一开始的一个不怎么起眼的小项目发展到现在拥有众多使用者的项目,从第一个bug不少的版本发展到较为稳定的0.9x版本,再到后来的1.x版本,开发者们能够真真切切地感受到libGDX的每一步成长。
作为一个游戏引擎,libGDX支持使用Java语言进行游戏开发,这让众多爱好游戏开发,但又苦于使用门槛过高的其他语言进行游戏开发的开发者看到了希望。不仅如此,libGDX还兼容多种平台,包括Android、iOS、Html5、PC。作为一个游戏引擎,libGDX让使用Android原生进行开发的开发者们从SurfaceView中解脱出来,开发效率得到极大的提高。
本书内容涵盖了使用libGDX进行游戏开发所必须掌握的知识,其中包
括但不限于:libGDX的架构、libGDX UI编程、libGDX配套的开发工具、libGDX在一些问题上的解决方案。从内容结构上非常注重知识的实用性和可操作性。必须掌握的细节处绝不吝惜笔墨、甚至细致到每一次的鼠标单击;仅需要大致了解的内容绝不铺张浪费纸张、整体结构的描述提纲挈领。这样的安排注重了只介绍初学阶段必备知识的深入了解,需要大致了解的知识也能够有所认识,这种由浅入深、循序渐进地讲授遵循和尊重初学者对知识的认知规律。
本书每介绍一个基本知识点,都会通过一个难度适中的实例来帮助读者加深对该知识点的理解,从而能够举一反三。而且,每一个实例作者都会配以详细的注释,帮助读者更好地理解这个实例中每一行代码的意义,从而达到“知其然”,更要“知其所以然”的目标。
本书中所介绍的两个项目存在于真实的商业开发中。其中捡金币的游戏模型应用广泛。2014年百度在圣诞节期间的一些推广活动就是嵌入到这种模式的游戏中实现的。而其中的2048游戏更是在各大应用商店中广受好评。
本章对libGDX游戏引擎进行了较为详细的介绍。包括libGDX的历史、功能、特点以及libGDX都有哪些配套的开发工具。让读者对libGDX有一个感性的认识,为后面的章节打下基础。
本章详细介绍了libGDX的生命周期、模块组成、libGDX中可以使用的输出日志的方式以及一些其他的辅助工具。通过本章的学习,读者对使用libGDX进行游戏开发时所编写的代码架构有了较为直观的认知,清楚每一个生命周期函数中都应该完成哪些操作。
本章详细介绍了使用libGDX进行游戏开发时所需要用到的一些游戏的数据存取的一些方式。这一章知识的讲解不局限于libGDX,也详细介绍了Android中的操作方式。因为在开发的时候,这两种经常会配合着使用。
本章详细介绍了使用libGDX来进行UI编程时所需要的知识。其中包括对SpriteBatch、BitmapFont基础类的详细介绍。这些知识有利于更好地理解和掌握本书第5章的内容。毕竟本章的知识其实是第5章所介绍到的内容的底层实现。
本章是站在控件的高度去讲解UI编程。其中所涉及的控件包括Image、Button、Slider、SplitPane等常见又实用的控件。熟练掌握这些控件的使用,能让我们的开发更有效率,也让我们能够编写出符合我们项目需求的控件。
本章详细介绍了libGDX中的事件处理与音效。事件处理是整个游戏的基础,它能让游戏从一个状态切换到另外一个状态。而音效更是能为一个游戏加分不少。通过本章的学习,读者将掌握更上一层楼的技能与技巧。
本章详细介绍了资源加载器、游戏界面的跳转以及libGDX中的物理引擎。资源加载器所对应的异步加载方式常常被运用到大型游戏中。游戏界面的跳转是一个游戏中状态切换自然而然的效果。而物理引擎则在体育类游戏中随处可见。
本章详细介绍了如何让CocosStudio这个UI编辑器来为libGDX服务,从而弥补libGDX在UI编辑器上的不足。通过本章的学习能够让读者比较轻松地处理游戏元素较多的情况。
本章详细介绍了如何使用本书中所介绍到的知识去开发出一个具有较强趣味性的游戏——捡金币,该游戏模式被广泛应用各类的网页推广中。
本章详细介绍了一个成熟的休闲类游戏——2048的开发。首先分析了整个项目的组成,让读者对整个游戏的架构有一个清晰的认识。接下来把这个游戏中所涉及的每一部分都在详细的介绍中实现,并在最后提出了一些优化与改进的方向。
libGDX初级及中级开发者,了解Java语言和游戏开发的读者。
libGDX零基础,但具有一定的SurfaceView开发经验的工程师。
游戏开发零基础,但爱好游戏开发的工程师。
爱好游戏开发的大学生。
感谢南京邮电大学物联网学院院长孙知信教授在我编写本书时所给予的鼓励,也感谢给予过我很多帮助的同事和朋友们。
本书在编写过程中,我虽然具备一些开发经验,但是水平有限,书中不免有错误之处,望读者批评指正。
目前,手游在整个游戏的市场份额上所占的比例越来越大。腾讯系的手游更是一度占领了苹果App Store前十名超半数的席位。现在就让我们走进libGDX这个游戏引擎的世界,开发出一款属于自己的游戏。
在本节中,主要简单介绍一下libGDX这个游戏引擎,包括其特点、配套的开发工具等,让读者对libGDX这个游戏引擎有一个的感性的认识。
libGDX是一个跨平台的2D/3D游戏开发框架,它由Java/C/C++语言编写而成。它基于Apache LicenseVersion 2.0协议,对商业使用和非商业使用均免费,代码托管于Github中,最新版本为1.5.4。开源的游戏框架并不少见,但是libGDX的优势是相当明显的,主要是体现在以下几个方面:
libGDX兼容多种平台系统(Windows、Linux、Max OS X、Java Applet、Javascript/WebGL),包括移动系统Android(1.5版本+)和iOS。在Android阵营和相关市场日益扩大的情况下,对于Android平台的支持使libGDX的使用者越来越多。另外,基于RoboVM可以实现iOS兼容。
极强的兼容性为调试和开发提供了便利。你可以使用Android上面同样的代码在桌面PC上面编写、测试、调试你的应用(也就是说,你不需要打开Android模拟器,就可以调试用libGDX引擎写的游戏应用)。它剥离了常见的Windows之间/ Linux应用程序和一个Android应用程序的区别。一般的开发过程是尽可能地停留在桌面PC上,同时周期性地检查你的当前代码是否仍然能在Android上运行。
libGDX主要是用Java写的,其中也掺杂了一些C/C++代码,这些代码是为了处理一些对性能要求很高的操作,比如物理引擎或者音频处理。作为用户,你只需要关注Java的封装就可以了,它已经把所有的本地代码封装好了。相比于其他android游戏引擎,libGDX的效率优势十分明显。
libGDX的主要构成,如图1.1所示。
图1.1 libGDX的主要构成
libGDX由audio、files、graphics、math、physics、scenes、utils这些主要模块所组成,它们对应了libGDX中的常用函数、接口、音频操作、输入/输出、文件操作,2D/3D渲染及libGDX绘图相关运算、网络模块。
它提供了便于设计游戏的清晰架构,如图1.2所示。
图1.2 libGDX的架构
libGDX对于物理引擎的封装也是让人惊讶的。它使用jni封装了box2d的C++版本,使得其运行效率比其他同级的物理引擎如jbox2d更快。现在流行的几个包含物理引擎的Android游戏引擎(如Andengine、Rokon等),几乎都在用libGDX所封装的物理引擎。如果你的游戏(特别是针对Android平台的)准备使用物理引擎,请优先考虑libGDX。
libGDX具有较为完善的配套的开发工具,如粒子编辑器(Particle editor)、文字生成工具(Hiero bitmap font generator)、图片合并工具(Texture packer)等。这些所涉及的工具都会在以后的章节中陆续介绍。现在主要是贴出其软件界面,让大家先对其有一个直观的印象。
粒子编辑器一个很明显的作用就是可以给游戏的画面添加更炫丽的效果。无论是大型游戏还是中小型游戏,基本都会有“每日登录”这一模块,而这一模块中,就经常使用到粒子效果,如图1.3所示。
图1.3 粒子编辑器的界面
借助Hiero bitmap font generator,可以生成游戏中需要用到的字体文件。在显示游戏中的文字信息时,通常需要用到该字体文件。文字生成工具如图1.4所示。
图1.4 Hiero的界面
Texture packer的主要作用是将若干张小图合并为一张大图。这样在方便资源管理的同时,也尽可能地降低了GPU内存的浪费,如图1.5所示。
图1.5 Texture packer合并工具
2009年中期,项目开始,初始名称为AFX。
2010年3月6日,项目开源,代码托管在Google Code上。
2012年5月,市场占有率超过1.24%,超过了当时的Unity、AndEngine、Cocos2D、Corona和Marmelade。
2012年,Google Ingress使用了libGDX项目。
2013年,添加3D API支持。
2014年4月20日,1.0正式版发布。
此后libGDX版本的更新比较频繁,截止到2015年3月24日。libGDX已经更新到了1.5.4版本。libGDX的作者之一Mario Zechner,也是《Beginning Android Games》一书的作者之一。
这本书主要围绕libGDX 0.9.8来编写。与0.9.8版本相比,libGDX1.x具有以下特点。
1.更新了基于Gradle项目的安装,不需要更多的jars,支持所有平台的简单打包。
2.移除了对OpenGL ES 1.x的支持,支持OpenGL ES 3.0。
3.移除了大量Android后端的集群,要求最低Android版本是Android 2.2。
4.Box2D转移到了扩展中。
5.大幅度的改进了Scene2D,比如viewport enhancement。
6.更新了libGDX repo、网站和wiki,移除了旧的setup UI。
7.移除了音频和图像扩展和demo都放在了仓库中。
可以看出与0.9.8版本相比,使用libGDX 1.x开发起来将会更加简便。但是其最致命的缺点是移除了对OpenGL低版本的支持而不是选择兼容OpenGL低版本,这就导致市面上相当一部分使用OpenGL1.x手机的用户将无法运行使用libGDX1.x开发出来的游戏。而这一部分用户的丢失所带来的收益上的巨大损失是很多游戏开发商所无法接受的。所以,大部分的游戏开发商还是选择libGDX 0.9.8来进行游戏开发。这也是本书选择libGDX 0.9.8来进行讲解的主要原因。与libGDX 0.9.8相比,libGDX 1.x的主要API的改动较小,它们之间的大部分内容都是类似的。
在本节中,我们将介绍搭建libGDX的开发环境。本节是大家使用libGDX进行游戏开发的基本准备工作。需要注意的是,在此之前应该把JDK下载、安装并配置好。同样的,eclipse下的开发环境也需要配置好。
(1)登录官网:http://libgdx.badlogicgames.com/,单击红色框选中的“Download”,如图1.6所示。
图1.6 libGDX的官网
(2)单击“Download”选项后你会看到以下界面,单击其中的“Releases”选项,如图1.7所示。
图1.7 libGDX下载界面的入口
(3)这时你会看到以下界面,这个就是libGDX的各个版本的下载页面了,选中0.9.8的版本,如图1.8所示。
(4)单击之后便会自动进行下载。
图1.8 libGDX各个版本的下载页面
(1)打开eclipse。单击其中的“File”按钮,如图1.9所示。
图1.9 eclipse中的File按钮
(2)在出现的选项卡中选择“New”→“Android Appliacation Project”。这时候便会出现以下的选项卡,按图1.10进行填写。
图1.10 新建Android项目的选项卡
选项卡中各个选项的含义如下。
Application Name:Android应用程序的名称,该名称会显示在Android设备上。
Package Name:包名。应用商店根据该包名区分不同的应用。
(3)完成选项卡的配置后,一直单击“Next”按钮,直到“Next”按钮无法单击,这时单击“Finish”按钮即可完成Android项目的创建,如图1.11所示。
图1.11 新建项目后的效果图
所谓的libGDX的安装,就是把libGDX的压缩包解压出来,并把其中的一些jar包及目录集成到Android项目中。
(1)下载libGDX的压缩包及解压出来的文件夹,如图1.12所示。
图1.12 下载好的libGDX压缩包及解压后的文件夹
(2)进入解压后的文件夹,选中图1.13中选择的文件。
图1.13 解压后的libGDX安装包里面的内容
(3)将其复制到自己新建的Android项目中的libs目录下,如图1.14所示。
图1.14 将libGDX的jar包添加到项目后的效果
libs中的android-support-v4.jar是原本就有的,它是Android的兼容性包,其作用是让在Android 4.0系统上开发的应用能够在API Level 4的Android系统中正常运行。而剩下的文件及文件夹则是新导进来的,是在Android上使用libGDX进行游戏开发所需要的jar包及目录。
(4)这时,按住“Ctrl”键,然后单击左键选中“gdx.jar”和“gdk-backend-android.jar”,然后单击右键,在出现的选项卡中选择“Build Path”→“Add to Build Path”。(这一步如果不清楚的同学请参考一下光盘里面的视频教程。)
(5)单击左键项目名(例如,我们的第一个项目就叫作HelloWorld,这里就单击左键选中它),然后单击右键,在出现的选项卡中选择“Builde Path”→“configure Build Path”,在选项卡切换到“Order and Export”后,把其中的红色框选中的项目的钩都打上,如图1.15所示。
图1.15 Order and Export界面需要选择的选项
(6)切换到“Libraries”选项卡,把其中“Android Private Libraries”的选项删除,如图1.16所示。
图1.16 需要删除的选项
(7)到这里,就完成了在Android项目中使用libGDX进行游戏开发的环境配置。
经过1.3节以前的内容,我们已经搭建好了使用libGDX进行游戏开发的基本环境。那么这一节,就带大家用libGDX来写自己的第一个HelloWorld程序。
在这个HelloWorld程序中,将带大家使用libGDX把一张图片显示出来。通过这个HelloWorld的程序,让读者们对使用libGDX来进行游戏开发的程序结构有大致的了解。
具体步骤如下(考虑到有的读者可能之前没有开发经验,所以在这里把新建类的方法详细记录下来,但在以后章节的步骤中,将不会重复演示新建类的步骤。有经验的读者可以直接跳过这一部分阅读代码)。
(1)新建MainActivity类(把原来生成项目时自带的MainActivity.class删掉)。在这里完成一些初始化工作,是程序的入口。首先,单击左键包名,如图1.17所示。
图1.17 需要单击左键选中的包
(2)然后单击右键,在出来的选项卡中选择“New”菜单项,然后在二级选项卡中选择“Class”选项,这时就能看到以下界面,在“Name”菜单项中输入新建类的名字(在这里,我起了MainActivity这个名字,建议大家和我保持一致,这样便于今后找错),输完类名以后,单击“Browse”按钮,过程如图1.18所示。
图1.18 MainActivity类的新建
(3)单击“Browse”按钮后,会出现以下界面,在框中输入“AndroidApplication”,并单击选中备选框中在HelloWorld项目里面的那一个“AndroidApplication”,如图1.19所示。
图1.19 Maintivity需要继承的类
(4)然后单击“OK”按钮,在返回的界面中,单击“Finish”按钮即可完成MainActivity类的创建。
(5)新建MyGame类。在这里主要完成游戏的主要逻辑。首先在create()方法里面完成对象的初始化操作,如texture的初始化和bacth的初始化。然后在render()方法里面调用batch的draw(...)方法把图片绘画出来。
MyGame类的创建方法大致相同,有一点区别就在于:MainActivity类是继承于一个类,所以在新建MainActivity类的时候,单击的是“Browse”按钮,而MyGame类是实现了一个接口,那么这时候应该单击的是“Add”按钮,如图1.20所示。
图1.20 MyGame类的新建
(6)将这个Demo所需要的图片资源test1.jpg放到assets目录下的data目录下(data是在assets目录下新建的目录),如图1.21所示。
图1.21 资源的导入
MainActivity类和MyGame类的具体代码如下:
MainActivity类主要完成一些游戏相关的初始化操作。需要注意的是,不要去掉onCreate方法里面的super.onCreate(...)方法。
public class MainActivity extends AndroidApplication {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);//调用Android原生的onCreate()方法
initialize(new MyGame(), false);//完成相应的初始化工作
}
}
MyGame类主要是通过实现“使用SpriteBatch把Texture画出来的逻辑”,来对使用libGDX开发有一个感性的认识。需要注意以下几点:
Gdx.gl.glClearColor(1, 1, 1, 1);//把屏幕设置成白色
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);//清屏
public class MyGame implements ApplicationListener {
Texture texture;//纹理。简单理解成图像信息的容器
SpriteBatch batch;//简单理解成画笔
@Override
public void create() {
/**
* Gdx.files.internal("data/test1.jpg")这行代码的意思如下:
* 在assets目录下的data目录中找到名为test1.jpg的图片,然后返回一个
* FileHandle类型的对象,用于生成Texture类型的对象
*/
texture = new Texture(Gdx.files.internal("data/test1.jpg"));
// texture = new Texture(Gdx.files.internal("data/shopabg.jpg"));
batch = new SpriteBatch();//用构造函数生成一个SpriteBatch对象
}
@Override
public void dispose() {
// TODO Auto-generated method stub
}
@Override
public void pause() {
// TODO Auto-generated method stub
}
/**
* render()方法每一帧都会执行
*/
@Override
public void render() {
Gdx.gl.glClearColor(1, 1, 1, 1);//把屏幕设置成白色
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);//清屏
batch.begin();//开始绘制
/**
* texture:需要显示出来的纹理信息
* 第二个参数:需要显示出来的位置X轴坐标
* 第三个参数:需要显示出来的位置Y轴坐标
* 第四个参数:这幅图片显示出来的宽度
* 第五个参数:这幅图片显示出来的高度
*/
batch.draw(texture,0,0,480,800);//执行绘制操作
batch.end();//结束绘制
}
@Override
public void resize(int arg0, int arg1) {
// TODO Auto-generated method stub
}
@Override
public void resume() {
// TODO Auto-generated method stub
}
}
(1)将自己的手机与电脑连接。一个比较好的检测自己的手机是否和电脑连接成功的方法就是利用一些辅助工具,如“×××手机助手”等。如果你的手机已经和电脑成功连接,那么这时候会显示“已通过USB连接成功”的字样,如图1.22所示。
图1.22 手机的连接
(2)手机和电脑成功连接之后。单击左键选中自己要调试的项目,如在这里需要调试的是HelloWorld程序。然后再单击左键eclipse中一个绿色小圆圈中有一个白色三角形的按钮,如图1.23所示。
图1.23 项目的运行(1)
(3)如果是第一次运行该程序,会弹出以下对话框,单击左键选中其中的“Android Application”选项,然后再单击“OK”按钮,如图1.24所示。
图1.24 Run As对话框
(4)要确保自己的项目中的“Android private libraries”已经被移除,否则会报错。如图1.25所示,就是还没有将“Android private libraries”移除掉的情况。(移除“Android private libraries”的方法在1.2.3小节中已经介绍过)。
图1.25 需要删除的项目
(5)经过上面步骤后,会弹出一个对话框,以选择运行该程序的Android设备,单击选中设备并单击“OK”按钮,如图1.26所示。
图1.26 选择程序所运行的设备
(6)程序便会开始运行。运行效果如图1.27所示。
图1.27 运行效果图
通过1.3.2小节的学习,我们已经能在Android设备上运行我们的第一个HelloWorld程序了。作为一个跨平台的游戏引擎,libGDX还允许在桌面上调试程序,这极大地缩短了调试程序的时间。那么这一节就来学习一下桌面调试环境的搭建。
(1)新建一个Java项目(在之前的章节中我们已经学习了如何新建一个Android项目,它们的大部分步骤都是差不多的)。左键单击“File”→“New”→“Java Project”,便会弹出一个选项卡。在这个选项卡中填写好Java项目的项目名(例如,在这里我就把新的Java项目的项目名定义为HelloWorldDesktop),最后单击“Finish”按钮即可,如图1.28所示。
图1.28 Java项目的新建
(2)在下载好的libGDX的压缩包的解压包中找到以下4个jar包,如图1.29所示。
图1.29 桌面项目所需要的jar包
(3)然后把它们添加到刚刚新建好的Java项目的根目录下,如图1.30所示(图1.30中的libs目录也是自己手动新建的)。
图1.30 桌面项目中libGDX的jar包的导入
(4)按住“Ctrl”键,单击左键选中图1.30中的4个jar,然后单击右键,在弹出的选项卡中选择“Build Path”→“Add To Build Path”。将jar包成功添加到Build Path以后,会看到自己的项目中多了一个名为“Referenced Libraries”的条目,如图1.31所示。
图1.31 将jar包导入项目后的效果
(5)将Android项目中用到的资源复制一份到Java项目中,如图1.32所示。
图1.32 桌面项目与Android项目的资源的对应关系
(6)把Java项目和Android项目关联起来。单击左键,选中Java项目(例如这里是HelloWorldDesktop),单击右键,在出来的选项卡中选择“Build Path”→“Configure Build Path”。在出来的选项卡中选择“Project”选项,然后单击“Add”按钮,如图1.33所示。
图1.33 依赖项目的配置(1)
(7)单击“Add”按钮后,会出现一个选项卡,在这里选择自己需要关联的项目,并单击“OK”按钮即可,如图1.34所示。
图1.34 依赖项目的配置(2)
(8)完成上一步操作后会自动返回到上一个选项卡,在该选项卡同样单击“OK”按钮即可完成项目的关联。
(9)在Java项目中新建一个名为Main的类。在桌面项目中,只需要编写少量的代码即可。代码如下:
public class Main {
public static void main(String[] args) {
/**
* LwjglApplication.LwjglApplication(ApplicationListener listener, String title, int width, int height, boolean useGL2)
* 这个构造函数的作用是:创建一个lwjgl后台应用。这个应用的标题为title,宽度为width,高度为height
* listener: 传入自己在Android项目中所写的程序ApplicationListener的类的对象,在这里是MyGame类的对象
* title: 这个应用的标题
* width: 宽度
* height: 高度
* useGL2: 当这个参数设置为true的时候表示,当Android设备的OpenGL2.X可用的时候,将使用OpenGL2.X。考虑到要兼容全球大部分的手机,这个参数一般设置为false
*/
new LwjglApplication(new MyGame(), "HelloWorld",480,800,false);
}
}
(10)运行Java项目,进行桌面调试。步骤和运行Android项目一样,如图1.35所示。
图1.35 桌面项目的运行
(11)名为“Run as”的选项卡只有在第一次运行该项目时才会弹出,在以后的运行中都不会弹出。单击“OK”按钮后,会弹出一个界面,按图1.36所示进行操作即可。
图1.36 桌面项目的运行
(12)桌面调试的效果。经过上述操作,就能看到程序在电脑端运行的效果,如图1.37所示。
图1.37 桌面项目运行的效果
到这里就成功地搭建好了桌面调试环境。
通过前面的学习,我们已经对使用libGDX来进行游戏开发有了一定的认识,并且利用libGDX这个游戏引擎写出了自己的HelloWorld程序。下面对这个HelloWorld程序进行一下简单的分析。
1.MainActivity:这个类属于Android原生与libGDX的桥梁。在这里完成一些基本的初始化操作。
2.MyGame:这个类是libGDX的入口类,在这里开始编写整个游戏的逻辑。
3.当配置桌面调试环境的时候,记得要把Android项目中用到的资源复制一份到Java项目中。同样也需要把libGDX中相应的jar包复制一份到Java项目中。桌面项目中的类的代码编写量比较小——一行即可。
/**
* LwjglApplication.LwjglApplication(ApplicationListener listener, String title, int width, int height, boolean useGL2)
* 这个构造函数的作用是:创建一个lwjgl后台应用.这个应用的标题为title,宽度为width,高度为height
* listener: 传入自己在Android项目中所写的那一个程序ApplicationListener的类的对象,在这里是MyGame类的对象
* title: 这个应用的标题
* width: 宽度
* height: 高度
* useGL2: 当这个参数设置为true的时候表示,当Android设备的OpenGL2.X可用的时候,将使用OpenGL2.X。考虑到要兼容全球大部分的手机,这个参数一般设置为false
*/
new LwjglApplication(new MyGame(), "HelloWorld",480,800,false);
4.在清屏的时候,不要只写Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT)。只写这一句,Group在visible和unvisible之间切换时有可能会导致背景变白(通常情况下,在没有背景图时,游戏的背景应该是黑色)。所以清屏代码应该加上:
Gdx.gl.glClearColor(1, 1, 1, 1);//把屏幕设置成白色。不要把这一句漏了
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);//清屏
在1.3节中,我们已经能够编写自己的第一个libGDX的HelloWorld程序了。在学习libGDX这个游戏引擎的时候,libGDX官方还提供了一些例子,通过这些例子,我们能学习到别人是怎么架构和设计一个游戏的,以及别人对于细节的处理,这对于初学者来说,是很好的资源。这一节就来学习怎么利用好这些资源。
进入官网,常见的方式有两种。
1.直接在地址栏中输入:http://libgdx.badlogicgames.com/。
2.在百度中搜索“libGDX”,出现的第一个选项即是libGDX的官网,如图1.38所示。
图1.38 在百度中搜索的效果
单击进去以后,看到的就是libGDX的官网了,如图1.39所示。
图1.39 libGDX官网的主要选项
以下对其主要栏目做一下解释。
News:里面是libGDX的一些新闻。
Features:里面是libGDX的一些特性。
Download:在这里可以下载各个版本的libGDX。
Source:libGDX的一些源码。
Documentations:在这里有libGDX的一些文档。
Gallery:里面列出了许多使用libGDX游戏引擎所开发的优秀游戏。
Community:里面是libGDX的论坛。
一般情况下,主要访问Download、Documentations和Source这几个栏目。
(1)进入到libGDX的首页以后,单击“Documentation”栏目,然后在出来的界面中向下移动,单击“Read wiki”,如图1.40所示。
图1.40 官网中的“Read the wiki”选项
(2)单击“Read wiki”后,在出来的选项中找到“Running Demos”,如图1.41所示。
图1.41 “Running Demos”选项
(3)单击“Running Demos”后,就可以看到libGDX提供的一些Demo,如图1.42所示。
图1.42 可以下载的Demos
(4)选择自己想要学习的demo,单击进去,找到并单击其中的“Download ZIP”即可完成demo 的下载,如图1.43所示。
图1.43 “Download zip”按钮
通过学习前面的知识,我们已经学会写一个libGDX的HelloWorld程序,并且学会了在Android平台和桌面上调试自己的libGDX程序。但是在很多情况下,我们还需要看Android的源码、libGDX的源码、直接打开可运行的jar包。这一节就来讲解这些知识。
Android系统也是由许许多多的代码组成的,这些代码也很有可能会有错误。在遇到一个问题的时候,除了我们自身的原因,还有可能是别人给我们提供的代码本身就是错误的。所以,在开发的时候,经常需要去看一下别人的代码都是怎么写的,有没有错误。
(1)在这之前要先确保你已经利用SDK Manager下载好你所使用的Android版本的源码,下载界面如图1.44所示。
图1.44 SDK Manager的下载界面
(2)没有关联源码前,按住“Ctrl”键,单击MainActivity中onCreate()中的super.onCreate(),Main Activity中的代码情况,如图1.45所示。
图1.45 MainActivity里的onCreate()方法
若没有关联源码,会出现图1.46所示的界面。
图1.46 无法找到源码的效果
(3)单击左键选中项目,然后单击“Project”→“Properties”→“Java Build Path”→“Libraries”→展开“Android 4.3”(我的Android是4.3版本,所以这里是Android4.3)→展开“android.jar”→选中“Source attachment”→单击右边的“Edit”按钮,如图1.47所示。
图1.47 关联Adroid源码的步骤(1)
(4)然后在弹出来的对话框中选择“External locaition”→“External Folder”。这时候选择自己事先下载好的Android源码即可,如图1.48和图1.49所示。
图1.48 关联Adroid源码的步骤(2)
图1.49 关联Adroid源码的步骤(3)
(5)单击“OK”按钮即可完成Android源码的关联。需要注意的是,关联源码的过程可能有点久,耐心等待一下即可。
(6)下面测试这个程序。当再次按住“Ctrl”键,单击MainActivity中的onCreate()中的super.onCreate()。出现的界面如图1.50所示。
图1.50 关联Adroid源码成功后的效果
如果出现这个界面,就证明关联Android源码成功了。以上就是Android4.3版本中的onCreate()方法的实现。
(1)在关联libGDX源码之前,按住“Ctrl”键的同时,单击左键“Texture”这个类,如图1.51所示。
图1.51 关联libGDX源码前的效果(1)
假如还没有关联libGDX源码,那么会出现如图1.52所示的界面。
图1.52 关联libGDX源码前的效果(2)
(2)关联源码。先去之前下载好的libGDX的压缩包的解压包中找到所需要的jar包,如图1.53所示。
图1.53 关联libGDX源码的步骤(1)
(3)把里面的gdx-sources.jar、gdk-backend-android-sources.jar包复制到Android项目中的libs目录下(与之前所导进去的jar包处于同一级目录),如图1.54所示。
图1.54 关联libGDX源码的步骤(2)
(4)单击左键选中项目,然后在出来的选项卡选择“Build Path”→“Configure Build Path”,这时候按图1.55所示步骤进行操作即可完成libGDX源码的关联。
图1.55 关联libGDX源码的步骤(3)
同样地,按照以上步骤即可完成gdx.jar的源码的关联。
(5)测试。完成libGDX的源码的关联后,按住“Ctrl”键,同时单击左键“Texture”,将会看到以下界面,如图1.56所示。
图1.56 关联libGDX源码成功后的效果
需要注意的是,关联源码时在自己的项目中不能包含“Android Private Libraries”这个选项,否则依然无法查看源码。
(1)修改.jar文件的默认打开方式。在这里用来演示所用的.jar文件,是我们使用libGDX进行游戏开发时很重要的合图工具gdx-texturepacker.jar。其下载地址是:https://code.google.com/p/libgdx-texturepacker-gui/downloads/list。建议大家下载3.2.0的版本,因为这本教程中演示时所使用到的是3.2.0的版本的gdx-texturepacker,这时候我们选择3.2.0的版本进行下载,如图1.57所示。
图1.57 texturepacker的下载
(2)单击右键“gdx-texturepacker.jar”将其默认打开方式修改为“javaw.exe”(它在Java的安装路径下的bin目录中,例如我的javaw.exe就在C:\Program Files (x86)\Java\jdk1.7.0_21\bin中),如图1.58所示。
图1.58 直接打开texturepacker的步骤(1)
(3)修改注册表。通过上面的步骤后,还不能双击运行.jar文件。在Windows开始菜单的搜索框中输入“regedit”,如图1.59所示。
图1.59 直接打开texturepacker的步骤(2)
(4)双击运行“regedit”,这时会出现一个注册表编辑器。在注册表编辑器中,找到“HKEY_ CLASSES_ROOT\Applications\javaw.exe\shell\open\command”,在其中文件打开命令中加入参数“-jar”(无引号),修改后的数值类似:“"C:\Program Files\Java\jre7\bin\javaw.exe" -jar "%1"”(只需要添加-jar参数,无需修改其他信息),保存并退出注册表编辑器,如图1.60所示。
图1.60 直接打开texturepacker的步骤(2)
(5)测试程序。双击运行“gdx-texturepacker.jar”,你会看到图1.61所示的界面。
图1.61 成功双击运行.jar文件
这就说明我们已经成功地完成相应的配置。
通过对第1章的学习,我们掌握了libGDX的基本使用方法。但是作为游戏引擎,我们有必要对其模块组成、生命周期及其提供的一些方便的工具有一定的了解。这些就是这一章所需要学习的内容。
无论是Android原生应用,还是libGDX应用,都有生命周期的概念。它管理着一个应用的各个状态,如应用的创建、重新开始、暂停以及销毁。
libGDX的生命周期的流程图与Android原生的生命周期流程图很像,从图2.7可以看到,libGDX是架构在Android平台之上的,libGDX很多东西都对Android做了封装。libGDX的生命周期流程图如图2.1所示。
图2.1 libGDX中的生命周期
从图2.1可以看出,当一个libGDX应用开始运行的时候,它首先会调用create()方法,接着调用resize()方法,接下来libGDX应用就正常运行了,它在运行的时候每一帧都会调用render()方法来进行渲染。以下是在运行时可能出现的两种常见情况,以及对应的生命周期流程:
1.libGDX应用失去焦点。这时会调用pause()方法,当libGDX再次获得焦点,会调用resume()函数,游戏重新回到正常运行的状态。
2.退出libGDX应用。这时候会调用pause()方法,接着调用dispose(),最后libGDX应用就退出了。
表2.1列举了libGDX生命周期中的各个函数,并且对其作用做了较为详细的描述。
表2.1 libGDX中生命周期的各个函数及其描述
方法 |
描述 |
---|---|
create () |
调用时机:这个函数在应用被创建的时候调用一次 |
resize(int width, int height) |
调用时机:当屏幕的大小发生改变,且应用不处于pause( )状态,这个函数会被调用。同时,它也会在create( )方法之后调用一次 |
render () |
调用时机:这个函数在每一帧都会被调用,用来渲染游戏画面 |
pause () |
调用时机:在Android中,这个函数会在用户按下“Home”键的时候被调用。而在桌面项目中,退出游戏时,它会在调用dispose()之前被调用 |
resume () |
调用时机:这个函数会在游戏从pause状态重新获得焦点的时候被调用。需要注意的是,这个函数会对Android设备起作用 |
dispose () |
调用时机:当一个应用要被销毁的时候,在pause()之后被调用 |
2.1.1小节与2.1.2小节已经给大家讲解了生命周期的一些理论知识,那么以下通过一个小例子来加深对生命周期中的理解。以下的例子中,主要是在生命周期中的每一个方法里面都加了一个log(日志),用于研究每一个方法的调用时机。(这个例子中,MainAcitivity的代码HelloWorld中是一样的,不一样的是MyGame这个类里面的代码书写。)
以下这个例子,用于在控制台中输出日志,研究libGDX生命周期中各个方法的调用时机。让大家更好地理解图2.1中的内容。其中就只用到了MyGame这个类,分别在生命周期中所涉及的方法create( )、dispose( )、pause( )、render( )、resize( )、resume( )中都加上了一个打印语句System.out。
public class MyGame implements ApplicationListener {
@Override
public void create() {
System.out.println("------->create()");//在控制台中输出日志
}
@Override
public void dispose() {
System.out.println("------->dispose()");//在控制台中输出日志
}
@Override
public void pause() {
System.out.println("------->pause()");//在控制台中输出日志
}
@Override
public void render() {
Gdx.gl.glClearColor(1, 1, 1, 1);//把屏幕设置成白色
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);//清屏
}
@Override
public void resize(int arg0, int arg1) {
System.out.println("------->resize()");//在控制台中输出日志
}
@Override
public void resume() {
System.out.println("------->resume()");//在控制台中输出日志
}
}
(1)运行程序(运行程序的操作在第1章已经讲解得很细致了,这里不再赘述)。可以看到Logcat中打印出以下log,如图2.2所示。
图2.2 控制台打印出的log(1)
(2)单击Back键。会看到在Logcat中打印出以下log,如图2.3所示。
图2.3 控制台打印出的log(2)
(3)再次进入应用。单击HOME键,这时Logcat上会出现以下log,如图2.4所示。
图2.4 控制台打印出的log(3)
(4)再单击HOME键之后,进入应用,那么这时候Logcat中打印出以下log,如图2.5所示。
图2.5 控制台打印出的log(4)
通过以上的实验,再在脑海中回顾一下2.1.1小节中介绍到的生命周期的流程图,我们对libGDX的生命周期的理解又深了一点。
大家可以看到,render( )函数中并没有打印日志的代码,假如给它加上一个打印日志的代码,那么在Logcat中看到的log,如图2.6所示。
因为render( )方法是每一帧都会调用的,所以Logcat中不断地产生日志。
图2.6 控制台打印出的log(5)
libGDX这个游戏引擎的有一个特点就是架构清晰、组成模块化。那么现在我们就来学习一下libGDX这个游戏都由哪些模块组成,这些模块分别对外提供什么功能。
libGDX由数个模块组成,它们分别为一个游戏的各个步骤提供了服务与支持。这些主要模块在一个游戏中的典型架构如图2.7所示。
图2.7 libGDX的架构
由图2.7可以看出,libGDX主要由Input、Files、Graphics、Audio这几个模块组成,以及图2.7中没有显示出来的Net模块。它们分别对应了libGDX中的输入操作、文件操作、2D/3D等图形绘制、音频操作及网络操作。
这些可以在源码中体现出来。打开Gdx的源码,可以看到,Gdx这个类中简单到只有数个成员变量,这些成员变量在libGDX中分别负责一个模块。例如audio负责音频模块。
public class Gdx {
public static Application app;//提供了众多方便的方法
public static Graphics graphics;//图形渲染模块
public static Audio audio;//音频模块
public static Input input;//输入模块
public static Files files;//文件模块
public static Net net;//网络模块
public static GLCommon gl;
public static GL10 gl10;
public static GL11 gl11;
public static GL20 gl20;
public static GLU glu;
}
以上所涉及的各个模块都会在以后的相应章节展开讲解。
通过2.2.1小节,我们对libGDX的模块组成有了一个大体的了解,这一小节来分析libGDX中几个主要模块的作用。
1.Application:在这个接口中提供了很多方便的方法。包括获取程序目前运行环境、目前程序所占用的内存大小等。
2.Graphics:利用OpenGL ES 将图片绘制到屏幕上。它抽象了与GPU之间的通信,并且提供了方便的方法去获取一个OpenGL ES的封装实例。而这个封装的实例可不可用取决于底层硬件是否支持。如果底层硬件不支持,那么返回来的实例将会是null值。例如,可以通过以下代码获取一个OpenGL 2.0的实例。
GL20 gl = Gdx.graphics.getGL20 ();
这个方法会返回一个实例,使用这个实例就可以将图片绘画到屏幕上。但是,如果底层硬件不支持,那么返回来的值会是null。
当获取到一个OpenGL ES的实例以后,可以使用这个实例去完成一定的事情,如清屏:
gl.glClearColor(1f, 0.0f, 0.0f, 1);
gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
3.Audio:为各个平台音频文件的提供了一种统一、方便的操作。它通过Sound、Music来操作音频文件。支持的音频文件的格式有:WAV、MP3和OGG。
以下代码演示了怎么通过Audio去操作音频文件:
//利用音频文件生成一个Music类的实例
Music music = Gdx.audio.newMusic(Gdx.files.getFileHandle("data/myMusicFile.mp3", FileType.Internal));
music.setVolume(0.5f);//设置音量的大小
music.play();//开始播放音乐
music.setLooping(true);//将音乐设置成不断循环
4.Input:用于处理输入操作。libGDX通过Input为各个平台(如Android、IOS、Desktop)提供统一的输入模型和处理机制。它支持键盘输入、单击屏幕、加速度传感器以及鼠标(对Desktop来说)。
举例:以下代码就是通过Input模块来获取当前单击的位置。
if (Gdx.input.isTouched()) {//如果监听到单击事件
System.out.println("刚刚单击的位置的横坐标是 x=" +Gdx.input.getX() + ",纵坐标是 y=" + Gdx.input.getY());
}
5.Files:文件操作。为各个平台提供了一种统一、简单的文件读写的方式,而不用去处理各种平台之间的差异。它让开发者可以更简单地去读写文件。但是出于平台的安全性的考虑,它在“写”操作上也有一定的限制。
以下代码演示了使用Files模块提供的功能,利用一个assets目录下的图片资源文件来生成一个Texture类的实例:
Texture myTexture = new Texture(Gdx.files.internal(“assets/texture/brick.png”));
6.Networking:给游戏提供基本的网络支持。在一段时间内,libGDX更多地适用于弱联网游戏。但现在libGDX对网络方面已经提供了越来越多的支持。
我们在2.2节中分析学习了libGDX的模块组成及各个模块的功能。它们分别是Input、Graphics、Files、Audio。在这一节,我们学习一下Application这个接口提供了哪些方便的功能。
在实际开发的时候,常常需要根据不同的平台来进行一些不同的操作。如保存数据时,保存数据到Android的SD卡与保存数据到Desktop中有较大的差异。libGDX完全考虑到了这种需求,我们可以通过Applciation.getType( )来获取程序现在所运行的平台。常用代码如下:
switch (Gdx.app.getType()) {
case Android:
// android specific code
break;
case Desktop:
// desktop specific code
break;
case WebGl:
// HTML5 specific code
break;
default:
// Other platforms specific code
}
如果当前程序是运行在Android平台上,那么还可以通过以下代码来获取Android SDK的版本。
int androidVersion = Gdx.app.getVersion();
以上代码会返回当前设备所支持的SDK的版本。如Android 1.5的SDK的版本就是3。
当调试程序的时候,往往需要知道一个程序的内存消耗的情况。在libGDX中,提供了相应的API来查询内存的消耗情况。以下代码分别获取当前程序所占用的JavaHeap和NativeHeap的字节数。
long javaHeap = Gdx.app.getJavaHeap();
long nativeHeap = Gdx.app.getNativeHeap();
众所周知,日志在调试程序的时候起着非常重要的作用。这一节我们就来对比学习Android中的日志工具与libGDX中的日志工具。
在Android原生中,在程序中输出日志使用android.util.Log类。该类提供了若干静态方法。
Log.v(String tag, String msg);
Log.d(String tag, String msg);
Log.i(String tag, String msg);
Log.w(String tag, String msg);
Log.e(String tag, String msg);
分别对应Verbose、Debug、Info、Warning、Error。tag是一个标识,可以是任意字符串,通常可以使用类名+方法名,主要是用来在查看日志时提供一个筛选条件。
以下通过一个例子来演示使用Android原生来输出日志。
新建一个Android项目,在该项目中的MainActicity中编写以下代码。该代码主要在onCreate( )方法里面通过Log来输出各个级别的日志,用于给大家认识各个级别的日志信息的特点。
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.v("------->verbose", "这是verbose级别的日志");
Log.d("------->debug", "这是debug级别的日志");
Log.i("------->info", "这是info级别的日志");
Log.w("------->warn", "这是warn级别的日志");
Log.e("------->error", "这是error级别的日志");
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
程序运行结果如图2.8所示。
图2.8 Android中各个级别的日志信息
在libGDX中,如果想要输出日志,主要是通过Application这个接口,它提供了3种输出日志的方式:
Gdx.app.log("MyTag", "my informative message");//对应Android原生的info级别
Gdx.app.error("MyTag", "my error message", exception);//对应error级别
Gdx.app.debug("MyTag", "my debug message");//对应Android原生的debug级别
在libGDX中,可以通过代码来设置日志从哪一个级别开始输出:
Gdx.app.setLogLevel(logLevel);//从logLevel级别开始输出日志
logLevel可以是以下几种中的一种:
Application.LOG_NONE: 过滤掉所有的日志
Application.LOG_DEBUG: 输出>=debug级别的日志。对于libGDX来说,就等于是输出所有日志
Application.LOG_ERROR: 输出>=error级别的日志。对于libGDX来说,就等于是只输出error级别的日志
Application.LOG_INFO: 输出>=info级别的日志。对于libGDX来说,就等于是输出info和error两个级别的日志
以下通过例子来演示libGDX中的日志输出。
新建一个libGDX项目,再去MyGame类编写一下代码(MainActivity类的代码与HelloWorld的相同)。
public class MyGame implements ApplicationListener {
@Override
public void create() {
/**
* Application.LOG_NONE: 隐藏所有的log()
* Application.LOG_DEBUG: 显示>=debug级别的log
* Application.LOG_INFO: 显示>=info级别 的log
* Application.LOG_ERROR: 显示>=error级别的log
*
* 各个级别的关系如下:
* verbose
* debug
* info
* warn
* error
* asset
*/
// Gdx.app.setLogLevel(Application.LOG_NONE);//设置从哪一个级别的日志开始输出
}
@Override
public void dispose() {
}
@Override
public void pause() {
}
@Override
public void render() {
Gdx.gl.glClearColor(1, 1, 1, 1);// 设置背景为白色
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);// 清屏
//测试各个级别的日志输出
// Gdx.app.log("mytag...", "log mytag");
// Gdx.app.error("mytag error", "log error");
// Gdx.app.debug("debug mytag...", "my debug tag");
System.out.println("---------->hello world");
}
@Override
public void resize(int arg0, int arg1) {
}
@Override
public void resume() {
}
}
在运行的时候,大家可以依次设置各个级别的起始日志来看看Logcat中日志输出的差异。
2.4.1小节与2.4.2小节分别介绍了Android中与libGDX中输出日志的方式。在真实项目开发中,你还可以通过以下代码来输出日志:
System.out.println("---------->hello world");
这时候你在Logcat中会看到以下效果,如图2.9所示。
图2.9 libGDX使用System.out来输出日志