在本章,我们将为Flappy Bird项目创建一个真正的场景。该游戏场景由几个具有共同属性和功能的游戏对象组成。但是,这些对象被渲染的方式和行为却各有不同,
简单的
对象直接渲染其所分配的纹理,复杂的对象可能需要多个纹理组合渲染。
创建游戏对象
首先创建AbstractGameObject类,并添加下面代码:
package com.art.zok.flappybird.game.object;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
public abstract class AbstractGameObject {
public Vector2 position;
public Vector2 dimension;
public Vector2 origin;
public Vector2 scale;
public float rotation;
public Body body;
public AbstractGameObject() {
position = new Vector2();
dimension = new Vector2(1, 1);
origin = new Vector2();
scale = new Vector2(1, 1);
rotation = 0;
}
public void update(float deltaTime) {
if(body != null) {
position.set(body.getPosition());
rotation = body.getAngle() * MathUtils.radiansToDegrees;
}
}
public abstract void render(SpriteBatch batch);
}
该类存储了对象的位置、尺寸、原点、缩放因子和旋转角度。该类还包含两个方法,update()和render(),这两个方法将分别在控制器和渲染器中调用。因为我们创建的每个对象都需要参与碰撞检测,因此我们在该类中还包含一个BOX2D的Body类型成员变量,在update()中,如果body不等于null则说明我们需要使用BOX2D进行物理仿真,然后我们使用body对象的位置和旋转角度更新该对象的位置和旋转角度。对于渲染方法,我们会为每个对象提供一个特定的实现,因此我们将render()定义为abstract。
这里有个问题,因为BOX2D在LIBGDX中属于扩展内容,因此我们需要先添加相关的库文件,才能消除上述代码存在的错误。添加BOX2D扩展可分为以下几个步骤:
- 将gdx-box2d.jar拷贝到FlappyBird项目(libs文件中)。
- 将gdx-box2d-native.jar拷贝到FlappyBird-desktop项目。
- 将armeabi、armeabi-v7a和x86三个文件夹下的libgdx-box2d.so拷贝到FlappyBird-android项目的相应文件夹中。
- 将各项目新添加的库文件加入构建路径中。
创建Brid对象
package com.art.zok.flappybird.game.object;
import com.art.zok.flappybird.game.Assets;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.physics.box2d.World;
import com.badlogic.gdx.utils.Array;
public class Bird extends AbstractGameObject {
protected enum WAVE_STATE {
WAVE_FALLING, WAVE_RISE
}
private Array<AtlasRegion> birds;
private float animDuration;
private Animation birdAnimation;
private TextureRegion currentFrame;
private float max_wave_height;
private float min_wave_height;
private WAVE_STATE waveState = WAVE_STATE.WAVE_RISE;
public Bird() {
init((int) (Math.random() * 3));
}
// 初始化
public void init(int selected) {
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(-dimension.x * 1.5f, dimension.y / 2);
max_wave_height = position.y + 0.7f;
min_wave_height = position.y - 0.7f;
}
@Override
public void update(float deltaTime) {
super.update(deltaTime);
if (body == null) {
if (waveState == WAVE_STATE.WAVE_FALLING)
position.y -= 0.05f;
else if (waveState == WAVE_STATE.WAVE_RISE) {
position.y += 0.05f;
}
if (position.y < min_wave_height) {
waveState = WAVE_STATE.WAVE_RISE;
} else if (position.y > max_wave_height) {
waveState = WAVE_STATE.WAVE_FALLING;
}
}
}
@Override
public void render(SpriteBatch batch) {
animDuration += Gdx.graphics.getDeltaTime();
currentFrame = birdAnimation.getKeyFrame(animDuration);
batch.draw(currentFrame.getTexture(), position.x - dimension.x / 2, position.y - dimension.y / 2,
dimension.x / 2, dimension.y / 2, dimension.x, dimension.y, scale.x, scale.y, rotation,
currentFrame.getRegionX(), currentFrame.getRegionY(), currentFrame.getRegionWidth(),
currentFrame.getRegionHeight(), false, false);
}
}
首先,毋庸置疑Bird对象是一个动画对象,创建动画我们需要使用Animation类。Animation的构造函数需要一组动画帧作为参数,在Bird的
构造函数中我们首先产生了一个范围在[0-3)之内的随机整数作为init()方法的参数。在init()方法中我们首先使用传递进来的参数从三个可选的资源中选择一个作为Bird对象的动画资源。然后我们创建了一个周期为0.1秒的birdAnimation动画对象,并将其设定为循环
模式
。接下来我们设置了对象的尺寸和位置,这些魔法数是经过比例测算而来的,没有什么借鉴的价值,你也可以设定为其他值。
在ren
der方法中,我们首先根据动画经过的时间获得当前帧的纹理,然后使用SpriteBatch.draw方法渲染该帧纹理,注意我们为draw方法的x,y参数传递的是position.x - dimension.x / 2和position.y - dimension.y / 2,也就是说渲染的矩形区域始终以position为中心,这样做是有原因的,后面我们使用BOX2D模拟时获得的旋转角度都是以position为原点的,因此为了更方便我们使用position位置表示Bird对象的中心位置。
从官方Flappy Bird游戏我们可以知道,当没有开始游戏时,Bird对象具有一个上下波动的动画状态。所以我们这里会将每个对象分为未开使模拟(仿真)和已经开始模两个状态进行处理。我们为Bird对象添加了两个枚举常量WAVE_FALLING, WAVE_RISE就是为这一动画服务的。在init()方法中我们初始化了max_wave_height、min_wave_height两个值分别表示波动动画的最大高度和最小高度。在update()方法中,如果body等于null表示没有开始模拟,则执行波动动画,该波动效果是通过两个波动状态互相转换和y轴坐标位置的持续变化实现的。
测试Bird类
测试Bird类之前我们还必须做一些准备工作。首先,修改FlappyBirdMain.Create()方法: