本章源码链接:http://pan.baidu.com/s/1sjYE0sH 密码:q4n2
  正如标题上所标注的,本章将完成FlappyBird的所有剩余内容。对比原版游戏我们可以发现FlappyBird现在还差两个界面,如下所示:

LibGDX提供Game和Screen两个类使我们可以将游戏拆分为多个界面。其中Game实现了ApplicationListener接口,所以他也可以作为项目的主类(也就是共享项目的启动类)。而Screen是LibGDX提供的一个具有完整生命周期的界面类。Game类提供了一个setScreen()方法可以让我们切换界面。

现在我们将游戏拆分为三个界面,分别是欢迎界面(WelcomeScreen)、预览界面(PreviewScreen),这两个界面分别对应上述两张截图,最后一个界面就是我们的游戏开始界面(FlappyBirdScreen)。那么游戏主类就是继承于Game的一个类了,这里我们命名为FlappyBirdGame。

在欢迎界面我们有两个目的,第一个目的是显示一个具有标志性的logo,第二个目的是加载资源。这两个目的是相辅相成的,我们知道,当资源过多时从硬盘加载到内存是需要相当大的开销,如果我们直接加载的话,屏幕可以能会出现短暂的黑屏甚至可能导致ANR(应用程序无响应),这样的情况对于用户来说是一种极为糟糕的体验。可能你已经发现前面我们一直使用的是直接加载应用资源。接下来我们将实现异步加载应用,并且在加载资源的这段时间里我们会在屏幕上显示一个标志性的logo背景,这样就可以完美的避免ANR和糟糕的体验。

首先让我们改造一下Assets类,让我们可以异步加载资源:

public class Assets implements Disposable, AssetErrorListener {...public void init (AssetManager assetManager) {this.assetManager = assetManager;// 设定资源管理器的错误处理对象句柄assetManager.setErrorListener(this);// 载入纹理集assetManager.load(Constants.TEXTURE_ATLAS_OBJECTS, TextureAtlas.class);// 载入声音文件assetManager.load("sounds/sfx_die.ogg", Sound.class);assetManager.load("sounds/sfx_hit.ogg", Sound.class);assetManager.load("sounds/sfx_point.ogg", Sound.class);assetManager.load("sounds/sfx_swooshing.ogg", Sound.class);assetManager.load("sounds/sfx_wing.ogg", Sound.class);}public boolean isLoaded() {if(!assetManager.update()) return false;// 打印资源信息Gdx.app.debug(TAG, "# of assets loaded: " + assetManager.getAssetNames().size);for(String a : assetManager.getAssetNames()) {Gdx.app.debug(TAG, "asset: " + a);}atlas = assetManager.get(Constants.TEXTURE_ATLAS_OBJECTS, TextureAtlas.class);fonts = new AssetFonts();bird = new AssetBird(atlas);pipe = new AssetPipe(atlas);land = new AssetLand(atlas);number = new AssetNumber(atlas);assetUI = new AssetUI(atlas);sounds = new AssetSounds(assetManager);decoration = new AssetDecoration(atlas);return true;}...
}

我们发现我们删除了init()方法中加载资源和创建分类资源的代码,剩下的代码仅仅是预加载资源而已。这里我们添加了一个isLoaded()方法,该方法具有两个功能,第一是判断资源是否加载成功,第二是创建分类资源对象。在isLoaded()中,我们首先调用AssetManager的update()方法,该方法也具有两个功能,第一是触发资源开始加载,并且是异步触发,也就死说update()方法不会阻塞当前线程,第二个是判断资源是否加载成功。所以当我们第一次调用isLoaded()的时候触发了资源开始异步加载,接着后面每次调用isLoaded()都会判断资源是否加载成功,如果成功则创建分类资源对象,然后返回true,否则返回false。

接下来我们创建第一个界面WelcomeScreen:

package com.art.zok.flappybird;import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.Texture;
import com.art.zok.flappybird.game.Assets;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.Action;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
import com.badlogic.gdx.utils.viewport.StretchViewport;
import com.badlogic.gdx.scenes.scene2d.actions.Actions;public class WelcomeScreen implements Screen {private Stage stage;private FlappyBirdGame game;public WelcomeScreen(FlappyBirdGame game) {this.game = game;}@Overridepublic void show() {Image background = new Image(new Texture(Gdx.files.internal("images/splash.png")));background.setSize(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());Action fadeIn = Actions.fadeIn(1);background.addAction(fadeIn);stage = new Stage(new StretchViewport(Gdx.graphics.getWidth(), Gdx.graphics.getHeight()));stage.addActor(background);// 预加载资源Assets.instance.init(new AssetManager());}@Overridepublic void hide() {stage.dispose();}private void update() {if(Assets.instance.isLoaded()) game.setScreen(new PreviewScreen(game));}@Overridepublic void render(float delta) {update();stage.act();stage.draw();}...}
  首先我们的构造函数需要一个主类对象作为参数,我们保存该参数是为了在恰当的时间切换界面。现在需要着重关注三个重要的重写方法show()、hide()、render(),render方法不用多说,因为我们见的太多了,而且都具有相同的功能。主要是show()和hide(),show()方法在我们使用Game类的setScreen()方法中调用,也就是我们准备显示该界面的时候调用一次,因此该方法是我们实现初始化的最佳地方。hide()方法其实也是在show()方法中调用,但不同的是,hide()方法是在切换第二个界面时调用的,也就是说当我们每次调用setScreen()方法时,Game类都会提前先掉用当前保存的Screen类对象的hide()方法,所以hide()方法非常合适释放内存。
  根据上述分析,我们在show()中创建一个背景图片Image对象和Stage对象,并为Image对象添加一个渐入动作类,最后我们进行资源的预加载。
  hide()方法我们释放了stage对象。
  update()方法中我们触发并判断资源是否加载成功,如果成功则切换到下一个界面(PreviewScreen)。render()方法 中调用了update()并渲染了stage对象。
  在创建第二个界面之前我们需要修改一下Bird对象,这是为什么呢,因为在预览界面我们也需要使用Bird对象,但是预览界面和游戏界面Bird对象在屏幕的位置完全不同,因此为了能让Bird支持在不同的位置显示,我们必须改造其构造方法和初始化方法:
...public Bird(float x, float y) {init((int) (Math.random() * 3), x, y);}// 初始化public void init(int selected, float x, float y) {super.init();                  // 保证对父类的初始化contacted = false;flashed = false;if (selected == 1) {birds = Assets.instance.bird.bird0;} else if (selected == 2) {birds = Assets.instance.bird.bird1;} else {birds = Assets.instance.bird.bird2;}birdAnimation = new Animation(0.1f, birds);birdAnimation.setPlayMode(Animation.PlayMode.LOOP);dimension.set(3.72f, 2.64f);position.set(x, y);}...

我们为构造方法和初始化方法分别添加了两个float类型数据表示Bird的初始化位置,最后我们将x和y设置为position的值。

  既然修改了Bird构造函数,那么我们就必须修改WorldController的initBird()方法:
private void initBird() {if(bird == null) {bird = new Bird(-4.5f, 1.3f);} else {bird.init((int) (Math.random() * 3), -4.5f, 1.3f);}}

 接下来我们创建第二个界面:
package com.art.zok.flappybird;import com.art.zok.flappybird.game.Assets;
import com.art.zok.flappybird.game.UI.CustomButton;
import com.art.zok.flappybird.game.object.Bird;
import com.art.zok.flappybird.game.object.Land;
import com.art.zok.flappybird.util.Constants;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.InputListener;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
import com.badlogic.gdx.utils.viewport.StretchViewport;public class PreviewScreen implements Screen {private Bird bird;                     // bird objectprivate Land land;                        // land objectprivate Stage stage;                  // stageprivate Image title;                    // text title imageprivate Image copyRight;             // text copyright imageprivate float  viewWidth;                // last viewport widthprivate SpriteBatch batch;                // sprite render batchprivate FlappyBirdGame game;          // main classprivate AtlasRegion background;            // random background texture regionprivate OrthographicCamera camera;       // 正交投影相机private CustomButton play, score, rate;    // three buttonspublic PreviewScreen(FlappyBirdGame game) {this.game = game;}@Overridepublic void show() {batch = new SpriteBatch();viewWidth = Constants.VIEWPORT_HEIGHT * Gdx.graphics.getWidth() / Gdx.graphics.getHeight(); // bird and landbird = new Bird(0, 2);land = new Land();// cameracamera = new OrthographicCamera(Constants.VIEWPORT_WIDTH, Constants.VIEWPORT_HEIGHT);camera.position.set(0, 0, 0);camera.update();// stagestage = new Stage(new StretchViewport(Constants.VIEWPORT_GUI_WIDTH,Constants.VIEWPORT_GUI_HEIGHT));// background texture regionbackground = Assets.instance.decoration.bg.random();// title imagetitle = new Image(Assets.instance.assetUI.textTitle);title.setBounds(93, 553, 295, 90);// copyright imagecopyRight = new Image(Assets.instance.assetUI.copyRight);copyRight.setBounds(137, 110, 206, 21);// three buttonsplay = new CustomButton(Assets.instance.assetUI.buttonPlay);play.setBounds(43, 175, 173, 108);score = new CustomButton(Assets.instance.assetUI.buttonScore);score.setBounds(263, 175, 173, 108);rate = new CustomButton(Assets.instance.assetUI.buttonRate);rate.setBounds(189, 313, 102, 65);stage.addActor(title);stage.addActor(copyRight);stage.addActor(play);stage.addActor(score);stage.addActor(rate);//input handlerGdx.input.setInputProcessor(stage);// touch up event (touchDown must be return true)play.addListener(new InputListener() {public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {return true;};public void touchUp(InputEvent event, float x, float y, int pointer, int button) {game.setScreen(new FlappyBirdScreen());}});}@Overridepublic void hide() {batch.dispose();stage.dispose();}private void update(float deltaTime) {bird.update(deltaTime);           // wave animationland.update(deltaTime);            // move animation}@Overridepublic void render(float delta) {update(Gdx.graphics.getDeltaTime());Gdx.gl.glClearColor(0f, 0f, 0f, 1f);Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);batch.setProjectionMatrix(camera.combined);batch.begin();batch.draw(background, -viewWidth / 2, -Constants.VIEWPORT_HEIGHT / 2, viewWidth, Constants.VIEWPORT_HEIGHT);bird.render(batch);land.render(batch);batch.end();stage.act();stage.draw();}@Overridepublic void resize(int width, int height) {camera.viewportWidth = Constants.VIEWPORT_HEIGHT * width / height;camera.update();}@Overridepublic void pause() {}@Overridepublic void resume() {}@Overridepublic void dispose() {}
}

虽然PreviewScreen类中添加的东西比较多,但是并没有什么难以理解的内容。我们可以简单的介绍一下实现思路,首先我们维护了一个Stage对象用于承载相应的组件对象,如文本图片(“FlappyBird”)、开始按钮(play)、分数按钮(score)、还有速度按钮(rate)和版权图片。接着我们创建了一个Bird对象和一个Land对象,然后创建了一个相机对象,该对象用于渲染Bird和Land,并且我添加了update()方法更新Bird和Land对象。最后我们在hide()中释放了batch和stage两个对象。

  最重要的是我们为play按钮添加了点击监听器,如果点击该按钮我们就切换到游戏界面。
  接下来,我们创建FlappyBirdScreen界面:
package com.art.zok.flappybird;import com.art.zok.flappybird.game.WorldController;
import com.art.zok.flappybird.game.WorldRenderer;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.GL20;public class FlappyBirdScreen implements Screen {private WorldController worldController;private WorldRenderer worldRenderer;public boolean paused;@Overridepublic void show() {paused = false;worldController = new WorldController(this);worldRenderer =  new WorldRenderer(worldController);}@Overridepublic void hide() {worldController.dispose();worldRenderer.dispose();}@Overridepublic void render(float delta) {if(!paused) {float deltaTime = Math.min(Gdx.graphics.getDeltaTime(), 0.018f);worldController.update(deltaTime);}Gdx.gl.glClearColor(0x64/255.0f, 0x95/255.0f, 0xed/255.0f, 0xff/255.0f);  Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);  worldRenderer.render(); }@Overridepublic void resize(int width, int height) {worldRenderer.resize(width, height);}@Overridepublic void pause() {paused = true;}@Overridepublic void resume() {paused = false;}@Overridepublic void dispose() {}
}

上述的类和之前的FlappyBirdMain类几乎一样,稍有不同的是我们将初始化放在了show()方法中,将对象释放的代码放在了hide()方法中。并且我们不在resume()中重新加载资源了。我们之前说Android在应用进入后台后可能会释放部分资源,所以在重返前台时必须重新加载资源,但是这里指的是我们直接使用文件句柄加载资源的情况,如果我们使用AssetManger加载并管理资源就不需要担心这个问题了。

 最后,我们需要创建FlappyBirdGame类:
package com.art.zok.flappybird;import com.art.zok.flappybird.game.Assets;
import com.badlogic.gdx.Game;public class FlappyBirdGame extends Game {@Overridepublic void create() {setScreen(new WelcomeScreen(this));}@Overridepublic void dispose() {super.dispose();Assets.instance.dispose();}
}

该类的初始化化函数依然是create(),所以我们只是在create()初始化界面为欢迎界面,然后重载dispose()方法保证游戏退出时资源的释放。

添加音频响应
 最后我们要为应用添加声音。首先我们这里加载了五种声音资源,分别是die、hit、point、swooshing和wind。这五种资源分别是在应用死亡、碰撞、得分、按钮按下和煽动翅膀的时候触发,下面我们分别添加五种声音。
首先是die和hit在碰撞那一瞬间就应该触发,所以我们应该在Bird的contact()方法中播放hit和die:
// 通知碰撞事件public void contact() {contacted = true;Assets.instance.sounds.hit.play();Assets.instance.sounds.die.play();}

小鸟煽动翅膀应该是在每次触发跳跃时:

// 触发跳跃public void setJumping() {if(body != null) {body.setLinearVelocity(0, 35f);Assets.instance.sounds.wing.play();}}

下面是point,point应该在WorldController的score增加1时播放,所以:

// 计算分数private void calculateScore () {for(Pipe pipe : pipes.pipes) {if(pipe.position.x < bird.position.x) {if(pipe.getScore() == 1) {score += 1;Assets.instance.sounds.point.play();Gdx.app.debug(TAG, "Your current score:" + score);}}}}

最后是swooshing应该在每个自定义按钮被按下时调用,所以修改CustomButton:

public class CustomButton extends Button {...public CustomButton(AtlasRegion reg) {...addListener(new InputListener() {@Overridepublic boolean touchDown(InputEvent event, float x, float y,int pointer, int button) {Assets.instance.sounds.swooshing.play();return super.touchDown(event, x, y, pointer, button);}});}

我们在CustomButton的构造函数添加了一个监听器,并重写了touchDown方法,在该方法中我们只是播放了swooshing声音,这样凡事继承于CustomButton的按钮都会在按下时播放该声音。

  到现在,我们的游戏FlappyBird已经全部完成了,现在可以进行一次完整的测试了。
  下面是测试视频链接:
  http://pan.baidu.com/s/1hryFmoW
谢谢大家,我用了八章的篇幅介绍了如何使用LibGDX以及内置的BOX2D引擎重建FlappyBird游戏,在部分内容上可以并不是最佳的实现方法,所以希望大家不吝赐教,文中有所疏漏在所难免,希望各位读者能及时指出,本人将不胜感激。

转载于:https://my.oschina.net/u/2432369/blog/610410

LibGDX重建Flappy Bird——(8)屏幕切换与播放声音(终结)相关推荐

  1. LibGDX重建Flappy Bird——(4) 创建游戏对象

      在本章,我们将为Flappy Bird项目创建一个真正的场景.该游戏场景由几个具有共同属性和功能的游戏对象组成.但是,这些对象被渲染的方式和行为却各有不同, 简单的 对象直接渲染其所分配的纹理,复 ...

  2. LibGDX_8.2: LibGDX 项目实战: 开发跨平台 Flappy Bird(像素鸟)游戏

    本文链接: http://blog.csdn.net/xietansheng/article/details/50188319 LibGDX 基础教程(总目录) 声明: 游戏中使用到的图片和音频资源来 ...

  3. cmd小游戏_使用pygame制作Flappy bird小游戏

    原文链接: [Python]使用Pygame做一个Flappy bird小游戏(一)​mp.weixin.qq.com 最近看到很多大佬用强化学习玩Flappy bird.所以打算也上手玩一玩,但是苦 ...

  4. 使用cocos2d-x实现一款类似《Flappy Bird》的游戏

    最近,一只8比特位像素的小鸟霸占了IOS免费游戏排行榜的第一名,这款<Flappy Bird>游戏可谓是一夜爆红,简单并不粗糙的画面.超级玛丽游戏中的绿色通道.眼神有些呆滞的小鸟和几朵白云 ...

  5. 利用python开发的flappy bird 游戏

    python 中 pygame模块能让我们很方便的编写游戏,16年我用python 仿制了flappy bird 游戏,下面是游戏的完整代码以及素材,分享给大家. 第一个python文件,flappy ...

  6. Compose 正式发布,来打造一个 Flappy Bird! | 开发者说·DTalk

    本文原作者: 小虾米君,原文发布于: TechMerger https://mp.weixin.qq.com/s/Hpd2NF0hOw4xOo3wVb_VFg 之前看到 fun 神用 Compose ...

  7. cocos2dx-html5 实现网页版flappy bird游戏

    我也是第一次使用cocos2d_html5,对js和html5也不熟,看引擎自带的例子和引擎源码,边学边做,如果使用过cocos2d-x的话,完成这个游戏还是十分简单的.游戏体验地址: http:// ...

  8. flappy bird游戏源代码揭秘和下载

    背景: 最近火爆全球的游戏flappy bird让笔者叹为观止,于是花了一天的时间山寨了一个一模一样的游戏,现在把游戏的思路和源码分享出来,代码是基于javascript语言,cocos2d-x游戏引 ...

  9. java实现Flappy Bird游戏(附免费素材+代码+详细注解)

    目录 前言 一.实现效果 二.实现代码 前言 该小游戏我设计成BackGroundView类(背景图).Bird类.Pipe类,Main类四部分 图片素材地址:https://download.csd ...

最新文章

  1. 最实用的机器学习算法优缺点分析,没有比这篇说得更好了
  2. JavaScript学习--闭包
  3. 关于bcp的那些事儿
  4. Spring-Spring整合MyBatis详解
  5. drtek收音机使用说明_一百年前的便携式矿石收音机长啥样?这台1919年产品给你答案...
  6. jsp实现数据禁用和只读
  7. C点滴成海------Dev C++怎么修改成简体中文
  8. html语言怎样设置密码类型,html – 在使用contenteditable div时模仿密码类型输入
  9. 何小鹏退出UC浏览器母公司股东名单
  10. Map 3D 2012定制和应用程序开发Webcast将于明天(6月23号)进行
  11. selenium调用edge driver的坑
  12. 悠歌“即时”游戏回合文案
  13. 江苏科技大学MATLAB考试,江苏科技大学精品课程申报表.DOC
  14. 省市区级联SQL文件(MySQL)
  15. form表单提交时传递额外的参数
  16. php的 提示无效字符,ORA-00911: 无效字符
  17. Android 游戏破解修改金币
  18. SQLSyntaxErrorException: SELECT command denied to user ‘XXXXX‘@‘xxxx‘ for table ‘XXXX‘ 异常解决
  19. 漏洞检测~SQL注入
  20. 第14章 Linux终端设备驱动之终端设备

热门文章

  1. 2023最新豆十三沙雕视频教学全套课程/附带完整素材和插件
  2. java并发学习03---CountDownLatch 和 CyclicBarrier
  3. vscode 使用Java Extension Pack插件导致的Java版本不合问题的解决方案
  4. fiddle导出jmx文件_Fiddler导出JMX文件配置
  5. LVDS-CML-LVPECL 原理及转换
  6. 有趣的数学 依靠想象力的微积分
  7. 女生25岁转行软件测试晚吗?
  8. 华强北刷机经历 type password to decrypt storage
  9. defy手机 小技能 问题
  10. ubuntu18.04上安装ROS机器人操作系统