文档章节

LibGDX重建Flappy Bird——(2) 创建游戏框架

artzok
 artzok
发布于 2016/01/27 15:20
字数 2167
阅读 18
收藏 0

我们都知道Libgdx只是一个2D游戏框架,并不是游戏引擎,著名的AndEngine 2D游戏引擎就是基于Libgdx开发的,并且Flappy Bird原版就是使用AndEngine引擎开发的。所以我们必须先创建自己游戏引擎。

创建UML类图

首先我们需要分析Flappy Bird的项目架构。我们使用类图帮助我们可视化项目结构并理解项目构成。下面是该类图:

上图中FlappyBirdMain是项目主类,并且必须继承于LibGDX的ApplicationListener接口。主类包含了一个Assets类引用,Assets类被用于访问和组织游戏资源。主类还包含了WorldController和WorldRenderer两个类的句柄。

WorldController类包含了应用的初始化和所有游戏逻辑。它管理所有游戏对象并控制他们的行为。

WorldRenderer类被用于渲染每个游戏对象和需要的GUI信息。

倒数第二行的三个类继承于AbstractGameObject抽象类。他们共用一组接口,公共接口对所有游戏对象具有相同的操作功能,并能将对象渲染到场景。

下面是Flappy Bird的三个游戏对象:

  • Bird:玩家控制的角色

  • Pipe:我们看到的绿色管道。

  • Land:水平移动的地面。

实现Constants类

package com.art.zok.flappybird.util;

public class Constants {

	// 视口宽度为20米
	public static final float VIEWPORT_WIDTH =  50f;
	// 视口高度为20米
	public static final float VIEWPORT_HEIGHT = 50f;
}

首先我们将视口尺寸定义为50*50米,其实这里的“米”是一个相对单位。比如说我们在一个分辨率为480*800的窗口中,我们将视口设定为50*50米,则竖直方向上每米表示800/50像素,水平方向每米表示480/50像素。我们发现如果将视口设定为固定值,则水平和竖直方向上的单位长度将会不统一,放心,后面我们将会考虑到该问题并会得到有效解决。还有,这里为什么设置为50米呢?其实理论上视口尺寸可以被设定为任意值,因为他只是一个相对单位。但是因为我们后续将使用BOX2D作为碰撞检测和物理模拟的引擎,而BOX2D有一个不成文的规定,当物体的尺寸位于0.01-10米内时可以获得最大的模拟性能。

接下来我们将实现FlappyBirdMain类

package com.art.zok.flappybird;
  
import com.badlogic.gdx.ApplicationListener;
  
public class FlappyBirdMain implements ApplicationListener{  
    private static final String TAG =   
    		FlappyBirdMain.class.getName();  
      
    private WorldController worldController;  
    private WorldRenderer worldRenderer;  
      
    @Override public void create() { }  
    @Override public void resize(int width, int height) { }  
    @Override public void render() { }  
    @Override public void pause() { }  
    @Override public void resume() { }  
    @Override public void dispose() { }  
}

FlappyBirdMain类实现了ApplicationListener接口,那么现在就成了LibGDX的主类。
WorldController引用和WorldRenferer引用使FlappyBirdMain能够更新并控制游戏的运行,并且将游戏的当前状态渲染到屏幕上。
TAG常量被设置为类名,表示一个独一无二的标签。它是为了记录日志而创建的。

实现WorldController类

package com.art.zok.flappybird.game;  
  
public class WorldController {  
    private static final String TAG =   
            WorldController.class.getName();  
    public WorldController() { }  
      
    private void init() { }  
      
    public void update(float deltaTime) { }  
}

该类使用一个私有的init()方法初始化内容。虽然所有的初始化代码都可以放在构造函数中,但是将初始化代码放在独立的方法中是非常有好处的,当任何时候我们想重置一个对象,而我们又不想完全重建它,或者根本没有必要完全重建,这时独立的初始化函数就节省了许多资源并提高了游戏执行效率。还有,这个方法还可以大大减少Garbage Collector(GC)引起的中断。相反,初始化函数可以让我们重新激活存在的对象,而且这也是我们推荐的方法,因为这可以在有限的资源最大化执行效率。对于资源量很小的移动设备,这点显得更重要了。

update()方法包含了游戏逻辑代码,他可能会在每秒被访问几百次。他需要一个增量时间作为参数,并且他将根据该时间增量更新游戏。

实现WorldRenderer类

package com.art.zok.flappybird.game;

import com.badlogic.gdx.graphics.OrthographicCamera;  
import com.badlogic.gdx.graphics.g2d.SpriteBatch;  
import com.badlogic.gdx.utils.Disposable;  
  
public class WorldRenderer implements Disposable {  
      
    private OrthographicCamera camera;  
    private SpriteBatch batch;  
    private WorldController worldController;  
      
    public WorldRenderer(WorldController worldController) { }  
    private void init() { }  
      
    public void render() { }  
    public void resize(int width, int height) { }  
      
    @Override  
    public void dispose() { }    
}

该类也包含一个初始化函数init()。其中还包含了一个render()方法,在该方法中我们定义了所有对象渲染的逻辑。当窗口尺寸发生改变时包括应用启动时窗口的改变都应该调用resize()以适应最新配置。

渲染过程使用一个正交投影相机完成二维渲染。幸运的是,LibGDX提供了一个OrthographicCamera类简化了二维渲染任务。然而,SpriteBatch类才是真正的工作者,它按照相机设置(位置,投影,缩放等等)将所有对象渲染到屏幕上。因为SpriteBatch实现了Disposable接口,因此当SpriteBatch实例不再需要的时候应当呼叫dispose()方法释放其占用的内存。为了达到这一目的,我们也为WorldRenerer类实现了Disposable接口,并在dispose方法中调用SpriteBatch的dispose方法,这使得当FlappyBirdMain类的dispose()方法被调用后可以很容易的执行有序释放(清理)过程。

需要注意,该类在构造函数中需要引用一个WorldController实例以便后续渲染时可以访问控制器管理的所有对象。

组织架构

修改FlappyBirdMain类的create方法:

@Override 
    public void create() { 
    	// 设定日志记录级别
    	Gdx.app.setLogLevel(Application.LOG_DEBUG);
    	worldController = new WorldController();
    	worldRenderer = new WorldRenderer(worldController);
    }

首先,我们设置了日志记录的级别。然后创建了WorldController和WorldRenderer类实例。

接下来修改该类的render()方法:

@Override public void render() { 
    	// 根据最后一帧到当前的增量时间跟新游戏
    	worldController.update(Gdx.graphics.getDeltaTime());
    	// 设定清屏颜色为浅蓝色
    	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(); 
    }

首先我们根据增量时间更新游戏内容。Gdx.graphics模块提供了getDeltaTime()方法获得当前的增量时间。接下来,LibGDX通过Gdx.gl模块直接调用两个OpenGL底层方法。第一个方法是glClearColor()设定清屏时使用的颜色,该方法需要使用red,green,blue和alpha(RGBA)四个值作为参数。每个通道的值需要使用一个介于0-1的浮点数表示8位二进制数的范围。第二个方法glClear()首先擦除屏幕上显示的所有内容然后使用上述设置的颜色填充整个屏幕。最后一步,将更新后的游戏世界渲染到屏幕上。
接下来修改resize()方法

@Override   
public void resize(int width, int height) {   
    worldRenderer.resize(width, height);  
}

无论任何时候窗口尺寸发生变化,ApplicationListener接口的resize()方法将被调用。因为窗口尺寸改变事件与场景渲染息息相关,因此我们将其处理代码放在WorldRenderer类中。所以这里只需要简单的调用worldRenderer.resize方法。

修改dispose()方法

@Override public void dispose() {   
    worldRenderer.dispose();  
}

当资源不在使用时,系统将调用FlappyBirdMain.dispose()方法,该方法总是调用worldRenderer.dispose()方法释放资源。
因为Android系统还具有两个额外的系统事件,分别是pause和resume。我们希望当接收到pause或resume事件时我们的应用可以暂停和恢复。为了达到这个目的,我们需要创建一个paused成员变量。

private boolean paused; 

然后修改create()和render()方法,修改完成之后如下:

@Override   
public void create() {   
    // 将LibGDX的日志级别设定为DEBUG  
    Gdx.app.setLogLevel(Application.LOG_DEBUG);  
    // 初始化控制器和渲染器  
    worldController = new WorldController();  
    worldRenderer = new WorldRenderer(worldController);  
    // 启动时激活游戏  
    paused = false;  
}

@Override   
public void render() {   
    // 当游戏暂停时不更新游戏世界  
    if(!paused) {  
        // 根据最后一帧到当前帧的增量时间更新游戏世界  
        worldController.update(Gdx.graphics.getDeltaTime());  
    }  
    // 设置清屏颜色为:浅蓝色  
    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();  
}

最后响应暂停和恢复事件,在pause()和resume()方法添加相应的代码:

@Override   
public void pause() {   
    paused = true;  
}  
  
@Override   
public void resume() {   
    paused = false;  
}

到现在为止我们的框架就已经建立完毕了,现在就可以尝试运行了。


本章内容到现在就已经全部介绍完了,本章我们分析并创建了游戏的框架。下一章我们将介绍资源的打包方法。





















本文转载自:http://blog.csdn.net/artzok/article/details/50549467

artzok
粉丝 2
博文 9
码字总数 0
作品 0
马鞍山
私信 提问
LibGDX 跨平台游戏开发基础教程(总目录)

本文链接: http://blog.csdn.net/xietansheng/article/details/50185655 前言 本套教程讲解了 LibGDX 的基础开发,从环境搭建,到基础 API 的使用,最后再到 2048 和 Flappy Bird 两个游戏的...

xietansheng
2015/12/09
0
0
LibGDX_8.2: LibGDX 项目实战: 开发跨平台 Flappy Bird(像素鸟)游戏

本文链接: http://blog.csdn.net/xietansheng/article/details/50188319 LibGDX 基础教程(总目录) 声明: 游戏中使用到的图片和音频资源来自网络,资源版权和游戏创意属原作者,这里仅供学...

xietansheng
2015/12/07
0
0
Android 5.0 开发者预览版隐藏Flappy Bird克隆

在发布Android 5.0 Lollipop之后, Google发布了开发者预览版更新,加入了一些用户以后将会在正式版本中使用到的新功能,它还隐藏了一个至今最大的Android彩蛋——一个可玩的Flappy Bird克隆...

oschina
2014/10/21
4.8K
9
cocos2d-x支持c++、js、lua开发

作者:左文 链接:https://www.zhihu.com/question/21130385/answer/21789568 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 纯属个人观点 1 Unity3d支...

壹峰
2017/10/18
0
0
不到24小时!开发者用 Swift 语言复制 Flappy Bird

今年初,一款像素风格的超难小游戏 Flappy Bird 迅速流行起来,随后开发者认为 Flappy Bird 让人太沉迷,将游戏下架。Flappy Bird 下架后,App Store 中出现了大量山寨 Flappy Bird 的游戏。...

oschina
2014/06/04
9.8K
66

没有更多内容

加载失败,请刷新页面

加载更多

利用mybatis generator生成实体类、Mapper接口以及对应的XML文件

项目中通常会遇到数据的持久化,如果是采用mybatis的orm,就会涉及到生成xml的问题,刚好mybatis官网提供了这么个插件MyBatis Generator,效果简直是棒呆。 1. 首先需要在build.gradle文件中...

啊哈关关
今天
2
0
SpringSocial相关的知识点

使用SprigSocial开发第三方登录 核心类 ServiceProvider(AbstractOauth2ServiceProvider):主要负责实现server提供商(例如QQ,微信等共有的东西),默认实现类是AbstractOauth2ServiceProvider...

chendom
今天
3
0
Java并发之AQS详解

一、概述   谈到并发,不得不谈ReentrantLock;而谈到ReentrantLock,不得不谈AbstractQueuedSynchronizer(AQS)!   类如其名,抽象的队列式的同步器,AQS定义了一套多线程访问共享资源...

群星纪元
昨天
4
0
Fabric-sdk-java最新教程

Fabric Java SDK是Fabric区块链官方提供的用于Java应用开发的SDK,全称为Fabric-sdk-java,网上可用资料不多,本文列出了精心整理的针对Fabric Java SDK的最新精选教程。 如果希望快速掌握F...

汇智网教程
昨天
3
0
react 子组件监听props 变化

componentWillReceiveProps //已经被废弃 getDerivedStateFromProps// 推荐使用//如果条件不存在必须要返回null static getDerivedStateFromProps(props, current_stat...

一箭落旄头
昨天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部