求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
     
   
分享到
如何使用libgdx编写一个简单的游戏
 
火龙果软件    发布于 2013-9-4
 

(一)— 雏形

这个系列主要讲述了如何使用Cocos2D编写简单的游戏。稍微读读感觉不错,所以想写个libgdx版本的。

本篇文章主要讲述基本内容的编写,包括显示人物、怪兽和飞镖。

最终效果如下图:

获取libgdx

你可以从libgdx的官网下载打包好的代码,我下载的是0.98版本。

当然,你也可以从git代码仓库获取最新的版本的,或者你习惯使用的以前版本,比如0.97。

创建项目

libgdx项目的创建可以有多种方式,我推荐使用setup-ui。方便易用还可以省去很多麻烦,特别是ADT升级以后的ClassNotFound问题。

如果是下载打包好的,那么就默认包含了gdx-setup-ui,双击就可以打开。

填写一些基本信息,然后选中你下载的0.98.zip那个压缩文件。这里我只生成一个桌面项目和Android项目。

桌面项目是方便调试,而Android项目是最后发布的。在整个开发中我始终用桌面项目调试,因为速度快,容易排错。同时周期性的在Android真机上测试。

点击生成项目,然后在Eclipse中导入。

一般导入进去以后Android项目会有一些问题,修改project.properties文件和AndroidManifest.xml配置文件。

运行效果如下:

准备工作

本例子中用到的图片如下:

用gdx-texturepacker打包成一张大图。

我整个例子都是用的是Stage模式。所以它的坐标原点在左下角,如果是一般用Spirte直接绘制,那么原点在右上角。

首先将打包好的图册复制到assets中新建的pack文件夹。

然后我们开始动工了,首先删除setup-ui生成的多余代码,整理DartsShaSha.java文件如下:

package com.cnblogs.htynkn;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.scenes.scene2d.Stage;
public class DartsShaSha extends ApplicationAdapter {
    Stage stage;
    @Override
    public void create() {
        stage = new Stage(480, 320, true);
    }
    @Override
    public void dispose() {
        stage.dispose();
    }
    @Override
    public void render() {
        Gdx.gl.glClearColor(1, 1, 1, 1);
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
        stage.act();
        stage.draw();
    }
}

这时候运行效果是一个白茫茫的画面。

注意一下这句

stage = new Stage(480, 320, true); 

因为我希望屏幕的自适应有Stage自动完成,所以坐标基本可以写死。

先不着急开工,我们先添加一个现实FPS的标签。我希望这个标签显示在屏幕右下角。

在create方法中添加

LabelStyle labelStyle = new LabelStyle(new BitmapFont(), Color.BLACK); //创建一个Label样式,使用默认黑色字体
        Label label = new Label("FPS:", labelStyle); //创建标签,显示的文字是FPS:
        label.setName("fpsLabel"); //设置标签名称为fpsLabel
        label.setY(0); //设置Y为0,即显示在最下面
        label.setX(480 - label.getTextBounds().width); //设置X值,显示为最后一个字紧靠屏幕最右侧
        stage.addActor(label); //将标签添加到舞台

在render方法中更新fps的值


Label label = (Label) stage.getRoot().findActor("fpsLabel"); //获取名为fpsLabel的标签

label.setText("FPS:" + Gdx.graphics.getFramesPerSecond());

label.setX(480 - label.getTextBounds().width); //更新X值以保证显示位置正确性

效果如下:

添加忍者

现在来添加我们的主角,我希望主角显示在屏幕左侧中央。所以它的x值必然是0,但是它的y值并不是320的一半,而是160减去图片高度的一半。

主角其实就是一张图片,并没有太多特别的效果,所以我使用Image类。

首先获取图册

TextureAtlas atlas = new TextureAtlas("pack/default.pack"); 

在从图册中获取Player.png并创建Image对象。

Image man = new Image(atlas.findRegion("Player")); //获取图册中的Player.png并创建image对象
        man.setX(0);
        man.setY(160 - man.getHeight() / 2); //设置Y值,以让图片在中间显示
        stage.addActor(man); //将主角添加到舞台

效果如下:


添加怪兽

然后我们来添加几只怪兽。怪兽应该是随机从屏幕右侧出现,并直线移动到屏幕左侧。

同时我们还要检测怪兽的生命值什么的,或者其他效果,所以为了方便处理,我们专门建立一个Group来管理怪兽。

新建类TargetGroup,并集成Group类。

package com.cnblogs.htynkn;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.scenes.scene2d.Group;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
public class TargetGroup extends Group {
    public TargetGroup(AtlasRegion region) {
        super();
    }
}

因为还需要传入怪兽的图片,所以我们的创建方法保留参数AtlasRegion region。

怪兽的Y值因为是随机的,但是又不能超出屏幕。所以用随机数来生成。libgdx的MathUtils提供了相关方法。

int minY = 0;
        int maxY = (int) (320 - region.getRegionHeight());
        int tempY = MathUtils.random(minY, maxY);

这里还有一个问题需要注意,就是怪兽之间不应该出现遮挡,所以对于生成的Y值还需要进行判断。

假设我们要生成3只怪兽,那么代码应该如下:

int tempY = 0;
        for (int i = 0; i < 3; i++) {
            Image image = new Image(region);
            image.setX(480 - image.getWidth());
            // 开始判断Y值是否符合要求
            boolean flag = false;
            do {
                flag = false;
                tempY = MathUtils.random(minY, maxY); // 生成Y值
                Actor[] actors = this.getChildren().begin(); // 获取当前已有的怪兽对象
                for (int j = 0; j < this.getChildren().size; j++) {
                    Actor tempActor = actors[j];
                    if (tempY == tempActor.getY()) { // 如果Y值相等,比如重合,所以舍弃,重新生成
                        flag = true;
                        break;
                    } else if (tempY < tempActor.getY()) { // 如果生成的Y值小于当前怪兽的Y值,
则判断生成的Y值加上高度后是否合适
                        if ((tempY + region.getRegionHeight()) >= tempActor
                                .getY()) {
                            flag = true;
                            break;
                        }
                    } else { // 如果生成的Y值大于当前怪兽的Y值,则判断当前怪兽的Y值加上高度后是否合适
                        if (tempY <= (tempActor.getY() + region
                                .getRegionHeight())) {
                            flag = true;
                            break;
                        }
                    }
                }
            } while (flag);
            image.setY(tempY);
            this.addActor(image); //添加到组中

在主类的create方法中添加

TargetGroup group = new TargetGroup(atlas.findRegion("Target"));
        stage.addActor(group);

效果如下:

目前怪兽还不能移动,这里需要一个简单的动画效果,libgdx中的Action可以办到。

考虑到怪兽是水平移动,即Y值不变,X值变小。

所以添加一个方法

public void AddMove(Actor actor, float time) {
        actor.addAction(Actions.moveTo(0, actor.getY(), time));
    }

怪兽的移动速度也随机一下,代码如下

image.setY(tempY);
this.AddMove(image, MathUtils.random(3f, 8f)); //怪兽移动效果
this.addActor(image); //添加到组中

效果如下:

添加武器

我们的主角自然不能赤手空拳和怪兽进行搏斗,现在添加一些飞镖。

假定用户触摸屏幕以后,主角就向触摸位置发射一个飞镖。

因为飞镖的数量不一定,所以我这里创建一个专门的类ProjectileFactory来处理。

首先是飞镖的创建,和怪兽群一样的原因,我还是希望一个专门的组来管理。

创建一个专门的方法来创建飞镖

public static Image createProjectile(AtlasRegion region, Actor man,
            Vector3 target) {
        Image image = new Image(region);
        image.setX(man.getX() + man.getWidth() / 2);
        image.setY(man.getY() + man.getHeight() / 2);
        image.addAction(Actions.moveTo(target.x, target.y, 2f)); //设置飞镖的移动
        return image;
    }

在主类进行一些修改以便其可以获取屏幕的触摸。

首先修改类声明为

public class DartsShaSha extends InputAdapter implements ApplicationListener

其实具体也可以两个都实现接口,主要是我觉得看着不舒服。

重写touchDown方法

public boolean touchDown(int screenX, int screenY, int pointer, int button) {
        Vector3 vector3 = new Vector3(screenX, screenY, 0);
        stage.getCamera().unproject(vector3); // 坐标转化
        projectiles.addActor(ProjectileFactory.createProjectile(
                atlas.findRegion("Projectile"), man, vector3)); // 添加新飞镖到飞镖组
        return true;
    }

在create方法中添加新的Group并设置Input响应。

stage.addActor(projectiles); //添加飞镖组到舞台
        InputMultiplexer multiplexer = new InputMultiplexer(); //多输入接收器
        multiplexer.addProcessor(this); //添加自身作为接收
        multiplexer.addProcessor(stage); //添加舞台
        Gdx.input.setInputProcessor(multiplexer); //设置多输入接收器为接收器

效果如下:

更完善的飞镖

飞镖虽然添加出来了,但是飞镖没有转动…而且飞镖没有在到达目的地后自动消失。

现在先来添加旋转效果,libgdx提供了rotateBy方法。

在创建飞镖的createProjectile方法中添加

image.addAction(Actions.repeat(50, Actions.rotateBy(360,  0.5f))); //设置飞镖的旋转 

这个不方便截图,就不展示效果了。

现在来考虑如何让飞镖到达目的后消失。首先来看看我们的Image对象,它包含了两个Action,一个是旋转Action,另外一个移动Action。

我们可以检测Action的数量,如果只有一个Action,我们可以断定飞镖只是在旋转而已经到达目的地了。这个时候就可以把它删除了。

添加一个专门的方法来判断飞镖是否应该移除了

public static Boolean checkAlive(Actor projectile) {
        if (projectile.getActions().size == 1) {
            return false;
        }
        return true;
    }

在render方法中添加处理代码

// 飞镖的移除判
        Actor[] projectile = projectiles.getChildren().begin(); //获取Actor数组
        for (int j = 0; j < projectiles.getChildren().size; j++) { //移除判断
            Actor actor = projectile[j];
            if (!ProjectileFactory.checkAlive(actor)) {
                projectiles.removeActor(actor);
            }
        }

效果如下:

现在飞镖可以自动消失了,并且也在旋转了。不过旋转效果很奇怪,它并不是沿中心旋转,而是沿着左下角旋转的。

重新设置中心

image.setOrigin(image.getWidth() / 2, image.getHeight() / 2);

现在一切正常了。

碰撞检测和杀敌

当然,发出飞镖的目的自然是杀敌,现在马上来添加这个功能。

我们可以把怪兽看着一个矩形,即飞镖击中任何位置都算作有效。而飞镖就以其中心为代表。

创建方法attackAlive

public static Boolean attackAlive(Actor target, Actor projectile) {
        Rectangle rectangle = new Rectangle(target.getX(), target.getY(),
                target.getWidth(), target.getHeight()); // 创建一个矩形
        return rectangle.contains(
                projectile.getX() + projectile.getWidth() / 2,
                projectile.getY() + projectile.getHeight() / 2); //判断是否在矩阵中,即是否击中
    }

在render方法中修改

// 开始处理飞镖
        Actor[] projectile = projectiles.getChildren().begin();
        Actor[] targets = targetGroup.getChildren().begin();
        for (int i = 0; i < projectiles.getChildren().size; i++) {
            Actor actor = projectile[i];
            for (int j = 0; j < targetGroup.getChildren().size; j++) {
                Actor target = targets[j];
                if (ProjectileFactory.attackAlive(target, actor)) {
                    targetGroup.removeActor(target);
                    projectiles.removeActor(actor);
                    break;
                }
            }
        }
        // 如果飞镖已经飞到则刪除
        projectile = projectiles.getChildren().begin();
        for (int j = 0; j < projectiles.getChildren().size; j++) {
            Actor actor = projectile[j];
            if (!ProjectileFactory.checkAlive(actor)) {
                projectiles.removeActor(actor);
            }
        }

效果如下:

写在最后

虽然实现了个大概,但是仔细看看其实问题还是很多的,后面的文章会提到进一步的修改。包括逻辑上的完善,声音效果,预加载,背景绘制,集成第三方社交和广告等等。

这片文章对应demo可以从这里下载来试试。

http://pan.baidu.com/share/link?shareid=328840&uk=4127624209

我用的2.2的sdk编译的,低版本没有测试。我的手机是ZTE V880,fps50上下。

ps:testin的测试结果是通过率 100.00%

ps: 代码上传到github上了,地址 https://github.com/htynkn/DartsShaSha

文章对应的代码的tag为page 1。

(二)— 完善

完善飞镖逻辑

现在的飞镖可以旋转可以飞行了,但是有一个问题却没有解决。

首先飞镖的速度,如果用户触摸位置很靠近左侧,那么飞镖的速度就很慢了。

其次,如果用户触摸中间位置,默认情况下飞镖应该是朝那个方向飞行,而不是飞到触摸位置就消失了。

这里的处理办法很简单,就是根据用户触摸位置,算出一个X为480的值,这样飞镖就可以飞到最右侧,同时保持相当的速度。

在createProjectile方法中添加

float r = (target.y - image.getY()) / (target.x - image.getX()); //获取斜率
float detY = r * 480; //获取Y的变动量
image.addAction(Actions.moveTo(480 + image.getX(), detY + image.getY(),
                2f)); // 设置飞镖的移动

这样基本就解决了问题。

接下来来思考飞镖的数量和相应位置。

首先飞镖的速度一定要得到限制,不然满屏幕飞镖有什么意思。这里限制飞镖的数量为5个。

在touchDown的最开始添加

if (projectiles.getChildren().size >= 5) { //限制飞镖的数量为5个
    return false;
}

这样当屏幕上的飞镖数量大于等于5时就不会产生新的飞镖了。

还有就是触摸的位置,如果触摸的位置太靠右的话,会出现飞镖倒飞或者速度过快的问题,所以当触摸位置太靠近左侧的时候就不释放飞镖。

在touchDown方法中添加

if (vector3.x < man.getX() + 5) { //如果触摸太靠近左侧就不响应
    return false;
}

这里的5是我随便写的,仅仅表示个意思。测试一下,觉得5还是太小了,最后我改成10了。

更带感的对手

说实话,现在的对手一动不动,只会匀速平移。我们先改进它的外貌吧。

我从http://untamed.wild-refuge.net/rmxpresources.php?characters获取到如下图片

打包以后放入assets文件夹中。

因为libgdx只有默认Animation类,但是没法办法直接在stage中使用,所以新建一个Scythe类并继承Actor类。

public Scythe(AtlasRegion atlasRegion) {
    super();
    this.setWidth(titleWidth); //设置高度
    this.setHeight(titleHeight); //设置宽度
    TextureRegion[][] temp = atlasRegion.split(titleWidth, titleHeight); //分割图块
    walkFrames = new TextureRegion[4]; //获取第二行的4帧
    for (int i = 0; i < 4; i++) {
        walkFrames[i] = temp[1][i];
    }
    animation = new Animation(0.1f, walkFrames); //创建动画,帧间隔0.1
}

因为原图宽200,高192,一共16张图,所以每一块的宽就是50,高48。使用Animation类需要手动提供相关帧,并通过Animation和当前时间获取的帧。

重写draw方法如下

@Override
public void draw(SpriteBatch batch, float parentAlpha) {
    stateTime += Gdx.graphics.getDeltaTime(); //获取总时间
    currentFrame = animation.getKeyFrame(stateTime, true); //获取当前关键帧
    batch.draw(currentFrame, this.getX(), this.getY(), this.getWidth(),
            this.getHeight()); //绘制
}

修改TargetGroup中有关region.getRegionHeight()的部分,全部除以4。同时修改Image类Scythe类。

最后完整的Scythe如下

package com.cnblogs.htynkn;
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.scenes.scene2d.Actor;
public class Scythe extends Actor {
    TextureRegion[] walkFrames; // 保存每一帧
    Animation animation; // 动画类
    float stateTime; // 总时间
    TextureRegion currentFrame; // 当前帧
    int titleWidth = 50; // 声明块宽度
    int titleHeight = 48; // 声明块高度
    public Scythe(AtlasRegion atlasRegion) {
        super();
        this.setWidth(titleWidth); // 设置高度
        this.setHeight(titleHeight); // 设置宽度
        TextureRegion[][] temp = atlasRegion.split(titleWidth, titleHeight); // 分割图块
        walkFrames = new TextureRegion[4]; // 获取第二行的4帧
        for (int i = 0; i < 4; i++) {
            walkFrames[i] = temp[1][i];
        }
        animation = new Animation(0.1f, walkFrames); // 创建动画,帧间隔0.1
    }
    @Override
    public void draw(SpriteBatch batch, float parentAlpha) {
        stateTime += Gdx.graphics.getDeltaTime(); // 获取总时间
        currentFrame = animation.getKeyFrame(stateTime, true); // 获取当前关键帧
        batch.draw(currentFrame, this.getX(), this.getY(), this.getWidth(),
                this.getHeight()); // 绘制
    }
}

效果如下:

添加血条

我们来试试在给予怪兽血量,即有些怪兽可以承受两次伤害。这里我们将用到比较基本的东西。

首先是血条的位置,一般来看应该在怪兽正上方,以红色显示。

绘制可以用很多种方法,我不怎么习惯mesh那套,所以这里我使用Pixmap类。

先在Sythe类中添加两个变量

int margin = 2; // 血条和人物之间的间隔
int pixHeight = 5; // 血条高度

然后在绘制方法中添加

Pixmap pixmap = new Pixmap(64, 8, Format.RGBA8888); //生成一张64*8的图片
pixmap.setColor(Color.BLACK); //设置颜色为黑色
pixmap.drawRectangle(0, 0, titleWidth, pixHeight); //绘制边框
Texture pixmaptex = new Texture(pixmap); //生成图片
TextureRegion pix = new TextureRegion(pixmaptex, titleWidth, pixHeight); //切割图片
batch.draw(pix, this.getX(), this.getY() + this.titleHeight
        + this.margin); //绘制

这样我们就有了一个黑色的边框了

然后就是血量的填充了,在添加两个变量以记录总血量和当前血量

int maxHp; // 总血量
int currentHp; // 当前血量

绘制血条的代码添加到绘制完黑框的语句后面

pixmap.setColor(Color.RED); // 设置颜色为红色
pixmap.fillRectangle(0, 1, titleWidth * currentHp / maxHp,
        pixHeight - 2); // 绘制血条

最后一定要释放掉pixmap

pixmap.dispose();

这是设置总血量为2,当前血量为1的效果

控制转换

增加了血量以后我们的代码也需要修改了,在主类DartsShaSha中修改相关的逻辑。

为了方便起见我们将游戏的逻辑赋给控制器。

先新建一个IController

package com.cnblogs.htynkn.controller;
import com.badlogic.gdx.scenes.scene2d.Group;
import com.badlogic.gdx.scenes.scene2d.Stage;
public abstract class IController extends Group {
    public abstract void update(Stage stage);
}L>

然后新建TargetController类,重写update方法,在这个方法中我们处理相关逻辑,然后在主类中只需要调用方法就可以。

首先将中已有的代码拷贝过来,然后在Sythe中添加两个方法来处理受到伤害和死亡判断。

public void beAttacked(int damage) {
    if (this.currentHp > damage) { // 如果血量大于伤害就扣除响应数值
        currentHp = currentHp - damage;
    } else if (this.currentHp > 0) { // 如果血量小于伤害但是仍有血量就让血量归零
        currentHp = 0;
    }
}
public Boolean isAlive() {

    return this.currentHp > 0;
}

然后在TargetController中添加update的相关代码

public void update(Stage stage) {
    Group projectiles = (Group) stage.getRoot().findActor("projectiles"); // 获取飞镖所在Group
    Actor[] projectile = projectiles.getChildren().begin();
    Actor[] targets = this.getChildren().begin();
    for (int i = 0; i < projectiles.getChildren().size; i++) {
        Actor actor = projectile[i];
        for (int j = 0; j < this.getChildren().size; j++) {
            Actor target = targets[j];
            if (ProjectileFactory.attackAlive(target, actor)) {
                Scythe scythe = (Scythe) target;
                scythe.beAttacked(1);
                projectiles.removeActor(actor);
                if (!scythe.isAlive()) {
                    this.removeActor(target);
                }
                break;
            }
        }
    }
}

然后在主类中修改相关的实例化代码,在render中调用update方法。

targetController.update(this.stage); //调用update方法,处理怪兽的逻辑

效果如下:

飞镖的杀伤力和手势识别

上面的代码中每一个飞镖的杀伤力是1,每一个怪兽的血量是2。

现在我们来修改一下飞镖的控制,让飞镖也采用控制器来处理。

在这里设定为触摸一下屏幕是发射一般的飞镖,杀伤力为1。而长按以后的杀伤力是2。

libgdx中提供了一个手势识别的接口,实现它就可以了。这里我选择继承GestureAdapter。

public class DartsListener extends GestureAdapter

同时修改飞镖的控制为控制器模式,新建控制器DartsController。

代码如下:

package com.cnblogs.htynkn.controller;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.actions.Actions;
import com.cnblogs.htynkn.elements.Dart;
public class DartsController extends IController {
    AtlasRegion region;
    @Override
    public void update(Stage stage) {
        // 如果飞镖已经飞到则刪除
        Actor[] projectile = this.getChildren().begin();
        for (int j = 0; j < this.getChildren().size; j++) {
            Actor actor = projectile[j];
            if (!this.checkAlive(actor)) {
                this.removeActor(actor);
            }
        }
    }
    public DartsController(AtlasRegion region) {
        this.region = region;
    }
    public void AddDarts(Dart dart) {
        if (this.getChildren().size >= 5) { //如果飞镖数量大于等于5个就结束
            return;
        }
        float r = (dart.getTarget().y - dart.getY())
                / (dart.getTarget().x - dart.getX()); // 获取斜率
        float detY = r * 480; // 获取Y的变动量
        dart.addAction(Actions.moveTo(480 + dart.getX(), detY + dart.getY(), 2f)); // 设置飞镖的移动
        this.addActor(dart);
    }
    public Boolean attackAlive(Actor target, Actor projectile) {
        Rectangle rectangle = new Rectangle(target.getX(), target.getY(),
                target.getWidth(), target.getHeight()); // 创建一个矩形
        return rectangle.contains(
                projectile.getX() + projectile.getWidth() / 2,
                projectile.getY() + projectile.getHeight() / 2); // 判断是否在矩阵中,即是否击中
    }
    public Boolean checkAlive(Actor projectile) {
        if (projectile.getActions().size == 1) {
            return false;
        }
        return true;
    }
    public Dart createDart() {
        return new Dart(region);
    }
}

其中Dart类代码如下:

package com.cnblogs.htynkn.elements;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.actions.Actions;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
public class Dart extends Image {
    Vector2 target;
    public Dart(AtlasRegion region) {
        super(region);
        this.setOrigin(getWidth() / 2, getHeight() / 2);
        this.addAction(Actions.repeat(50, Actions.rotateBy(360, 0.5f)));
    }
    public void setTarget(Vector2 target) {
        this.target = target;
    }
    public void setTarget(float x, float y) {
        this.target = new Vector2(x, y);
    }
    public Vector2 getTarget() {
        return target;
    }
}

因为我们的输入接受另外有类DartsListener来处理,所以修改主类的继承如下:

public class DartsShaSha implements ApplicationListener

在multiplexer中添加我们新的手势识别器

GestureDetector gestureDetector = new GestureDetector(

        new DartsListener(this.stage));

multiplexer.addProcessor(gestureDetector); // 添加手势识别

目前DartsListener中代码如下

package com.cnblogs.htynkn.listener;
import com.badlogic.gdx.input.GestureDetector.GestureAdapter;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.cnblogs.htynkn.controller.DartsController;
import com.cnblogs.htynkn.elements.Dart;
public class DartsListener extends GestureAdapter {
    Stage stage;
    public DartsListener(Stage stage) {
        this.stage = stage;
    }
    @Override
    public boolean touchDown(float x, float y, int pointer, int button) {
        DartsController dartsController = (DartsController) stage.getRoot()
                .findActor("dartsController");
        if (dartsController.getChildren().size >= 5) { // 限制飞镖的数量为5个
            return false;
        }
        Vector3 vector3 = new Vector3(x, y, 0);
        stage.getCamera().unproject(vector3); // 坐标转化
        Actor man = stage.getRoot().findActor("player");
        if (vector3.x < man.getX() + 10) { // 如果触摸太靠近左侧就不响应
            return false;
        }
        Dart dart = dartsController.createDart();
        dart.setX(man.getX() + man.getWidth() / 2);
        dart.setY(man.getY() + man.getHeight() / 2);
        dart.setTarget(vector3.x, vector3.y);
        dartsController.AddDarts(dart);
        return true;
    }
    @Override
    public boolean longPress(float x, float y) {
        return true;
    }
}

可能还有其他细节的修改,详细的请参考代码。

目前我们只能算是重构了一下,游戏效果并没有改变。现在来设置飞镖的杀伤力和长按的处理。

在Dart中添加属性

int power;

在实例化中添加

power = 1; //默认杀伤力为1 

将TargetController中的

scythe.beAttacked(1); 

修改为

scythe.beAttacked(dart.getPower());

而中的longPress方法基本和touchDown相同,只是增加了

dart.setPower(2); //设置杀伤力为2

dart.setColor(Color.RED); //设置成红色

来思考一下处理流程,用户触摸屏幕,首先会触发tap事件,然后是touchDown,最后才是longPress。

也就是目前我们的游戏长按一下会发出一个普通的飞镖,然后才是我们的红色飞镖。

要处理这个问题,我们添加一个DartsDetector类,继承GestureDetector类。

因为事件的触发顺序是tap->touchdown->longpress->touchup。

所以我们的事件逻辑全部转移到touchup中,如果是longpress事件就发出红色飞镖,如果是touchdown就发出普通飞镖。

由于我们的DartsListener已经不处理任何逻辑了,所以删除其中所有代码。

GestureDetector中的代码如下

package com.cnblogs.htynkn.listener;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.input.GestureDetector;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.cnblogs.htynkn.controller.DartsController;
import com.cnblogs.htynkn.elements.Dart;
public class DartsDetector extends GestureDetector {
    Stage stage;
    public DartsDetector(Stage stage, GestureListener listener) {
        super(listener);
        this.stage = stage;
    }
    @Override
    public boolean touchUp(float x, float y, int pointer, int button) {
        DartsController dartsController = (DartsController) stage.getRoot()
                .findActor("dartsController");
        if (dartsController.getChildren().size >= 5) { // 限制飞镖的数量为5个
            return false;
        }
        Vector3 vector3 = new Vector3(x, y, 0);
        stage.getCamera().unproject(vector3); // 坐标转化
        Actor man = stage.getRoot().findActor("player");
        if (vector3.x < man.getX() + 10) { // 如果触摸太靠近左侧就不响应
            return super.touchUp(x, y, pointer, button);
        }
        Dart dart = dartsController.createDart();
        dart.setX(man.getX() + man.getWidth() / 2);
        dart.setY(man.getY() + man.getHeight() / 2);
        dart.setTarget(vector3.x, vector3.y);
        if (this.isLongPressed()) { //如果是长按就变成红色飞镖
            dart.setPower(2); // 设置杀伤力为2
            dart.setColor(Color.RED); // 设置成红色
        }
        dartsController.AddDarts(dart);
        return super.touchUp(x, y, pointer, button);
    }
}

效果如下:

写在最后

虽然实现了个大概,但是仔细看看其实问题还是很多的,后面的文章会提到进一步的修改。包括逻辑上的完善,声音效果,预加载,背景绘制,集成第三方社交和广告等等。

这片文章对应demo可以从这里下载来试试。

http://pan.baidu.com/share/link?shareid=328840&uk=4127624209

我用的2.2的sdk编译的,低版本没有测试。我的手机是ZTE V880,fps50上下。

ps:testin的测试结果是通过率 100.00%

ps: 代码上传到github上了,地址 https://github.com/htynkn/DartsShaSha

文章对应的代码的tag为page 1。

(三)— 人性化

这一篇主要是添加一些让游戏更人性化的东西,比如音效和加载画面,菜单等等。

这其中用到了很多资源,主要出自以下几个网站,大家有需要也可以去上面寻找。

添加音效

首先是飞镖发出时候的音效,我希望是类似"bing"的一声,要短小精炼。

我使用的是http://www.freesound.org/people/BMacZero/sounds/96132/

libgdx支持的音频主要是WAV, MP3和OGG,其他的支持需要扩展支持。

libgdx中的音频有两种,一种是sound,一种是music。

一般所谓的音效使用sound,而音乐就使用music。

在DartController中添加属性

Sound bing;

在实例化方法中添加

this.bing = Gdx.audio.newSound(Gdx.files.internal("audio/bing.wav")); 

在AddDart方法中添加

bing.play();

在怪兽被消灭的时候的音效我选用http://www.freesound.org/people/yottasounds/sounds/174465/

在TargetController中添加

great = Gdx.audio.newSound(Gdx.files.internal("audio/great.wav"));

最后添加一个背景音乐。我使用的是http://www.freesound.org/people/Setuniman/sounds/167453/

大小为5.7M,个人感觉有点大。

而且我需要循环播放,这就意味着末尾的这个部分的我不需要。

使用在线转换器截取并转化成ogg。

转化以后大小变成465kb了。仔细听一下,感觉很多部分是重复,所以再一次截取,只要前4秒的。

转化好以后在主类添加

backgroundMusic = Gdx.audio.newMusic(Gdx.files
        .internal("audio/background.ogg"));
backgroundMusic.setLooping(true); //循环播放
backgroundMusic.setVolume(0.4f); //设置音量
backgroundMusic.play(); //播放

因为music可以在resume和pause时自动播放和暂停,所以不需要其他的处理。在游戏关闭时需要将其释放掉。

backgroundMusic.dispose();

Game-Screen模式和资源预加载

到目前为止我们的所有操作都在一个界面上,但是一个游戏中可能有很多不同的界面和场景,比如菜单,帮助,关卡1,关卡2等等。

libgdx提供了Game-Screen模式,即只有一个Game,但是Game中有很多Screen。通过setScreen来切换。

这里注意一下Game所提供的切换功能没有任何过渡效果,后面会有添加过多效果的例子。

新建DartsGame类,继承Game类。在darts-shasha-android项目和darts-shasha-desktop项目中分别修改入口代码

package com.cnblogs.htynkn;
import android.os.Bundle;
import com.badlogic.gdx.backends.android.AndroidApplication;
import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration;
public class MainActivity extends AndroidApplication {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        AndroidApplicationConfiguration cfg = new AndroidApplicationConfiguration();
        cfg.useGL20 = false;
        initialize(new DartsGame(), cfg);
    }
}

package com.cnblogs.htynkn;
import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;
public class Main {
    public static void main(String[] args) {
        LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration();
        cfg.title = "darts-shasha";
        cfg.useGL20 = false;
        cfg.width = 480;
        cfg.height = 320;
        new LwjglApplication(new DartsGame(), cfg);
    }
}

新建一个类ShaScreen,实现Screen接口,其中的代码基本和原来的主类一样。唯一的区别就是create中的代码需要移动到show中。

在DartsGame中添加代码

package com.cnblogs.htynkn;
import com.badlogic.gdx.Game;
import com.cnblogs.htynkn.screen.ShaScreen;
public class DartsGame extends Game {
    @Override
    public void create() {
        ShaScreen screen = new ShaScreen();
        this.setScreen(screen);
    }
}

这样我们就转化到Game-Screen模式。

当我们在游戏中使用了很多资源的时候直接new的话经常会卡顿,比如我们这个demo再加入音效后进入时间明显变长了。

所以我们这里需要一个预加载过程,而libgdx提供的AssetManager可以实现这个功能。

加载中使用一个小图标加文字形式。由于要使用中文,同时文字量很小,所以使用hiero作图。

新建一个LoadingScreen类作为加载页面。

因为AssetManager的作用很特殊,很多类都可以要调用它,所以我一般使用单例模式。

在DartsGame添加

public static AssetManager manager;
public static AssetManager getManager() {
    if (manager == null) {
        manager = new AssetManager();
    }
    return manager;
}

在LoadingScreen中通过

AssetManager manager = DartsGame.getManager();

获取manager。然后通过load方法添加要加载的资源

manager.load("audio/background.ogg", Music.class);
manager.load("audio/bing.wav", Sound.class);
manager.load("audio/great.wav", Sound.class);
manager.load("pack/sha/default.pack", TextureAtlas.class);

这里只是告诉manager要加载什么资源,并没有实际加载。

在render方法中调用

DartsGame.getManager().update()

来启动加载并获取是否加载完成,如果返回值为true那么就表明加载完成。

其他类通过

DartsGame.getManager().get()

来获取资源。

LoadingScreen代码如下:

package com.cnblogs.htynkn.screen;
import com.badlogic.gdx.Game;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.audio.Music;
import com.badlogic.gdx.audio.Sound;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.actions.Actions;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.Label.LabelStyle;
import com.cnblogs.htynkn.DartsGame;
public class LoadingScreen implements Screen {
    Game game;
    Stage stage;
    @Override
    public void render(float delta) {
        Gdx.gl.glClearColor(0, 0, 0, 1);
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
        if (DartsGame.getManager().update()) {
            game.setScreen(new ShaScreen());
        }
        stage.act();
        stage.draw();
    }
    @Override
    public void resize(int width, int height) {

        // TODO Auto-generated method stub
    }
    @Override
    public void show() {
        AssetManager manager = DartsGame.getManager();
        manager.load("audio/background.ogg", Music.class);
        manager.load("audio/bing.wav", Sound.class);
        manager.load("audio/great.wav", Sound.class);
        manager.load("pack/sha/default.pack", TextureAtlas.class);
        stage = new Stage(480, 320, true);
        BitmapFont font = new BitmapFont(
                Gdx.files.internal("pack/loading/font.fnt"), false);
        LabelStyle labelStyle = new LabelStyle(font, Color.WHITE);
        Label label = new Label("游戏加载中...", labelStyle);
        label.setPosition(160, 150);
        stage.addActor(label);
        Image image = new Image(
                new TextureAtlas(Gdx.files
                        .internal("pack/loading/default.pack"))
                        .findRegion("ninja_attack"));
        image.setOrigin(image.getWidth() / 2, image.getHeight() / 2);
        image.addAction(Actions.repeat(1000, Actions.rotateBy(360, 3f)));
        image.setPosition(100, 140);
        stage.addActor(image);
    }
    @Override
    public void hide() {
        // TODO Auto-generated method stub
    }
    @Override
    public void pause() {
        // TODO Auto-generated method stub
    }
    @Override
    public void resume() {
        // TODO Auto-generated method stub
    }
    @Override
    public void dispose() {
        // TODO Auto-generated method stub
    }
    public LoadingScreen(Game game) {
        this.game = game;
    }
}

效果如下:

当我们不需要某个资源时通过

manager.unload

来销毁。或者使用

manager.clear();

来清空所有资源。

添加友盟统计

统计功能对于一个应用是必须的。你可以获取到用户的信息,比如分辨率,系统版本,还可以快速获取到错误等等。

libgdx是一个游戏框架,没有自带的统计功能,这里我选用友盟统计。

首先申请key

申请完成以后获取Appkey并下载SDK,拷贝到android项目的libs文件夹中。

在AndroidManifest.xml中添加相关信息

详细的情况参考友盟的文档。

现在来看一下友盟文档,最常用的是

MobclickAgent.onResume(this);

MobclickAgent.onPause(this);

为了方便开发,我们新建一个接口IStatisticsService。

package com.cnblogs.htynkn.extension;
public interface IStatisticsService {
void onResume();
void onPause();
} }

然后在DartsGame类添加

public static IStatisticsService statisticsService;

然后在Android项目中添加AndroidStatisticsService

package com.cnblogs.htynkn.extension;
import android.content.Context;
import com.umeng.analytics.MobclickAgent;
public class AndroidStatisticsService implements IStatisticsService {
    Context context;
    public AndroidStatisticsService(Context context) {
        this.context = context;
    }
    @Override
    public void onResume() {
        MobclickAgent.onResume(context);
    }
    @Override
    public void onPause() {
        MobclickAgent.onPause(context);
    }
}

修改入口文件为

package com.cnblogs.htynkn;
import android.os.Bundle;
import com.badlogic.gdx.backends.android.AndroidApplication;
import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration;
import com.cnblogs.htynkn.extension.AndroidStatisticsService;
public class MainActivity extends AndroidApplication {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AndroidApplicationConfiguration cfg = new AndroidApplicationConfiguration();
cfg.useGL20 = false;
DartsGame dartsGame = new DartsGame();
DartsGame.setStatisticsService(new AndroidStatisticsService(this));
initialize(dartsGame, cfg);
}
}

同样的,在桌面项目中添加DesktopStatisticsService

package com.cnblogs.htynkn.extension;
import android.content.Context;
import com.umeng.analytics.MobclickAgent;
public class AndroidStatisticsService implements IStatisticsService {
Context context;
public AndroidStatisticsService(Context context) {
this.context = context;
}
@Override
public void onResume() {
MobclickAgent.onResume(context);
}
@Override
public void onPause() {
MobclickAgent.onPause(context);
}
}

修改入口文件

package com.cnblogs.htynkn;
import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;
import com.cnblogs.htynkn.extension.DesktopStatisticsService;

public class Main {
public static void main(String[] args) {
LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration();
cfg.title = "飞镖杀杀";
cfg.useGL20 = false;
cfg.width = 480;
cfg.height = 320;
DartsGame.setStatisticsService(new DesktopStatisticsService());
new LwjglApplication(new DartsGame(), cfg);
}
}

因为桌面项目并没有统计功能,所以以输出log替代。

然后在游戏相应位置调用即可。

其他需要的功能办法基本相同,就是实现相应接口,然后不同的平台传入不同实现。没有实现的就用mock输出。

依照这种方法还可以添加其他第三方功能。

不过很遗憾友盟SDK升级以后我死活用不上了…现在换成百度APP统计了。原理是一样的,具体参考源码。

比如在Android的启动文件中重写onPause和onResume

package com.cnblogs.htynkn;
import android.os.Bundle;
import com.badlogic.gdx.backends.android.AndroidApplication;
import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration;
import com.cnblogs.htynkn.extension.AndroidStatisticsService;
public class MainActivity extends AndroidApplication {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AndroidApplicationConfiguration cfg = new AndroidApplicationConfiguration();
cfg.useGL20 = false;
DartsGame dartsGame = new DartsGame();
DartsGame.setStatisticsService(new AndroidStatisticsService(this));
initialize(dartsGame, cfg);
}
@Override
protected void onResume() {
super.onResume();
DartsGame.getStatisticsService().onResume();
}
@Override
protected void onPause() {
super.onPause();
DartsGame.getStatisticsService().onPause();
}
}

统计结果

实话,友盟的统计比百度的好很多,但是不知为嘛我用不起了。

写在最后

本来这一篇写好很久了,但是我一直想加入一个菜单和游戏结束的界面,不过最近实在没有时间,先发出来这个部分吧。

如果有时间以后会补上的。

 
相关文章

手机软件测试用例设计实践
手机客户端UI测试分析
iPhone消息推送机制实现与探讨
Android手机开发(一)
 
相关文档

Android_UI官方设计教程
手机开发平台介绍
android拍照及上传功能
Android讲义智能手机开发
相关课程

Android高级移动应用程序
Android系统开发
Android应用开发
手机软件测试
 
分享到
 
 


android人机界面指南
Android手机开发(一)
Android手机开发(二)
Android手机开发(三)
Android手机开发(四)
iPhone消息推送机制实现探讨
手机软件测试用例设计实践
手机客户端UI测试分析
手机软件自动化测试研究报告
更多...   


Android高级移动应用程序
Android应用开发
Android系统开发
手机软件测试
嵌入式软件测试
Android软、硬、云整合


领先IT公司 android开发平台最佳实践
北京 Android开发技术进阶
某新能源领域企业 Android开发技术
某航天公司 Android、IOS应用软件开发
阿尔卡特 Linux内核驱动
艾默生 嵌入式软件架构设计
西门子 嵌入式架构设计
更多...