如何使用libgdx编写一个简单的游戏(二)— 完善
上一篇介绍游戏雏形的编写,这一篇将完善部分逻辑并添加更多效果。
例子代码在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个 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); }
然后新建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); } }
效果如下:
写在最后
这一篇修改了很多细节,可能部分很小但却关键的修改没有在文中标明。比如为Actor设置名称以便通过findActor方法获取。
如果直接复制有问题,可以从git库获取https://github.com/htynkn/DartsShaSha,对应的tag为page2。
apk地址是http://pan.baidu.com/share/link?shareid=331455&uk=4127624209
下一篇将会添加一些声音效果和资源加载,然后会添加一个统计功能。
作者:黄云坤
出处:http://www.huangyunkun.com/
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
支持:
新浪微博