提要
经过前面的三篇文章,我们已经对libgdx有了一定的了解,并且搭建了一个简单的游戏场景,下面我们就继续在之前场景上添加更多的元素。
今天要实现的是精灵的行走、跳跃动画,还有碰撞检测。
行走
今天要实现的东西都是基于物理和数学的原理,首先我们来分析一下行走。
游戏中的动画都是帧动画,比如对Bob的行走一步的动画分解:
当这个序列连续播放的时候,就有了行走的效果。
之前已经有了移动的效果,所以只要在移动的时候循环播放动画就可以了。
现在需要的是决定帧的持续时间。
假设游戏渲染的是60FPS, 那么一帧的持续时间就是1/60s = 0.016 s。
对于一个成年人,每分钟大约走180步,那么每秒走的步数就是180/60 = 3,需要播放的帧为3×5 = 15.
那么每一帧持续时间就是1/15 = 0.066s.及66毫秒,即人物在运动的时候只要以66ms/帧对速度播放帧序列,就可以产生行走的效果了。
下面是代码实现。
首先是加载贴图。
和上一篇不同,这里用的是一次性加载贴图的方式,算是对游戏的一种优化。
需要和贴图textures.png一起添加到Android工程的asset/image/textures文件下的还有一个textures.pack文件,类似于一个xml,用于读取序列。
textures.png
format: RGBA8888
filter: Nearest,Nearest
repeat: none
block
rotate: false
xy: 1, 11
size: 48, 48
orig: 48, 48
offset: 0, 0
index: -1
bob-01
rotate: false
xy: 51, 31
size: 24, 28
orig: 24, 28
offset: 0, 0
index: -1
/*..omit..*/
接下来需要修改Bob类。
添加一个statetime成员用于记录Bob生存的时间,后面会用它来决定显示帧序列的哪一帧。
package com.me.testgdxgame.model;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
public class Bob {
public enum State {
IDLE, WALKING, JUMPING, DYING
}
public static final float SPEED = 4f; // unit per second
static final float JUMP_VELOCITY = 1f;
public static final float SIZE = 0.5f; // half a unit
Vector2 position = new Vector2();
Vector2 acceleration = new Vector2();
Vector2 velocity = new Vector2();
Rectangle bounds = new Rectangle();
State state = State.IDLE;
boolean facingLeft = true;
float stateTime = 0;
public Bob(Vector2 position) {
this.position = position;
this.bounds.height = SIZE;
this.bounds.width = SIZE;
}
public boolean isFacingLeft() {
return facingLeft;
}
public void setFacingLeft(boolean facingLeft) {
this.facingLeft = facingLeft;
}
public Vector2 getPosition() {
return position;
}
public Vector2 getAcceleration() {
return acceleration;
}
public Vector2 getVelocity() {
return velocity;
}
public Rectangle getBounds() {
return bounds;
}
public State getState() {
return state;
}
public void setState(State newState) {
this.state = newState;
}
public float getStateTime() {
return stateTime;
}
public void update(float delta) {
stateTime += delta;
position.add(velocity.cpy().mul(delta));
}
}
在libgdx中用于处理这种序列图的工具类是TextureAtlas。
getKeyFrame用于获取动画中的关键帧。
修改WorldRender类。
package com.me.testgdxgame.view;
import com.me.testgdxgame.*;
import com.me.testgdxgame.model.Block;
import com.me.testgdxgame.model.Bob;
import com.me.testgdxgame.model.Bob.State;
import com.me.testgdxgame.model.World;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.math.Rectangle;
public class WorldRenderer {
private World world;
private OrthographicCamera cam;
private static final float RUNNING_FRAME_DURATION = 0.06f;
private SpriteBatch spriteBatch;
private boolean debug=false;
private int width;
private int height;
private float ppuX; // pixels per unit on the X axis
private float ppuY; // pixels per unit on the Y axis
private static final float CAMERA_WIDTH = 10f;
private static final float CAMERA_HEIGHT = 7f;
/** Textures **/
private TextureRegion bobIdleLeft;
private TextureRegion bobIdleRight;
private TextureRegion blockTexture;
private TextureRegion bobFrame;
/** Animations **/
private Animation walkLeftAnimation;
private Animation walkRightAnimation;
/** for debug rendering **/
ShapeRenderer debugRenderer = new ShapeRenderer();
public WorldRenderer(World world) {
this.world = world;
this.cam = new OrthographicCamera(10, 7);
this.cam.position.set(5, 3.5f, 0);
this.cam.update();
spriteBatch=new SpriteBatch();
loadTextures();
}
public void setSize (int w, int h) {
this.width = w;
this.height = h;
ppuX = (float)width / CAMERA_WIDTH;
ppuY = (float)height / CAMERA_HEIGHT;
}
private void loadTextures() {
TextureAtlas atlas = new TextureAtlas(Gdx.files.internal("images/textures/textures.pack"));
bobIdleLeft = atlas.findRegion("bob-01");
bobIdleRight = new TextureRegion(bobIdleLeft);
bobIdleRight.flip(true, false);
blockTexture = atlas.findRegion("block");
TextureRegion[] walkLeftFrames = new TextureRegion[5];
for (int i = 0; i < 5; i++) {
walkLeftFrames[i] = atlas.findRegion("bob-0" + (i + 2));
}
walkLeftAnimation = new Animation(RUNNING_FRAME_DURATION, walkLeftFrames);
TextureRegion[] walkRightFrames = new TextureRegion[5];
for (int i = 0; i < 5; i++) {
walkRightFrames[i] = new TextureRegion(walkLeftFrames[i]);
walkRightFrames[i].flip(true, false);
}
walkRightAnimation = new Animation(RUNNING_FRAME_DURATION, walkRightFrames);
}
public void render() {
spriteBatch.begin();
drawBlocks();
drawBob();
spriteBatch.end();
if(debug) drawDebug();
}
private void drawBlocks(){
for (Block block : world.getBlocks()) {
spriteBatch.draw(blockTexture, block.getPosition().x * ppuX, block.getPosition().y * ppuY, Block.SIZE * ppuX, Block.SIZE * ppuY);
}
}
private void drawBob(){
Bob bob = world.getBob();
bobFrame = bob.isFacingLeft() ? bobIdleLeft : bobIdleRight;
//spriteBatch.draw(bobIdleLeft, bob.getPosition().x * ppuX, bob.getPosition().y * ppuY, Bob.SIZE * ppuX, Bob.SIZE * ppuY);
if(bob.getState().equals(State.WALKING)){
bobFrame = bob.isFacingLeft() ? walkLeftAnimation.getKeyFrame(bob.getStateTime(),true):walkRightAnimation.getKeyFrame(bob.getStateTime(),true);
}
spriteBatch.draw(bobFrame, bob.getPosition().x * ppuX, bob.getPosition().y * ppuY, Bob.SIZE * ppuX, Bob.SIZE * ppuY);
}
private void drawDebug(){
// render blocks
debugRenderer.setProjectionMatrix(cam.combined);
debugRenderer.begin(ShapeType.Rectangle);
for (Block block : world.getBlocks()) {
Rectangle rect = block.getBounds();
float x1 = block.getPosition().x + rect.x;
float y1 = block.getPosition().y + rect.y;
debugRenderer.setColor(new Color(1, 0, 0, 1));
debugRenderer.rect(x1, y1, rect.width, rect.height);
}
// render Bob
Bob bob = world.getBob();
Rectangle rect = bob.getBounds();
float x1 = bob.getPosition().x + rect.x;
float y1 = bob.getPosition().y + rect.y;
debugRenderer.setColor(new Color(0, 1, 0, 1));
debugRenderer.rect(x1, y1, rect.width, rect.height);
debugRenderer.end();
}
}
跑一下mian.java,键盘左右键就可以控制小人跑了。
跳跃
首先来复习一下初中的牛顿三定律...
第一定律:倘物体处于静止状态,或呈等速直线运动,只要没外力作用,物体将保持静止状态,或呈等速直线运动之状态。这定律又称为惯性定律。
第二定律:物体的加速度,与所受的净外力成正比。加速度的方向与净外力的方向相同。即 ;其中,是加速度, 是净外力, 是质量。
第三定律:两个物体的相互作用力总是大小相等,方向相反,同时出现或消失。强版第三定律还额外要求两支作用力的方向都处于同一直线。
分析一下Bob的状态。
最初始的时候,Bob处于静止状态,受两个力,一个是重力,一个是地面的支持力,二力平衡。
上图左边是Bob处于起跳状态,此时Bob用力蹬地面,地面给的反作用力大于Bob受的重力,Bob离开地面,起跳。
上图右边是Bob处于空中的状态,只受重力影响。
要用到一个速度计算公式:
v=u+at
u为初速度,a为加速度,t为时间。
下面看具体的代码实现。只需要在WorldController中添加对应的按键事件处理就可以了。
添加一些静态变量作为世界的参数。修改inputProcess和update函数。
注意仔细分析跳跃处理的逻辑。
package com.me.testgdxgame.controller;
import java.util.HashMap;
import java.util.Map;
import com.me.testgdxgame.model.Bob;
import com.me.testgdxgame.model.Bob.State;
import com.me.testgdxgame.model.World;
public class WorldController {
enum Keys{
LEFT,RIGHT,JUMP,FIRE
}
private World world;
private Bob bob;
private long jumpPressedTime;
private boolean jumpingPressed;
static Map<Keys,Boolean> keys = new HashMap<WorldController.Keys,Boolean>();
static {
keys.put(Keys.LEFT, false);
keys.put(Keys.RIGHT, false);
keys.put(Keys.JUMP, false);
keys.put(Keys.FIRE, false);
};
private static final long LONG_JUMP_PRESS = 150l;
private static final float ACCELERATION = 20f;
private static final float GRAVITY = -20f;
private static final float MAX_JUMP_SPEED = 7f;
private static final float DAMP = 0.8f;
private static final float MAX_VEL = 4f;
private static final float WIDTH =10f;
public WorldController(World w){
world=w;
bob=world.getBob();
}
//Key presses and touches
public void leftPressed(){
keys.get(keys.put(Keys.LEFT, true));
}
public void rightPressed() {
keys.get(keys.put(Keys.RIGHT, true));
}
public void jumpPressed() {
keys.get(keys.put(Keys.JUMP, true));
}
public void firePressed() {
keys.get(keys.put(Keys.FIRE, false));
}
public void leftReleased() {
keys.get(keys.put(Keys.LEFT, false));
}
public void rightReleased() {
keys.get(keys.put(Keys.RIGHT, false));
}
public void jumpReleased() {
keys.get(keys.put(Keys.JUMP, false));
jumpingPressed = false;
}
public void fireReleased() {
keys.get(keys.put(Keys.FIRE, false));
}
public void update(float delta){
processInput();
bob.getAcceleration().y = GRAVITY;
bob.getAcceleration().mul(delta);
bob.getVelocity().add(bob.getAcceleration().x, bob.getAcceleration().y);
if (bob.getAcceleration().x == 0) bob.getVelocity().x *= DAMP;
if (bob.getVelocity().x > MAX_VEL) {
bob.getVelocity().x = MAX_VEL;
}
if (bob.getVelocity().x < -MAX_VEL) {
bob.getVelocity().x = -MAX_VEL;
}
bob.update(delta);
//Set Bob's state to State.IDLE when Bob touch edge
if (bob.getPosition().y < 0) {
bob.getPosition().y = 0f;
bob.setPosition(bob.getPosition());
if (bob.getState().equals(State.JUMPING)) {
bob.setState(State.IDLE);
}
}
if (bob.getPosition().x < 0) {
bob.getPosition().x = 0;
bob.setPosition(bob.getPosition());
if (!bob.getState().equals(State.JUMPING)) {
bob.setState(State.IDLE);
}
}
if (bob.getPosition().x > WIDTH - bob.getBounds().width ) {
bob.getPosition().x = WIDTH - bob.getBounds().width;
bob.setPosition(bob.getPosition());
if (!bob.getState().equals(State.JUMPING)) {
bob.setState(State.IDLE);
}
}
}
private boolean processInput(){
if (keys.get(Keys.JUMP)) {
if (!bob.getState().equals(State.JUMPING)) {
jumpingPressed = true;
jumpPressedTime = System.currentTimeMillis();
bob.setState(State.JUMPING);
bob.getVelocity().y = MAX_JUMP_SPEED;
} else {
if (jumpingPressed && ((System.currentTimeMillis() - jumpPressedTime) >= LONG_JUMP_PRESS)) {
jumpingPressed = false;
} else {
if (jumpingPressed) {
bob.getVelocity().y = MAX_JUMP_SPEED;
}
}
}
}
if(keys.get(Keys.LEFT)){
bob.setFacingLeft(true);
if(!bob.getState().equals(State.JUMPING)){
bob.setState(State.WALKING);
}
bob.getVelocity().x=-ACCELERATION;
}else if (keys.get(Keys.RIGHT)) {
// left is pressed
bob.setFacingLeft(false);
if(!bob.getState().equals(State.JUMPING)){
bob.setState(State.WALKING);
}
bob.getVelocity().x=ACCELERATION;
}else {
if(!bob.getState().equals(State.JUMPING)){
bob.setState(State.IDLE);
} ;
// acceleration is 0 on the x
bob.getAcceleration().x = 0;
}
return false;
}
}
在WorldRender中还需要添加与跳跃对应的纹理还有修改drawBob函数。
加载纹理:
private void loadTextures() {
TextureAtlas atlas = new TextureAtlas(Gdx.files.internal("images/textures/textures.pack"));
bobJumpLeft = atlas.findRegion("bob-up");
bobJumpRight = new TextureRegion(bobJumpLeft);
bobJumpRight.flip(true, false);
bobFallLeft=atlas.findRegion("bob-down");
bobFallRight = new TextureRegion(bobFallLeft);
bobFallRight.flip(true, false);
bobIdleLeft = atlas.findRegion("bob-01");
bobIdleRight = new TextureRegion(bobIdleLeft);
bobIdleRight.flip(true, false);
blockTexture = atlas.findRegion("block");
TextureRegion[] walkLeftFrames = new TextureRegion[5];
for (int i = 0; i < 5; i++) {
walkLeftFrames[i] = atlas.findRegion("bob-0" + (i + 2));
}
walkLeftAnimation = new Animation(RUNNING_FRAME_DURATION, walkLeftFrames);
TextureRegion[] walkRightFrames = new TextureRegion[5];
for (int i = 0; i < 5; i++) {
walkRightFrames[i] = new TextureRegion(walkLeftFrames[i]);
walkRightFrames[i].flip(true, false);
}
walkRightAnimation = new Animation(RUNNING_FRAME_DURATION, walkRightFrames);
}
drawBob函数
private void drawBob(){
Bob bob = world.getBob();
bobFrame = bob.isFacingLeft() ? bobIdleLeft : bobIdleRight;
if(bob.getState().equals(State.WALKING)) {
bobFrame = bob.isFacingLeft() ? walkLeftAnimation.getKeyFrame(bob.getStateTime(), true) : walkRightAnimation.getKeyFrame(bob.getStateTime(), true);
} else if (bob.getState().equals(State.JUMPING)) {
if (bob.getVelocity().y > 0) {
bobFrame = bob.isFacingLeft() ? bobJumpLeft : bobJumpRight;
} else {
bobFrame = bob.isFacingLeft() ? bobFallLeft : bobFallRight;
}
}
spriteBatch.draw(bobFrame, bob.getPosition().x * ppuX, bob.getPosition().y * ppuY, Bob.SIZE * ppuX, Bob.SIZE * ppuY);
}
运行一下:
碰撞检测
现在的demo是可以穿墙的-_-!,这里的碰撞检测指的是Bob与Blocks之间的碰撞,当Bob撞墙的时候,Bob需要停止前进跳跃或是落下。
最简单的方法就是每update一次就检测Bob与每一个Block是否相撞,当然效率会非常低。
仔细来分析一下:每次与Bob可能发生碰撞的Block只有Bob身边的8块砖,那么在每次Update的时候与检测是否与身边的砖块相碰就可以了。
还可以再简单么?
看下面的图片。
假设Bob正在向右行走,那么在可能与Bob产生碰撞的Block中,只有下面的那个。所以,根据Bob当前的状态进行碰撞检测可以高效地进行碰撞检测。
我们用一个二维数组来保存地形,用于碰撞检测。
首先我们创建一个level,就是关卡,用world来装载,而不是直接在world里面生成。
package com.me.testgdxgame.model;
import com.badlogic.gdx.math.Vector2;
public class Level {
private int width;
private int height;
private Block[][] blocks;
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public Block[][] getBlocks() {
return blocks;
}
public void setBlocks(Block[][] blocks) {
this.blocks = blocks;
}
public Level() {
loadDemoLevel();
}
public Block get(int x, int y) {
return blocks[x][y];
}
private void loadDemoLevel() {
width = 10;
height = 7;
blocks = new Block[width][height];
for (int col = 0; col < width; col++) {
for (int row = 0; row < height; row++) {
blocks[col][row] = null;
}
}
for (int col = 0; col < 10; col++) {
blocks[col][0] = new Block(new Vector2(col, 0));
blocks[col][6] = new Block(new Vector2(col, 6));
if (col > 2) {
blocks[col][1] = new Block(new Vector2(col, 1));
}
}
blocks[9][2] = new Block(new Vector2(9, 2));
blocks[9][3] = new Block(new Vector2(9, 3));
blocks[9][4] = new Block(new Vector2(9, 4));
blocks[9][5] = new Block(new Vector2(9, 5));
blocks[6][3] = new Block(new Vector2(6, 3));
blocks[6][4] = new Block(new Vector2(6, 4));
blocks[6][5] = new Block(new Vector2(6, 5));
}
}
World类做对应的修改:
package com.me.testgdxgame.model;
import java.util.ArrayList;
import java.util.List;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
public class World {
/** Our player controlled hero **/
Bob bob;
/** A world has a level through which Bob needs to go through **/
Level level;
/** The collision boxes **/
Array<Rectangle> collisionRects = new Array<Rectangle>();
// Getters -----------
public Array<Rectangle> getCollisionRects() {
return collisionRects;
}
public Bob getBob() {
return bob;
}
public Level getLevel() {
return level;
}
/** Return only the blocks that need to be drawn **/
public List<Block> getDrawableBlocks(int width, int height) {
int x = (int)bob.getPosition().x - width;
int y = (int)bob.getPosition().y - height;
if (x < 0) {
x = 0;
}
if (y < 0) {
y = 0;
}
int x2 = x + 2 * width;
int y2 = y + 2 * height;
if (x2 > level.getWidth()) {
x2 = level.getWidth() - 1;
}
if (y2 > level.getHeight()) {
y2 = level.getHeight() - 1;
}
List<Block> blocks = new ArrayList<Block>();
Block block;
for (int col = x; col <= x2; col++) {
for (int row = y; row <= y2; row++) {
block = level.getBlocks()[col][row];
if (block != null) {
blocks.add(block);
}
}
}
return blocks;
}
// --------------------
public World() {
createDemoWorld();
}
private void createDemoWorld() {
bob = new Bob(new Vector2(7, 2));
level = new Level();
}
}
然后在WorldController中添加一个checkCollisionWithBlocks函数,函数里面首先检测X方向是否有碰撞,再检测Y方向是否有碰撞。
在update函数中,每运行一次就进行一次检测。
完整的WorldController类如下:
package com.me.testgdxgame.controller;
import java.util.HashMap;
import java.util.Map;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Pool;
import com.me.testgdxgame.model.Block;
import com.me.testgdxgame.model.Bob;
import com.me.testgdxgame.model.Bob.State;
import com.me.testgdxgame.model.World;
public class WorldController {
enum Keys {
LEFT, RIGHT, JUMP, FIRE
}
private static final long LONG_JUMP_PRESS = 150l;
private static final float ACCELERATION = 20f;
private static final float GRAVITY = -20f;
private static final float MAX_JUMP_SPEED = 7f;
private static final float DAMP = 0.90f;
private static final float MAX_VEL = 4f;
private World world;
private Bob bob;
private long jumpPressedTime;
private boolean jumpingPressed;
private boolean grounded = false;
// This is the rectangle pool used in collision detection
// Good to avoid instantiation each frame
private Pool<Rectangle> rectPool = new Pool<Rectangle>() {
@Override
protected Rectangle newObject() {
return new Rectangle();
}
};
static Map<Keys, Boolean> keys = new HashMap<WorldController.Keys, Boolean>();
static {
keys.put(Keys.LEFT, false);
keys.put(Keys.RIGHT, false);
keys.put(Keys.JUMP, false);
keys.put(Keys.FIRE, false);
};
// Blocks that Bob can collide with any given frame
private Array<Block> collidable = new Array<Block>();
public WorldController(World world) {
this.world = world;
this.bob = world.getBob();
}
// ** Key presses and touches **************** //
public void leftPressed() {
keys.get(keys.put(Keys.LEFT, true));
}
public void rightPressed() {
keys.get(keys.put(Keys.RIGHT, true));
}
public void jumpPressed() {
keys.get(keys.put(Keys.JUMP, true));
}
public void firePressed() {
keys.get(keys.put(Keys.FIRE, false));
}
public void leftReleased() {
keys.get(keys.put(Keys.LEFT, false));
}
public void rightReleased() {
keys.get(keys.put(Keys.RIGHT, false));
}
public void jumpReleased() {
keys.get(keys.put(Keys.JUMP, false));
jumpingPressed = false;
}
public void fireReleased() {
keys.get(keys.put(Keys.FIRE, false));
}
/** The main update method **/
public void update(float delta) {
// Processing the input - setting the states of Bob
processInput();
// If Bob is grounded then reset the state to IDLE
if (grounded && bob.getState().equals(State.JUMPING)) {
bob.setState(State.IDLE);
}
// Setting initial vertical acceleration
bob.getAcceleration().y = GRAVITY;
// Convert acceleration to frame time
bob.getAcceleration().mul(delta);
// apply acceleration to change velocity
bob.getVelocity().add(bob.getAcceleration().x, bob.getAcceleration().y);
// checking collisions with the surrounding blocks depending on Bob's velocity
checkCollisionWithBlocks(delta);
// apply damping to halt Bob nicely
bob.getVelocity().x *= DAMP;
// ensure terminal velocity is not exceeded
if (bob.getVelocity().x > MAX_VEL) {
bob.getVelocity().x = MAX_VEL;
}
if (bob.getVelocity().x < -MAX_VEL) {
bob.getVelocity().x = -MAX_VEL;
}
// simply updates the state time
bob.update(delta);
}
/** Collision checking **/
private void checkCollisionWithBlocks(float delta) {
// scale velocity to frame units
bob.getVelocity().mul(delta);
// Obtain the rectangle from the pool instead of instantiating it
Rectangle bobRect = rectPool.obtain();
// set the rectangle to bob's bounding box
bobRect.set(bob.getBounds().x, bob.getBounds().y, bob.getBounds().width, bob.getBounds().height);
// we first check the movement on the horizontal X axis
int startX, endX;
int startY = (int) bob.getBounds().y;
int endY = (int) (bob.getBounds().y + bob.getBounds().height);
// if Bob is heading left then we check if he collides with the block on his left
// we check the block on his right otherwise
if (bob.getVelocity().x < 0) {
startX = endX = (int) Math.floor(bob.getBounds().x + bob.getVelocity().x);
} else {
startX = endX = (int) Math.floor(bob.getBounds().x + bob.getBounds().width + bob.getVelocity().x);
}
// get the block(s) bob can collide with
populateCollidableBlocks(startX, startY, endX, endY);
// simulate bob's movement on the X
bobRect.x += bob.getVelocity().x;
// clear collision boxes in world
world.getCollisionRects().clear();
// if bob collides, make his horizontal velocity 0
for (Block block : collidable) {
if (block == null) continue;
if (bobRect.overlaps(block.getBounds())) {
bob.getVelocity().x = 0;
world.getCollisionRects().add(block.getBounds());
break;
}
}
// reset the x position of the collision box
bobRect.x = bob.getPosition().x;
// the same thing but on the vertical Y axis
startX = (int) bob.getBounds().x;
endX = (int) (bob.getBounds().x + bob.getBounds().width);
if (bob.getVelocity().y < 0) {
startY = endY = (int) Math.floor(bob.getBounds().y + bob.getVelocity().y);
} else {
startY = endY = (int) Math.floor(bob.getBounds().y + bob.getBounds().height + bob.getVelocity().y);
}
populateCollidableBlocks(startX, startY, endX, endY);
bobRect.y += bob.getVelocity().y;
for (Block block : collidable) {
if (block == null) continue;
if (bobRect.overlaps(block.getBounds())) {
if (bob.getVelocity().y < 0) {
grounded = true;
}
bob.getVelocity().y = 0;
world.getCollisionRects().add(block.getBounds());
break;
}
}
// reset the collision box's position on Y
bobRect.y = bob.getPosition().y;
// update Bob's position
bob.getPosition().add(bob.getVelocity());
bob.getBounds().x = bob.getPosition().x;
bob.getBounds().y = bob.getPosition().y;
// un-scale velocity (not in frame time)
bob.getVelocity().mul(1 / delta);
}
/** populate the collidable array with the blocks found in the enclosing coordinates **/
private void populateCollidableBlocks(int startX, int startY, int endX, int endY) {
collidable.clear();
for (int x = startX; x <= endX; x++) {
for (int y = startY; y <= endY; y++) {
if (x >= 0 && x < world.getLevel().getWidth() && y >=0 && y < world.getLevel().getHeight()) {
collidable.add(world.getLevel().get(x, y));
}
}
}
}
/** Change Bob's state and parameters based on input controls **/
private boolean processInput() {
if (keys.get(Keys.JUMP)) {
if (!bob.getState().equals(State.JUMPING)) {
jumpingPressed = true;
jumpPressedTime = System.currentTimeMillis();
bob.setState(State.JUMPING);
bob.getVelocity().y = MAX_JUMP_SPEED;
grounded = false;
} else {
if (jumpingPressed && ((System.currentTimeMillis() - jumpPressedTime) >= LONG_JUMP_PRESS)) {
jumpingPressed = false;
} else {
if (jumpingPressed) {
bob.getVelocity().y = MAX_JUMP_SPEED;
}
}
}
}
if (keys.get(Keys.LEFT)) {
// left is pressed
bob.setFacingLeft(true);
if (!bob.getState().equals(State.JUMPING)) {
bob.setState(State.WALKING);
}
bob.getAcceleration().x = -ACCELERATION;
} else if (keys.get(Keys.RIGHT)) {
// left is pressed
bob.setFacingLeft(false);
if (!bob.getState().equals(State.JUMPING)) {
bob.setState(State.WALKING);
}
bob.getAcceleration().x = ACCELERATION;
} else {
if (!bob.getState().equals(State.JUMPING)) {
bob.setState(State.IDLE);
}
bob.getAcceleration().x = 0;
}
return false;
}
}
运行截图:
总结
这篇blog的内容有点多,需要多花时间去体会。
特别是碰撞检测,思路一定要缕清晰。
这个游戏也暂时告一段落了,主要原因是国外的那位大牛也没有更新此系列的文章了...
当然,游戏还在继续。
工程下载
参考:Getting Started in Android Game Development with libgdx-http://obviam.net/