[libgdx游戏开发教程]使用Libgdx进行游戏开发(6)-添加主角和道具

时间:2023-03-09 19:36:51
[libgdx游戏开发教程]使用Libgdx进行游戏开发(6)-添加主角和道具

如前所述,我们的主角是兔子头。接下来我们实现它。

首先对AbstractGameObject添加变量并初始化:

public Vector2 velocity;
public Vector2 terminalVelocity;
public Vector2 friction;
public Vector2 acceleration;
public Rectangle bounds;

分别是速度,极限速度,摩擦力,加速度和边界。

然后我们需要加点物理仿真:

protected void updateMotionX(float deltaTime) {
if (velocity.x != 0) {
// Apply friction
if (velocity.x > 0) {
velocity.x = Math.max(velocity.x - friction.x * deltaTime, 0);
} else {
velocity.x = Math.min(velocity.x + friction.x * deltaTime, 0);
}
}
// Apply acceleration
velocity.x += acceleration.x * deltaTime;
// Make sure the object's velocity does not exceed the
// positive or negative terminal velocity
velocity.x = MathUtils.clamp(velocity.x, -terminalVelocity.x,
terminalVelocity.x);
} protected void updateMotionY(float deltaTime) {
if (velocity.y != 0) {
// Apply friction
if (velocity.y > 0) {
velocity.y = Math.max(velocity.y - friction.y * deltaTime, 0);
} else {
velocity.y = Math.min(velocity.y + friction.y * deltaTime, 0);
}
}
// Apply acceleration
velocity.y += acceleration.y * deltaTime;
// Make sure the object's velocity does not exceed the
// positive or negative terminal velocity
velocity.y = MathUtils.clamp(velocity.y, -terminalVelocity.y,
terminalVelocity.y);
}

然后在update里调用:

updateMotionX(deltaTime);
updateMotionY(deltaTime);
// Move to new position
position.x += velocity.x * deltaTime;
position.y += velocity.y * deltaTime;

这样所有的子类都拥有了这些物理特性。

添加金币:

package com.packtpub.libgdx.canyonbunny.game.objects;

import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.packtpub.libgdx.canyonbunny.game.Assets; public class GoldCoin extends AbstractGameObject {
private TextureRegion regGoldCoin;
public boolean collected; public GoldCoin() {
init();
} private void init() {
dimension.set(0.5f, 0.5f);
regGoldCoin = Assets.instance.goldCoin.goldCoin;
// Set bounding box for collision detection
bounds.set(0, 0, dimension.x, dimension.y);
collected = false;
} public void render(SpriteBatch batch) {
if (collected)
return;
TextureRegion reg = null;
reg = regGoldCoin;
batch.draw(reg.getTexture(), position.x, position.y, origin.x,
origin.y, dimension.x, dimension.y, scale.x, scale.y, rotation,
reg.getRegionX(), reg.getRegionY(), reg.getRegionWidth(),
reg.getRegionHeight(), false, false);
} public int getScore() {
return 100;
}
}

金币就一个图片,供玩家收集,收集一个就得100分,然后消失。

创建羽毛:

package com.packtpub.libgdx.canyonbunny.game.objects;

import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.packtpub.libgdx.canyonbunny.game.Assets; public class Feather extends AbstractGameObject {
private TextureRegion regFeather;
public boolean collected; public Feather() {
init();
} private void init() {
dimension.set(0.5f, 0.5f);
regFeather = Assets.instance.feather.feather;
// Set bounding box for collision detection
bounds.set(0, 0, dimension.x, dimension.y);
collected = false;
} public void render(SpriteBatch batch) {
if (collected)
return;
TextureRegion reg = null;
reg = regFeather;
batch.draw(reg.getTexture(), position.x, position.y, origin.x,
origin.y, dimension.x, dimension.y, scale.x, scale.y, rotation,
reg.getRegionX(), reg.getRegionY(), reg.getRegionWidth(),
reg.getRegionHeight(), false, false);
} public int getScore() {
return 250;
}
}

它的代码几乎和金币一样,就是分数高点。

创建兔子头:

我们需要根据羽毛来激活是否兔子头应该powerUp,当powerUp的时候,我们把兔子头变成黄色。

package com.packtpub.libgdx.canyonbunny.game.objects;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.packtpub.libgdx.canyonbunny.game.Assets;
import com.packtpub.libgdx.canyonbunny.util.Constants; public class BunnyHead extends AbstractGameObject {
public static final String TAG = BunnyHead.class.getName();
private final float JUMP_TIME_MAX = 0.3f;
private final float JUMP_TIME_MIN = 0.1f;
private final float JUMP_TIME_OFFSET_FLYING = JUMP_TIME_MAX - 0.018f; public enum VIEW_DIRECTION {
LEFT, RIGHT
} public enum JUMP_STATE {
GROUNDED, FALLING, JUMP_RISING, JUMP_FALLING
} private TextureRegion regHead;
public VIEW_DIRECTION viewDirection;
public float timeJumping;
public JUMP_STATE jumpState;
public boolean hasFeatherPowerup;
public float timeLeftFeatherPowerup; public BunnyHead() {
init();
} public void init() {
dimension.set(1, 1);
regHead = Assets.instance.bunny.head;
// Center image on game object
origin.set(dimension.x / 2, dimension.y / 2);
// Bounding box for collision detection
bounds.set(0, 0, dimension.x, dimension.y);
// Set physics values
terminalVelocity.set(3.0f, 4.0f);
friction.set(12.0f, 0.0f);
acceleration.set(0.0f, -25.0f);
// View direction
viewDirection = VIEW_DIRECTION.RIGHT;
// Jump state
jumpState = JUMP_STATE.FALLING;
timeJumping = 0;
// Power-ups
hasFeatherPowerup = false;
timeLeftFeatherPowerup = 0;
} public void setJumping(boolean jumpKeyPressed) {
switch (jumpState) {
case GROUNDED: // Character is standing on a platform
if (jumpKeyPressed) {
// Start counting jump time from the beginning
timeJumping = 0;
jumpState = JUMP_STATE.JUMP_RISING;
}
break;
case JUMP_RISING: // Rising in the air
if (!jumpKeyPressed)
jumpState = JUMP_STATE.JUMP_FALLING;
break;
case FALLING:// Falling down
case JUMP_FALLING: // Falling down after jump
if (jumpKeyPressed && hasFeatherPowerup) {
timeJumping = JUMP_TIME_OFFSET_FLYING;
jumpState = JUMP_STATE.JUMP_RISING;
}
break;
}
} public void setFeatherPowerup(boolean pickedUp) {
hasFeatherPowerup = pickedUp;
if (pickedUp) {
timeLeftFeatherPowerup = Constants.ITEM_FEATHER_POWERUP_DURATION;
}
} public boolean hasFeatherPowerup() {
return hasFeatherPowerup && timeLeftFeatherPowerup > 0;
} @Override
public void update(float deltaTime) {
super.update(deltaTime);
if (velocity.x != 0) {
viewDirection = velocity.x < 0 ? VIEW_DIRECTION.LEFT
: VIEW_DIRECTION.RIGHT;
}
if (timeLeftFeatherPowerup > 0) {
timeLeftFeatherPowerup -= deltaTime;
if (timeLeftFeatherPowerup < 0) {
// disable power-up
timeLeftFeatherPowerup = 0;
setFeatherPowerup(false);
}
}
} @Override
protected void updateMotionY(float deltaTime) {
switch (jumpState) {
case GROUNDED:
jumpState = JUMP_STATE.FALLING;
break;
case JUMP_RISING:
// Keep track of jump time
timeJumping += deltaTime;
// Jump time left?
if (timeJumping <= JUMP_TIME_MAX) {
// Still jumping
velocity.y = terminalVelocity.y;
}
break;
case FALLING:
break;
case JUMP_FALLING:
// Add delta times to track jump time
timeJumping += deltaTime;
// Jump to minimal height if jump key was pressed too short
if (timeJumping > 0 && timeJumping <= JUMP_TIME_MIN) {
// Still jumping
velocity.y = terminalVelocity.y;
}
}
if (jumpState != JUMP_STATE.GROUNDED)
super.updateMotionY(deltaTime);
} @Override
public void render(SpriteBatch batch) {
TextureRegion reg = null;
// Set special color when game object has a feather power-up
if (hasFeatherPowerup)
batch.setColor(1.0f, 0.8f, 0.0f, 1.0f);
// Draw image
reg = regHead;
batch.draw(reg.getTexture(), position.x, position.y, origin.x,
origin.y, dimension.x, dimension.y, scale.x, scale.y, rotation,
reg.getRegionX(), reg.getRegionY(), reg.getRegionWidth(),
reg.getRegionHeight(), viewDirection == VIEW_DIRECTION.LEFT,
false);
// Reset color to white
batch.setColor(1, 1, 1, 1);
}
}

不太明白的可以看流程图:

[libgdx游戏开发教程]使用Libgdx进行游戏开发(6)-添加主角和道具

setJumping的流程:

[libgdx游戏开发教程]使用Libgdx进行游戏开发(6)-添加主角和道具

更新岩石:

    public void setLength(int length) {
this.length = length;
// Update bounding box for collision detection
bounds.set(0, 0, dimension.x * length, dimension.y);
}

修改Level的init()和render()

package com.packtpub.libgdx.canyonbunny.game;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.utils.Array;
import com.packtpub.libgdx.canyonbunny.game.objects.AbstractGameObject;
import com.packtpub.libgdx.canyonbunny.game.objects.BunnyHead;
import com.packtpub.libgdx.canyonbunny.game.objects.Clouds;
import com.packtpub.libgdx.canyonbunny.game.objects.Feather;
import com.packtpub.libgdx.canyonbunny.game.objects.GoldCoin;
import com.packtpub.libgdx.canyonbunny.game.objects.Mountains;
import com.packtpub.libgdx.canyonbunny.game.objects.Rock;
import com.packtpub.libgdx.canyonbunny.game.objects.WaterOverlay; public class Level {
public static final String TAG = Level.class.getName(); public enum BLOCK_TYPE {
EMPTY(0, 0, 0), // black
ROCK(0, 255, 0), // green
PLAYER_SPAWNPOINT(255, 255, 255), // white
ITEM_FEATHER(255, 0, 255), // purple
ITEM_GOLD_COIN(255, 255, 0); // yellow
private int color; private BLOCK_TYPE(int r, int g, int b) {
color = r << 24 | g << 16 | b << 8 | 0xff;
} public boolean sameColor(int color) {
return this.color == color;
} public int getColor() {
return color;
}
}
public BunnyHead bunnyHead;
public Array<GoldCoin> goldcoins;
public Array<Feather> feathers; // objects
public Array<Rock> rocks;
// decoration
public Clouds clouds;
public Mountains mountains;
public WaterOverlay waterOverlay; public Level(String filename) {
init(filename);
} private void init(String filename) {
// player character
bunnyHead = null;
// objects
rocks = new Array<Rock>();
goldcoins = new Array<GoldCoin>();
feathers = new Array<Feather>();
// load image file that represents the level data
Pixmap pixmap = new Pixmap(Gdx.files.internal(filename));
// scan pixels from top-left to bottom-right
int lastPixel = -1;
for (int pixelY = 0; pixelY < pixmap.getHeight(); pixelY++) {
for (int pixelX = 0; pixelX < pixmap.getWidth(); pixelX++) {
AbstractGameObject obj = null;
float offsetHeight = 0;
// height grows from bottom to top
float baseHeight = pixmap.getHeight() - pixelY;
// get color of current pixel as 32-bit RGBA value
int currentPixel = pixmap.getPixel(pixelX, pixelY);
// find matching color value to identify block type at (x,y)
// point and create the corresponding game object if there is
// a match
// empty space
if (BLOCK_TYPE.EMPTY.sameColor(currentPixel)) {
// do nothing
}
// rock
else if (BLOCK_TYPE.ROCK.sameColor(currentPixel)) {
if (lastPixel != currentPixel) {
obj = new Rock();
float heightIncreaseFactor = 0.25f;
offsetHeight = -2.5f;
obj.position.set(pixelX, baseHeight * obj.dimension.y
* heightIncreaseFactor + offsetHeight);
rocks.add((Rock) obj);
} else {
rocks.get(rocks.size - 1).increaseLength(1);
}
}
// player spawn point
else if (BLOCK_TYPE.PLAYER_SPAWNPOINT.sameColor(currentPixel)) {
obj = new BunnyHead();
offsetHeight = -3.0f;
obj.position.set(pixelX,baseHeight * obj.dimension.y
+ offsetHeight);
bunnyHead = (BunnyHead)obj;
}
// feather
else if (BLOCK_TYPE.ITEM_FEATHER.sameColor(currentPixel)) {
obj = new Feather();
offsetHeight = -1.5f;
obj.position.set(pixelX,baseHeight * obj.dimension.y
+ offsetHeight);
feathers.add((Feather)obj);
}
// gold coin
else if (BLOCK_TYPE.ITEM_GOLD_COIN.sameColor(currentPixel)) {
obj = new GoldCoin();
offsetHeight = -1.5f;
obj.position.set(pixelX,baseHeight * obj.dimension.y
+ offsetHeight);
goldcoins.add((GoldCoin)obj);
}
// unknown object/pixel color
else {
int r = 0xff & (currentPixel >>> 24); // red color channel
int g = 0xff & (currentPixel >>> 16); // green color channel
int b = 0xff & (currentPixel >>> 8); // blue color channel
int a = 0xff & currentPixel; // alpha channel
Gdx.app.error(TAG, "Unknown object at x<" + pixelX + "> y<"
+ pixelY + ">: r<" + r + "> g<" + g + "> b<" + b
+ "> a<" + a + ">");
}
lastPixel = currentPixel;
}
}
// decoration
clouds = new Clouds(pixmap.getWidth());
clouds.position.set(0, 2);
mountains = new Mountains(pixmap.getWidth());
mountains.position.set(-1, -1);
waterOverlay = new WaterOverlay(pixmap.getWidth());
waterOverlay.position.set(0, -3.75f);
// free memory
pixmap.dispose();
Gdx.app.debug(TAG, "level '" + filename + "' loaded");
} public void update (float deltaTime) {
bunnyHead.update(deltaTime);
for(Rock rock : rocks)
rock.update(deltaTime);
for(GoldCoin goldCoin : goldcoins)
goldCoin.update(deltaTime);
for(Feather feather : feathers)
feather.update(deltaTime);
clouds.update(deltaTime);
} public void render(SpriteBatch batch) {
// Draw Mountains
mountains.render(batch);
// Draw Rocks
for (Rock rock : rocks)
rock.render(batch);
// Draw Gold Coins
for (GoldCoin goldCoin : goldcoins)
goldCoin.render(batch);
// Draw Feathers
for (Feather feather : feathers)
feather.render(batch);
// Draw Player Character
bunnyHead.render(batch);
// Draw Water Overlay
waterOverlay.render(batch);
// Draw Clouds
clouds.render(batch);
}
}

最后修改controller的update:level.update(deltaTime);

增加游戏逻辑:在相应的object加上碰撞方法和相应处理

也可以都交给controller,比如在WorldController中加入:

// Rectangles for collision detection
private Rectangle r1 = new Rectangle();
private Rectangle r2 = new Rectangle(); private void onCollisionBunnyHeadWithRock(Rock rock) {
}; private void onCollisionBunnyWithGoldCoin(GoldCoin goldcoin) {
}; private void onCollisionBunnyWithFeather(Feather feather) {
}; private void testCollisions() {
r1.set(level.bunnyHead.position.x, level.bunnyHead.position.y,
level.bunnyHead.bounds.width, level.bunnyHead.bounds.height);
// Test collision: Bunny Head <-> Rocks
for (Rock rock : level.rocks) {
r2.set(rock.position.x, rock.position.y, rock.bounds.width,
rock.bounds.height);
if (!r1.overlaps(r2))
continue;
onCollisionBunnyHeadWithRock(rock);
// IMPORTANT: must do all collisions for valid
// edge testing on rocks.
}
// Test collision: Bunny Head <-> Gold Coins
for (GoldCoin goldcoin : level.goldcoins) {
if (goldcoin.collected)
continue;
r2.set(goldcoin.position.x, goldcoin.position.y,
goldcoin.bounds.width, goldcoin.bounds.height);
if (!r1.overlaps(r2))
continue;
onCollisionBunnyWithGoldCoin(goldcoin);
break;
}
// Test collision: Bunny Head <-> Feathers
for (Feather feather : level.feathers) {
if (feather.collected)
continue;
r2.set(feather.position.x, feather.position.y,
feather.bounds.width, feather.bounds.height);
if (!r1.overlaps(r2))
continue;
onCollisionBunnyWithFeather(feather);
break;
}
}

现在3个碰撞事件都是空的,实现逻辑是这样的:

private void onCollisionBunnyHeadWithRock(Rock rock) {
BunnyHead bunnyHead = level.bunnyHead;
float heightDifference = Math.abs(bunnyHead.position.y
- (rock.position.y + rock.bounds.height));
if (heightDifference > 0.25f) {
boolean hitLeftEdge = bunnyHead.position.x > (rock.position.x + rock.bounds.width / 2.0f);
if (hitLeftEdge) {
bunnyHead.position.x = rock.position.x + rock.bounds.width;
} else {
bunnyHead.position.x = rock.position.x - bunnyHead.bounds.width;
}
return;
}
switch (bunnyHead.jumpState) {
case GROUNDED:
break;
case FALLING:
case JUMP_FALLING:
bunnyHead.position.y = rock.position.y + bunnyHead.bounds.height
+ bunnyHead.origin.y;
bunnyHead.jumpState = JUMP_STATE.GROUNDED;
break;
case JUMP_RISING:
bunnyHead.position.y = rock.position.y + bunnyHead.bounds.height
+ bunnyHead.origin.y;
break;
}
} private void onCollisionBunnyWithGoldCoin(GoldCoin goldcoin) {
goldcoin.collected = true;
score += goldcoin.getScore();
Gdx.app.log(TAG, "Gold coin collected");
} private void onCollisionBunnyWithFeather(Feather feather) {
feather.collected = true;
score += feather.getScore();
level.bunnyHead.setFeatherPowerup(true);
Gdx.app.log(TAG, "Feather collected");
}

试试效果,在WorldController的update加入:testCollisions();

跑起来,看看:

[libgdx游戏开发教程]使用Libgdx进行游戏开发(6)-添加主角和道具

操作键:上,下,左,右,逗号,点等键。

最后,我们跟前面一样,让摄像机跟随兔子头:在Controller的initLevel中添加cameraHelper.setTarget(level.bunnyHead);

修改handleDebugInput()和keyUp():

@Override
public boolean keyUp(int keycode) {
if (keycode == Keys.R) {
init();
Gdx.app.debug(TAG, "Game World Resetted!");
}// Toggle camera follow
else if (keycode == Keys.ENTER) {
cameraHelper.setTarget(cameraHelper.hasTarget() ? null
: level.bunnyHead);
Gdx.app.debug(TAG,
"Camera follow enabled: " + cameraHelper.hasTarget());
}
return false;
} private void handleInputGame(float deltaTime) {
if (cameraHelper.hasTarget(level.bunnyHead)) {
// Player Movement
if (Gdx.input.isKeyPressed(Keys.LEFT)) {
level.bunnyHead.velocity.x = -level.bunnyHead.terminalVelocity.x;
} else if (Gdx.input.isKeyPressed(Keys.RIGHT)) {
level.bunnyHead.velocity.x = level.bunnyHead.terminalVelocity.x;
} else {
// Execute auto-forward movement on non-desktop platform
if (Gdx.app.getType() != ApplicationType.Desktop) {
level.bunnyHead.velocity.x = level.bunnyHead.terminalVelocity.x;
}
}
// Bunny Jump
if (Gdx.input.isTouched() || Gdx.input.isKeyPressed(Keys.SPACE))
level.bunnyHead.setJumping(true);
else
level.bunnyHead.setJumping(false);
}
}
private void handleDebugInput(float deltaTime) {
if (Gdx.app.getType() != ApplicationType.Desktop)
return;
if (!cameraHelper.hasTarget(level.bunnyHead)) {
// Camera Controls (move)
float camMoveSpeed = 5 * deltaTime;
float camMoveSpeedAccelerationFactor = 5;
if (Gdx.input.isKeyPressed(Keys.SHIFT_LEFT))
camMoveSpeed *= camMoveSpeedAccelerationFactor;
if (Gdx.input.isKeyPressed(Keys.LEFT))
moveCamera(-camMoveSpeed, 0);
if (Gdx.input.isKeyPressed(Keys.RIGHT))
moveCamera(camMoveSpeed, 0);
if (Gdx.input.isKeyPressed(Keys.UP))
moveCamera(0, camMoveSpeed);
if (Gdx.input.isKeyPressed(Keys.DOWN))
moveCamera(0, -camMoveSpeed);
if (Gdx.input.isKeyPressed(Keys.BACKSPACE))
cameraHelper.setPosition(0, 0);
}
// Camera Controls (zoom)
float camZoomSpeed = 1 * deltaTime;
float camZoomSpeedAccelerationFactor = 5;
if (Gdx.input.isKeyPressed(Keys.SHIFT_LEFT))
camZoomSpeed *= camZoomSpeedAccelerationFactor;
if (Gdx.input.isKeyPressed(Keys.COMMA))
cameraHelper.addZoom(camZoomSpeed);
if (Gdx.input.isKeyPressed(Keys.PERIOD))
cameraHelper.addZoom(-camZoomSpeed);
if (Gdx.input.isKeyPressed(Keys.SLASH))
cameraHelper.setZoom(1);
}

在update里加上handleInputGame(deltaTime);

大家都知道有更加成熟的Box2D可以仿真物理世界。但是我们的兔子游戏并不是完全精确的仿真物理,甚至还有比较超物理的动作,如果用Box2D来模拟不真实的物理效果是很麻烦和多余的。

掉命,游戏结束以及其他:

我们通常都会在游戏结束之后经过3秒的过渡再跳转画面。

首先定义常量在Constants:// Delay after game over
public static final float TIME_DELAY_GAME_OVER = 3;

把下面的代码加到WorldController:判断玩家掉水里了没

    private float timeLeftGameOverDelay;

    public boolean isGameOver() {
return lives < 0;
} public boolean isPlayerInWater() {
return level.bunnyHead.position.y < -5;
}

修改init()和update():死掉一条命,右上角的小兔头就变透明

private void init() {
Gdx.input.setInputProcessor(this);
cameraHelper = new CameraHelper();
lives = Constants.LIVES_START;
timeLeftGameOverDelay = 0;
initLevel();
} public void update(float deltaTime) {
handleDebugInput(deltaTime);
if (isGameOver()) {
timeLeftGameOverDelay -= deltaTime;
if (timeLeftGameOverDelay < 0)
init();
} else {
handleInputGame(deltaTime);
}
level.update(deltaTime);
testCollisions();
cameraHelper.update(deltaTime);
if (!isGameOver() && isPlayerInWater()) {
lives--;
if (isGameOver())
timeLeftGameOverDelay = Constants.TIME_DELAY_GAME_OVER;
else
initLevel();
}
}

看看效果怎么样。

为了防止摄像机向下移动的太快,修改CameraHelpe的update()

public void update (float deltaTime) {
if (!hasTarget()) return;
position.x = target.position.x + target.origin.x;
position.y = target.position.y + target.origin.y;
// Prevent camera from moving down too far
position.y = Math.max(-1f, position.y);
}

在WorldRenderer中加上game over的信息:

    private void renderGuiGameOverMessage(SpriteBatch batch) {
float x = cameraGUI.viewportWidth / 2;
float y = cameraGUI.viewportHeight / 2;
if (worldController.isGameOver()) {
BitmapFont fontGameOver = Assets.instance.fonts.defaultBig;
fontGameOver.setColor(1, 0.75f, 0.25f, 1);
fontGameOver.drawMultiLine(batch, "GAME OVER", x, y, 0,
BitmapFont.HAlignment.CENTER);
fontGameOver.setColor(1, 1, 1, 1);
}
}

加上兔子捡到羽毛的剩余时间:

private void renderGuiFeatherPowerup(SpriteBatch batch) {
float x = -15;
float y = 30;
float timeLeftFeatherPowerup = worldController.level.bunnyHead.timeLeftFeatherPowerup;
if (timeLeftFeatherPowerup > 0) {
// Start icon fade in/out if the left power-up time
// is less than 4 seconds. The fade interval is set
// to 5 changes per second.
if (timeLeftFeatherPowerup < 4) {
if (((int) (timeLeftFeatherPowerup * 5) % 2) != 0) {
batch.setColor(1, 1, 1, 0.5f);
}
}
batch.draw(Assets.instance.feather.feather, x, y, 50, 50, 100, 100,
0.35f, -0.35f, 0);
batch.setColor(1, 1, 1, 1);
Assets.instance.fonts.defaultSmall.draw(batch, ""
+ (int) timeLeftFeatherPowerup, x + 60, y + 57);
}
}

然后把新加的这两个方法加入到renderGUI();

private void renderGui (SpriteBatch batch) {
batch.setProjectionMatrix(cameraGUI.combined);
batch.begin();
// draw collected gold coins icon + text
// (anchored to top left edge)
renderGuiScore(batch);
// draw collected feather icon (anchored to top left edge)
renderGuiFeatherPowerup(batch);
// draw extra lives icon + text (anchored to top right edge)
renderGuiExtraLive(batch);
// draw FPS text (anchored to bottom right edge)
renderGuiFpsCounter(batch);
// draw game over text
renderGuiGameOverMessage(batch);
batch.end();
}

Nice!到这里,游戏的主体基本完成了。犒赏一下自己,嗯,实在太棒了!多玩一玩自己的杰作,等休息好了我们再继续。

在下一章,我们将给游戏加上进入游戏之前的菜单界面,让游戏完整。