如何使用libgdx编写一个简单的游戏(二)— 完善

本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

转载自夜明的孤行灯

本文链接地址: https://www.huangyunkun.com/2013/02/17/libgdx-game-2/

上一篇介绍游戏雏形的编写,这一篇将完善部分逻辑并添加更多效果。

例子代码在https://github.com/htynkn/DartsShaSha,如有需要请自行在tag中下载对应部分。

完善飞镖逻辑

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

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

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

这里的处理办法很简单,就是根据用户触摸位置,算出一个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个
returnfalse;
}

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

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

在touchDown方法中添加

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

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

更带感的对手

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

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

scythe

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

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

publicScythe(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
publicvoiddraw(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;

publicclass Scythe extends Actor {
TextureRegion[] walkFrames;// 保存每一帧
Animation animation;// 动画类
float stateTime;// 总时间
TextureRegion currentFrame;// 当前帧
int titleWidth =50;// 声明块宽度
int titleHeight =48;// 声明块高度

publicScythe(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
publicvoiddraw(SpriteBatch batch,float parentAlpha) {
stateTime += Gdx.graphics.getDeltaTime();// 获取总时间
currentFrame = animation.getKeyFrame(stateTime,true);// 获取当前关键帧
batch.draw(currentFrame,this.getX(),this.getY(),this.getWidth(),
this.getHeight());// 绘制
}
}

效果如下:

01

添加血条

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

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

绘制可以用很多种方法,我不怎么习惯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的效果

03

控制转换

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

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

先新建一个IController

package com.cnblogs.htynkn.controller;

import com.badlogic.gdx.scenes.scene2d.Group;
import com.badlogic.gdx.scenes.scene2d.Stage;

publicabstractclass IController extends Group {
publicabstractvoidupdate(Stage stage);
}

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

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

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

public BooleanisAlive() {
returnthis.currentHp >0;
}

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

publicvoidupdate(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方法,处理怪兽的逻辑

效果如下:

04

飞镖的杀伤力和手势识别

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

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

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

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

publicclass 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;

publicclass DartsController extends IController {

AtlasRegion region;

@Override
publicvoidupdate(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);
}
}
}

publicDartsController(AtlasRegion region) {
this.region = region;
}

publicvoidAddDarts(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 BooleanattackAlive(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 BooleancheckAlive(Actor projectile) {
if (projectile.getActions().size ==1) {
returnfalse;
}
returntrue;
}

public DartcreateDart() {
returnnew 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;

publicclass Dart extends Image {

Vector2 target;

publicDart(AtlasRegion region) {
super(region);
this.setOrigin(getWidth() /2, getHeight() /2);
this.addAction(Actions.repeat(50, Actions.rotateBy(360,0.5f)));
}

publicvoidsetTarget(Vector2 target) {
this.target = target;
}

publicvoidsetTarget(float x,float y) {
this.target =new Vector2(x, y);
}

public Vector2getTarget() {
return target;
}
}

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

publicclass 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;

publicclass DartsListener extends GestureAdapter {

Stage stage;

publicDartsListener(Stage stage) {
this.stage = stage;
}

@Override
publicbooleantouchDown(float x,float y,int pointer,int button) {
DartsController dartsController = (DartsController) stage.getRoot()
.findActor("dartsController");
if (dartsController.getChildren().size >=5) {// 限制飞镖的数量为5个
returnfalse;
}
Vector3 vector3 =new Vector3(x, y,0);
stage.getCamera().unproject(vector3);// 坐标转化
Actor man = stage.getRoot().findActor("player");
if (vector3.x < man.getX() +10) {// 如果触摸太靠近左侧就不响应
returnfalse;
}
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);
returntrue;
}

@Override
publicbooleanlongPress(float x,float y) {
returntrue;
}
}

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

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

在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;

publicclass DartsDetector extends GestureDetector {

Stage stage;

publicDartsDetector(Stage stage, GestureListener listener) {
super(listener);
this.stage = stage;
}
@Override
publicbooleantouchUp(float x,float y,int pointer,int button) {
DartsController dartsController = (DartsController) stage.getRoot()
.findActor("dartsController");
if (dartsController.getChildren().size >=5) {// 限制飞镖的数量为5个
returnfalse;
}
Vector3 vector3 =new Vector3(x, y,0);
stage.getCamera().unproject(vector3);// 坐标转化
Actor man = stage.getRoot().findActor("player");
if (vector3.x < man.getX() +10) {// 如果触摸太靠近左侧就不响应
returnsuper.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);
returnsuper.touchUp(x, y, pointer, button);
}
}

效果如下:

05

写在最后

这一篇修改了很多细节,可能部分很小但却关键的修改没有在文中标明。比如为Actor设置名称以便通过findActor方法获取。

如果直接复制有问题,可以从git库获取https://github.com/htynkn/DartsShaSha,对应的tag为page2。

apk地址是http://pan.baidu.com/share/link?shareid=331455&uk=4127624209

下一篇将会添加一些声音效果和资源加载,然后会添加一个统计功能。

本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

转载自夜明的孤行灯

本文链接地址: https://www.huangyunkun.com/2013/02/17/libgdx-game-2/

《如何使用libgdx编写一个简单的游戏(二)— 完善》有1条评论

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

    回复

发表评论